MP-Gadget  5.0.1.dev1-76bc7d4726-dirty
cooling_rates.c
Go to the documentation of this file.
1 /* This version of cooling.c is reimplemented from a
2  * python rate network, to cleanly allow self-shielding,
3  * metal cooling and UVB fluctuations. It shares no code
4  * with the original Gadget-3 cooling code.
5  *
6  * Copyright (c) Simeon Bird 2019 and Yu Feng 2015-2019.
7  * Released under the terms of the GPLv2 or later.
8  */
9 
10 /* A rate network for neutral hydrogen following
11  * Katz, Weinberg & Hernquist 1996, astro-ph/9509107, eq. 28-32.
12 
13  Most internal methods are CamelCapitalized and follow a convention that
14  they are named like the process and then the ion they refer to.
15  eg:
16  CollisionalExciteHe0 is the neutral Helium collisional excitation rate.
17  RecombHp is the recombination rate for ionized hydrogen.
18 
19  Externally useful methods (the API) are named like get_*.
20  These are:
21  get_temp() - gets the temperature from the density and internal energy.
22  get_heatingcooling_rate() - gets the total (net) heating and cooling rate from density and internal energy.
23  get_neutral_fraction_phys_cgs() - gets the neutral fraction from the rate network given density and internal energy in physical cgs units.
24  get_helium_ion_fraction_phys_cgs() - gets the neutral fraction from the rate network given density and internal energy in physical cgs units.
25  get_global_UVBG() - Interpolates the TreeCool table to a desired redshift and returns a struct UVBG.
26  Two useful helper functions:
27  get_equilib_ne() - gets the equilibrium electron density.
28  get_ne_by_nh() - gets the above, divided by the hydrogen density (Gadget reports this as ElectronAbundance).
29 
30  Global Variables:
31  redshift - the redshift at which to evaluate the cooling. Affects the photoionization rate,
32  the Inverse Compton cooling and the self shielding threshold.
33  photo_factor - Factor by which to multiply the UVB amplitude.
34  f_bar - Baryon fraction. Omega_b / Omega_cdm.
35  selfshield - Flag to enable self-shielding following Rahmati 2012
36  cool - which cooling rate coefficient table to use.
37  Supported are: KWH (original Gadget rates)
38  Nyx (rates used in Nyx (Lukic 2015) and in Enzo 2.
39  Sherwood (rates used in the Sherwood simulation,
40  which fix a bug in the Cen 92 rates, but are otherwise the same as KWH.
41  Sherwood also uses the more accurate recombination and collisional ionization rates).
42  recomb - which recombination and collisional ionization rate table to use.
43  Supported are: C92 (Cen 1992, the Gadget default)
44  V96 (Verner & Ferland 1996, more accurate rates). Voronov 97 is used for collisional ionizations.
45  B06 (Badnell 2006 rates, current cloudy defaults. Very similar to V96).
46 
47  The default is to follow what is done in the Sherwood simulations, Bolton et al 2016 1605.03462
48  These are essentially KWH but with collisional ionization and recombination rates and cooling changed to match Verner & Ferland 96.
49  The Cen 92 high temp correction factor is also changed.
50  treecool_file - File to read a UV background from. Same format as Gadget has always used.
51 */
52 
53 #include "cooling_rates.h"
54 #include "cooling_qso_lightup.h"
55 #include "cosmology.h"
56 
57 #include <omp.h>
58 #include <math.h>
59 #include <mpi.h>
60 #include <stdio.h>
61 #include <string.h>
62 #include <gsl/gsl_interp.h>
63 #include "physconst.h"
64 #include "utils/endrun.h"
65 #include "utils/paramset.h"
66 #include "utils/mymalloc.h"
67 
68 static struct cooling_params CoolingParams;
69 
70 static gsl_interp * GrayOpac;
71 
72 /*Tables for the self-shielding correction. Note these are not well-measured for z > 5!*/
73 #define NGRAY 6
74 /* Gray Opacity for the Faucher-Giguere 2009 UVB. HM2018 is a little larger and would lead to a 10% higher self-shielding threshold.*/
75 static const double GrayOpac_ydata[NGRAY] = { 2.59e-18, 2.37e-18, 2.27e-18, 2.15e-18, 2.02e-18, 1.94e-18};
76 static const double GrayOpac_zz[NGRAY] = {0, 1, 2, 3, 4, 5};
77 
78 /*Convenience structure bundling together the gsl interpolation routines.*/
79 struct itp_type
80 {
81  double * ydata;
82  gsl_interp * intp;
83 };
84 /*Interpolation objects for the redshift evolution of the UVB.*/
85 /*Number of entries in the table*/
86 static int NTreeCool;
87 /*Redshift bins*/
88 static double * Gamma_log1z;
89 /*These are the photo-ionization rates*/
90 static struct itp_type Gamma_HI, Gamma_HeI, Gamma_HeII;
91 /*These are the photo-heating rates*/
92 static struct itp_type Eps_HI, Eps_HeI, Eps_HeII;
93 
94 /*Recombination and collisional rates*/
95 #define NRECOMBTAB 1000
96 #define RECOMBTMAX log(1e9)
97 #define RECOMBTMIN 0 //log(1)
98 static double * temp_tab;
99 static double * rec_alphaHp, * rec_alphaHep, * rec_alphaHepp;
100 static double * rec_GammaH0, * rec_GammaHe0, * rec_GammaHep;
103 /*For the Free-free cooling rate*/
104 static double * cool_freefree1;
105 
106 static void
107 init_itp_type(double * xarr, struct itp_type * Gamma, int Nelem)
108 {
109  Gamma->intp = gsl_interp_alloc(gsl_interp_linear,Nelem);
110  gsl_interp_init(Gamma->intp, xarr, Gamma->ydata, Nelem);
111 }
112 
113 /* Helper function to correctly load a value in the TREECOOL file*/
114 static double
115 load_tree_value(char ** saveptr)
116 {
117  double data = atof(strtok_r(NULL, " \t", saveptr));
118  if(data > 0)
119  return log10(data);
120  return -9000;
121 }
122 
123 /* This function loads the treecool file into the (global function) data arrays.
124  * Format of the treecool table:
125  log_10(1+z), Gamma_HI, Gamma_HeI, Gamma_HeII, Qdot_HI, Qdot_HeI, Qdot_HeII,
126  where 'Gamma' is the photoionization rate and 'Qdot' is the photoheating rate.
127  The Gamma's are in units of s^-1, and the Qdot's are in units of erg s^-1.
128 */
129 static void
130 load_treecool(const char * TreeCoolFile)
131 {
133  return;
134  FILE * fd = fopen(TreeCoolFile, "r");
135  if(!fd)
136  endrun(456, "Could not open photon background (TREECOOL) file at: '%s'\n", TreeCoolFile);
137 
138  /*Find size of file*/
139  NTreeCool = 0;
140  do
141  {
142  char buffer[1024];
143  char * retval = fgets(buffer, 1024, fd);
144  /*Happens on end of file*/
145  if(!retval)
146  break;
147  retval = strtok(buffer, " \t");
148  /*Discard comments*/
149  if(!retval || retval[0] == '#')
150  continue;
151  NTreeCool++;
152  }
153  while(1);
154  rewind(fd);
155 
156  MPI_Bcast(&(NTreeCool), 1, MPI_INT, 0, MPI_COMM_WORLD);
157 
158  if(NTreeCool<= 2)
159  endrun(1, "Photon background contains: %d entries, not enough.\n", NTreeCool);
160 
161  /*Allocate memory for the photon background table.*/
162  Gamma_log1z = (double *) mymalloc("TreeCoolTable", 7 * NTreeCool * sizeof(double));
163  Gamma_HI.ydata = Gamma_log1z + NTreeCool;
164  Gamma_HeI.ydata = Gamma_log1z + 2 * NTreeCool;
166  Eps_HI.ydata = Gamma_log1z + 4 * NTreeCool;
167  Eps_HeI.ydata = Gamma_log1z + 5 * NTreeCool;
169 
170  int ThisTask;
171  MPI_Comm_rank(MPI_COMM_WORLD, &ThisTask);
172  if(ThisTask == 0)
173  {
174  int i = 0;
175  while(i < NTreeCool)
176  {
177  char buffer[1024];
178  char * saveptr;
179  char * line = fgets(buffer, 1024, fd);
180  /*Happens on end of file*/
181  if(!line)
182  break;
183  char * retval = strtok_r(line, " \t", &saveptr);
184  if(!retval || retval[0] == '#')
185  continue;
186  Gamma_log1z[i] = atof(retval);
187  /*Get the rest*/
188  Gamma_HI.ydata[i] = load_tree_value(&saveptr);
189  Gamma_HeI.ydata[i] = load_tree_value(&saveptr);
190  Gamma_HeII.ydata[i] = load_tree_value(&saveptr);
191  Eps_HI.ydata[i] = load_tree_value(&saveptr)+ CoolingParams.HydrogenHeatAmp;
192  Eps_HeI.ydata[i] = load_tree_value(&saveptr);
193  Eps_HeII.ydata[i] = load_tree_value(&saveptr);
194  i++;
195  }
196 
197  fclose(fd);
198  }
199 
200  /*Broadcast data to other processors*/
201  MPI_Bcast(Gamma_log1z, 7 * NTreeCool, MPI_DOUBLE, 0, MPI_COMM_WORLD);
202  /*Initialize the UVB redshift interpolation: reticulate the splines*/
203  init_itp_type(Gamma_log1z, &Gamma_HI, NTreeCool);
204  init_itp_type(Gamma_log1z, &Gamma_HeI, NTreeCool);
207  init_itp_type(Gamma_log1z, &Eps_HeI, NTreeCool);
209 
210  message(0, "Read %d lines z = %g - %g from file %s\n", NTreeCool, pow(10, Gamma_log1z[0])-1, pow(10, Gamma_log1z[NTreeCool-1])-1, TreeCoolFile);
211 }
212 
213 /*Get photo ionization rate for neutral Hydrogen*/
214 static double
215 get_photo_rate(double redshift, struct itp_type * Gamma_tab)
216 {
218  return 0;
219  double log1z = log10(1+redshift);
220  double photo_rate;
221  if (NTreeCool < 1 || log1z >= Gamma_log1z[NTreeCool - 1])
222  return 0;
223  else if (log1z < Gamma_log1z[0])
224  photo_rate = Gamma_tab->ydata[0];
225  else {
226  photo_rate = gsl_interp_eval(Gamma_tab->intp, Gamma_log1z, Gamma_tab->ydata, log1z, NULL);
227  }
228  return pow(10, photo_rate) * CoolingParams.PhotoIonizeFactor;
229 }
230 
231 /*Calculate the critical self-shielding density.
232  *This formula is taken from Rahmati 2012 eq. 13. https://arxiv.org/abs/1210.7808
233  gray_opac is a parameter of the UVB
234  Here we use that for the Faucher-Giguere 2009 UVB.
235  HM2018 is a little larger and would lead to a 10% higher self-shielding threshold.
236  gray_opac is in cm^2 (2.49e-18 is HM01 at z=3)
237  temp is particle temperature in K
238  f_bar is the baryon fraction. 0.17 is roughly 0.045/0.265
239  Returns density in atoms/cm^3.
240  At higher redshifts than this was computed,
241  we keep the self-shielding density constant. In reality the reionization model should take over.
242 */
243 static double
244 self_shield_dens(double redshift, const struct UVBG * uvbg)
245 {
246  /*Before the UVBG switches on, no need for self-shielding*/
247  if(uvbg->gJH0 == 0)
248  return 1e10;
249  double G12 = uvbg->gJH0/1e-12;
250  double greyopac;
251  if (redshift <= GrayOpac_zz[0])
252  greyopac = GrayOpac_ydata[0];
253  else if (redshift >= GrayOpac_zz[NGRAY-1])
254  greyopac = GrayOpac_ydata[NGRAY-1];
255  else {
256  greyopac = gsl_interp_eval(GrayOpac, GrayOpac_zz, GrayOpac_ydata,redshift, NULL);
257  }
258  return 6.73e-3 * pow(greyopac / 2.49e-18, -2./3)*pow(G12, 2./3)*pow(CoolingParams.fBar/0.17,-1./3);
259 }
260 
261 /* This initializes a global UVBG by interpolating the redshift tables,
262  * to which the UV fluctuations can be applied*/
263 struct UVBG get_global_UVBG(double redshift)
264 {
265  struct UVBG GlobalUVBG = {0};
266 
268  return GlobalUVBG;
269 
270  /* Set the homogeneous reionization redshift as the point when the UVB switches on.*/
271  GlobalUVBG.zreion = pow(10,Gamma_log1z[NTreeCool - 1])-1;
272 
275 
276  /* if a threshold is set, disable UV bg above that redshift */
278  return GlobalUVBG;
279 
280 
281  GlobalUVBG.gJH0 = get_photo_rate(redshift, &Gamma_HI);
282  GlobalUVBG.gJHe0 = get_photo_rate(redshift, &Gamma_HeI);
283  GlobalUVBG.gJHep = get_photo_rate(redshift, &Gamma_HeII);
284 
285  GlobalUVBG.epsH0 = get_photo_rate(redshift, &Eps_HI);
286  GlobalUVBG.epsHe0 = get_photo_rate(redshift, &Eps_HeI);
287  /* During helium reionization we have a model for the inhomogeneous non-equilibrium heating.
288  * To avoid double counting, remove the heating in the existing UVB*/
289  if(during_helium_reionization(redshift))
290  GlobalUVBG.epsHep = 0;
291  else
292  GlobalUVBG.epsHep = get_photo_rate(redshift, &Eps_HeII);
293  GlobalUVBG.self_shield_dens = self_shield_dens(redshift, &GlobalUVBG);
294  return GlobalUVBG;
295 }
296 
297 /*Correction to the photoionisation rate as a function of density from Rahmati 2012, eq. 14.
298  Calculates Gamma_{Phot} / Gamma_{UVB}.
299  Inputs: hydrogen density, temperature
300  n_H
301  The coefficients are their best-fit from appendix A."""
302 */
303 static double
304 self_shield_corr(double nh, double logt, double ssdens)
305 {
306  /* Turn off self-shielding for low-density gas.
307  * If such gas becomes very cold, this is not strictly what they find,
308  * but I think it is more physical*/
309  if(!CoolingParams.SelfShieldingOn || nh < ssdens * 0.01)
310  return 1;
311  /* T/1e4**0.17*/
312  double T4 = exp(0.17 * (logt - log(1e4)));
313  double nSSh = 1.003*ssdens*T4;
314  return 0.98*pow(1+pow(nh/nSSh,1.64),-2.28)+0.02*pow(1+nh/nSSh, -0.84);
315 }
316 
317 /*Here come the recombination rates*/
318 
319 /* Recombination rates and collisional ionization rates, as a function of temperature. There are three options implemented.
320  *
321  * Cen92:
322  * used in standard Gadget and Illustris.
323  * Taken from KWH 06, astro-ph/9509107, Table 2, based on Cen 1992.
324  *
325  * Verner96:
326  * the fit from Verner & Ferland 1996 (astro-ph/9509083).
327  * Collisional rates are the fit from Voronov 1997 (http://www.sciencedirect.com/science/article/pii/S0092640X97907324).
328  * In a very photoionised medium this changes the neutral hydrogen abundance by approximately 10% compared to Cen 1992.
329  * These rates are those used by Nyx and Sherwood.
330  *
331  * Badnell06:
332  * Recombination rates are the fit from Badnell's website: http://amdpp.phys.strath.ac.uk/tamoc/RR/#partial.
333  * These are used in CLOUDY, and for our purposes basically identical to Verner 96. Included for completeness.
334  */
335 
336 /*Formula used as a fitting function in Verner & Ferland 1996 (astro-ph/9509083).*/
337 static double
338 _Verner96Fit(double temp, double aa, double bb, double temp0, double temp1)
339 {
340  double sqrttt0 = sqrt(temp/temp0);
341  double sqrttt1 = sqrt(temp/temp1);
342  return aa / ( sqrttt0 * pow(1 + sqrttt0, 1-bb)*pow(1+sqrttt1, 1+bb) );
343 }
344 
345 /*Recombination rate for H+, ionized hydrogen, in cm^3/s. Temp in K.*/
346 double
347 recomb_alphaHp(double temp)
348 {
349  switch(CoolingParams.recomb)
350  {
351  case Cen92:
352  return 8.4e-11 / sqrt(temp) / pow(temp/1000, 0.2) / (1+ pow(temp/1e6, 0.7));
353  case Verner96:
354  /*The V&F 96 fitting formula is accurate to < 1% in the worst case.
355  See line 1 of V&F96 table 1.*/
356  return _Verner96Fit(temp, 7.982e-11, 0.748, 3.148, 7.036e+05);
357  case Badnell06:
358  /*Slightly different fit coefficients*/
359  return _Verner96Fit(temp, 8.318e-11, 0.7472, 2.965, 7.001e5);
360  default:
361  endrun(3, "Recombination rate not supported\n");
362  }
363 }
364 
365 /*Helper function to implement Verner & Ferland 96 recombination rate.*/
366 static double
367 _Verner96alphaHep(double temp)
368 {
369  /*Accurate to ~2% for T < 10^6 and 5% for T< 10^10.
370  * VF96 give two rates. The first is more accurate for T < 10^6, the second is valid up to T = 10^10.
371  * We use the most accurate allowed. See lines 2 and 3 of Table 1 of VF96.*/
372  double lowTfit = _Verner96Fit(temp, 3.294e-11, 0.6910, 1.554e+01, 3.676e+07);
373  double highTfit = _Verner96Fit(temp, 9.356e-10, 0.7892, 4.266e-02, 4.677e+06);
374  /*Note that at 10^6K the two fits differ by ~10%. This may lead one to disbelieve the quoted accuracies!
375  * We thus switch over at a slightly lower temperature. The two fits cross at T ~ 3e5K.*/
376  double swtmp = 7e5;
377  double deltat = 1e5;
378  double upper = swtmp + deltat;
379  double lower = swtmp - deltat;
380  //In order to avoid a sharp feature at 10^6 K, we linearly interpolate between the two fits around 10^6 K.
381  double interpfit = (lowTfit * (upper - temp) + highTfit * (temp - lower))/(2*deltat);
382  return (temp < lower)*lowTfit + (temp > upper)*highTfit + (upper > temp)*(temp > lower)*interpfit;
383 }
384 
385 /*Recombination rate for He+, ionized helium, in cm^3/s. Temp in K.*/
386 static double
387 recomb_alphaHep(double temp)
388 {
389  switch(CoolingParams.recomb)
390  {
391  case Cen92:
392  return 1.5e-10 / pow(temp,0.6353);
393  case Verner96:
394  return _Verner96alphaHep(temp);
395  case Badnell06:
396  return _Verner96Fit(temp, 1.818E-10, 0.7492, 10.17, 2.786e6);
397  default:
398  endrun(3, "Recombination rate not supported\n");
399  }
400 }
401 
402 /* Recombination rate for dielectronic recombination, in cm^3/s. Temp in K.
403  * The HeII->HeI dielectronic recombination rate is the cooling rate divided by 3Ry (for the HeII LyA transition).
404  */
405 static double
406 recomb_alphad(double temp)
407 {
408  switch(CoolingParams.recomb)
409  {
410  case Cen92:
411  return 1.9e-3 / pow(temp,1.5) * exp(-4.7e5/temp)*(1+0.3*exp(-9.4e4/temp));
412  case Verner96:
413  case Badnell06:
414  /* Private communication from Avery Meiksin:
415  The rate in Black (1981) is wrong by a factor of 0.65. He took the rate fit from Aldrovandi & Pequignot (1973),
416  who based it on Burgess (1965), but was unaware of the correction factor in Burgess & Tworkowski (1976, ApJ 205:L105-L107, fig 1).
417  The correct dielectronic cooling rate should have the coefficient 0.813e-13 instead of 1.24e-13 as in Black's table 3.
418  */
419  return 1.23e-3 / pow(temp,1.5) * exp(-4.72e5/temp)*(1+0.3*exp(-9.4e4/temp));
420  default:
421  endrun(3, "Recombination rate not supported\n");
422  }
423 }
424 
425 /*Convenience function for the total helium recombination cooling rate*/
426 static double
427 recomb_alphaHepd(double temp)
428 {
429  return recomb_alphad(temp) + recomb_alphaHep(temp);
430 }
431 
432 /* Recombination rate for doubly ionized recombination, in cm^3/s. Temp in K.*/
433 static double
434 recomb_alphaHepp(double temp)
435 {
436  switch(CoolingParams.recomb)
437  {
438  case Cen92:
439  return 4 * recomb_alphaHp(temp);
440  case Verner96:
441  return _Verner96Fit(temp, 1.891e-10, 0.7524, 9.370, 2.774e6);
442  case Badnell06:
443  return _Verner96Fit(temp, 5.235E-11, 0.6988 + 0.0829*exp(-1.682e5/temp), 7.301, 4.475e6);
444  default:
445  endrun(3, "Recombination rate not supported\n");
446  }
447 }
448 
449 /*Fitting function for collisional rates. Eq. 1 of Voronov 1997. Accurate to 10%, but data is only accurate to 50%.*/
450 static double
451 _Voronov96Fit(double temp, double dE, double PP, double AA, double XX, double KK)
452 {
453  double UU = dE / (BOLEVK * temp);
454  return AA * (1 + PP * sqrt(UU))/(XX+UU) * pow(UU, KK) * exp(-UU);
455 }
456 
457 /* Collisional ionization rate for H0 in cm^3/s. Temp in K.*/
458 static double
459 recomb_GammaeH0(double temp)
460 {
461  switch(CoolingParams.recomb)
462  {
463  case Cen92:
464  return 5.85e-11 * sqrt(temp) * exp(-157809.1/temp) / (1 + sqrt(temp/1e5));
465  case Verner96:
466  case Badnell06:
467  //Voronov 97 Table 1
468  return _Voronov96Fit(temp, 13.6, 0, 0.291e-07, 0.232, 0.39);
469  default:
470  endrun(3, "Collisional rate not supported\n");
471  }
472 }
473 
474 /* Collisional ionization rate for He0 in cm^3/s. Temp in K.*/
475 static double
476 recomb_GammaeHe0(double temp)
477 {
478  switch(CoolingParams.recomb)
479  {
480  case Cen92:
481  return 2.38e-11 * sqrt(temp) * exp(-285335.4/temp) / (1+ sqrt(temp/1e5));
482  case Verner96:
483  case Badnell06:
484  //Voronov 97 Table 1
485  return _Voronov96Fit(temp, 24.6, 0, 0.175e-07, 0.180, 0.35);
486  default:
487  endrun(3, "Collisional rate not supported\n");
488  }
489 }
490 
491 /* Collisional ionization rate for He+ in cm^3/s. Temp in K.*/
492 static double
493 recomb_GammaeHep(double temp)
494 {
495  switch(CoolingParams.recomb)
496  {
497  case Cen92:
498  return 5.68e-12 * sqrt(temp) * exp(-631515.0/temp) / (1+ sqrt(temp/1e5));
499  case Verner96:
500  case Badnell06:
501  //Voronov 97 Table 1
502  return _Voronov96Fit(temp, 54.4, 1, 0.205e-08, 0.265, 0.25);
503  default:
504  endrun(3, "Collisional rate not supported\n");
505  }
506 }
507 
508 /*Get interpolated value for one of the recombination interpolators. Takes natural log of temperature.*/
509 static double
510 get_interpolated_recomb(double logt, double * rec_tab, double rec_func(double))
511 {
512  /*Find the index to use in our temperature table.*/
513  double dind = (logt - RECOMBTMIN) / (RECOMBTMAX - RECOMBTMIN) * NRECOMBTAB;
514  int index = (int) dind;
515  /*Just call the function directly if we are out of interpolation range*/
516  if(index < 0 || index >= NRECOMBTAB-1)
517  return rec_func(exp(logt));
518  //if (temp_tab[index] > logt || temp_tab[index+1] < logt || index < 0 || index >= NRECOMBTAB)
519  // endrun(2, "Incorrect indexing of recombination array\n");
520  return rec_tab[index + 1] * (dind - index) + rec_tab[index] * (1 - (dind - index));
521 }
522 
523 /*The neutral hydrogen number density, divided by the hydrogen number density.
524  * Eq. 33 of KWH. Photofac is the self-shielding correction.*/
525 static double
526 nH0_internal(double logt, double ne, const struct UVBG * uvbg, double photofac)
527 {
528  double alphaHp = get_interpolated_recomb(logt, rec_alphaHp, &recomb_alphaHp);
529  double GammaeH0 = get_interpolated_recomb(logt, rec_GammaH0, &recomb_GammaeH0);
530  /*Be careful when there is no ionization.*/
531  double photorate = 0;
532  if(uvbg->gJH0 > 0. && ne > 1e-50)
533  photorate = uvbg->gJH0/ne * photofac;
534  return alphaHp/ (alphaHp + GammaeH0 + photorate);
535 }
536 
537 /*The ionised hydrogen number density, divided by the hydrogen number density. Eq. 34 of KWH.*/
538 static double
539 nHp_internal(double nH0)
540 {
541  double nHp = 1. - nH0;
542  if (nHp < 0)
543  return 0;
544  return nHp;
545 }
546 
547 struct he_ions
548 {
549  double nHe0;
550  double nHep;
551  double nHepp;
552 };
553 
554 /*The helium ionic number densities, divided by the helium number fraction. Eq. 35, 36 and 37 of KWH. */
555 static struct he_ions
556 nHe_internal(double nh, double logt, double ne, const struct UVBG * uvbg, double photofac)
557 {
558  double alphaHep = get_interpolated_recomb(logt, rec_alphaHep, &recomb_alphaHepd);
559  double alphaHepp = get_interpolated_recomb(logt, rec_alphaHepp, &recomb_alphaHepp);
560  double GammaHe0 = get_interpolated_recomb(logt, rec_GammaHe0, &recomb_GammaeHe0);
561  double GammaHep = get_interpolated_recomb(logt, rec_GammaHep, &recomb_GammaeHep);
562  struct he_ions He;
563  /*Be careful when there is no ionization.*/
564  if(uvbg->gJHe0 > 0. && ne > 1e-50) {
565  GammaHe0 += uvbg->gJHe0/ne * photofac;
566  GammaHep += uvbg->gJHep/ne * photofac;
567  }
568  /*Deal with the case where there is no ionization separately to avoid NaN.*/
569  if(GammaHe0 > 1e-50) {
570  He.nHep = nh / (1 + alphaHep / GammaHe0 + GammaHep/alphaHepp);
571  He.nHe0 = He.nHep * alphaHep / GammaHe0;
572  He.nHepp = He.nHep * GammaHep / alphaHepp;
573  }
574  else {
575  He.nHep = 0;
576  He.nHe0 = 1;
577  He.nHepp = 0;
578  }
579  return He;
580 }
581 
582 /*Compute temperature (in K) from internal energy and electron density.
583  Uses: internal energy
584  electron abundance per H atom (ne/nH)
585  hydrogen mass fraction (0.76)
586  Internal energy is in erg/g.
587  Factor to convert U (erg/g) to T (K) : U = N k T / (γ - 1)
588  T = U (γ-1) μ m_P / k_B
589  where k_B is the Boltzmann constant
590  γ is 5/3, the perfect gas constant
591  m_P is the proton mass
592 
593  μ = 1 / (mean no. molecules per unit atomic weight)
594  = 1 / (X + Y /4 + E)
595  where E = Ne * X, and Y = (1-X).
596  Can neglect metals as they are heavy.
597  Leading contribution is from electrons, which is already included
598  [+ Z / (12->16)] from metal species
599  [+ Z/16*4 ] for OIV from electrons.*/
600 static double
601 get_temp_internal(double nebynh, double ienergy, double helium)
602 {
603  /*convert U (erg/g) to T (K) : U = N k T / (γ - 1)
604  T = U (γ-1) μ m_P / k_B
605  where k_B is the Boltzmann constant
606  γ is 5/3, the perfect gas constant
607  m_P is the proton mass
608  μ is 1 / (mean no. molecules per unit atomic weight) calculated in loop.
609  Internal energy units are 10^-10 erg/g*/
610  double hy_mass = 1 - helium;
611  double muienergy = 4 / (hy_mass * (3 + 4*nebynh) + 1)*ienergy;
612  /*So for T in K, Boltzmann in erg/K, internal energy has units of erg/g*/
613  double temp = GAMMA_MINUS1 * PROTONMASS / BOLTZMANN * muienergy;
614  if(temp < CoolingParams.MinGasTemp)
615  return CoolingParams.MinGasTemp;
616  return temp;
617 }
618 
619 /*The electron number density. Eq. 38 of KWH.*/
620 static double
621 ne_internal(double nh, double ienergy, double ne, double helium, double * logt, const struct UVBG * uvbg)
622 {
623  double yy = helium / 4 / (1 - helium);
624  *logt = log(get_temp_internal(ne/nh, ienergy, helium));
625  double photofac = self_shield_corr(nh, *logt, uvbg->self_shield_dens);
626  double nH0 = nH0_internal(*logt, ne, uvbg, photofac);
627  double nHp = nHp_internal(nH0);
628  struct he_ions He = nHe_internal(nh, *logt, ne, uvbg, photofac);
629  return nh * nHp + yy * He.nHep + 2 * yy * He.nHepp;
630 }
631 
632 /*Maximum number of iterations to perform*/
633 #define MAXITER 1000
634 /* Absolute tolerance to converge the rate network. Absolute is ok because we are converging electon abundance, ne/nh,
635  * which ~ 1 in all cases where it matters.*/
636 #define ITERCONV 1e-6
637 
638 /* This finds a fixed point of the function where ``func(x0) == x0``.
639  Uses Steffensen's Method with Aitken's ``Del^2`` convergence
640  acceleration (Burden, Faires, "Numerical Analysis", 5th edition, pg. 80).
641  This routine is ported from scipy.optimize.fixed_point.
642  Notice that ne_init is the electron abundance in units of nh, not the cgs electron abundance as returned by ne_internal.
643 */
644 static double
645 scipy_optimize_fixed_point(double ne_init, double nh, double ienergy, double helium, double *logt, const struct UVBG * uvbg)
646 {
647  int i;
648  double ne0 = ne_init;
649  for(i = 0; i < MAXITER; i++)
650  {
651  double logt1;
652  double ne1 = ne_internal(nh, ienergy, ne0*nh, helium, &logt1, uvbg) / nh;
653 
654  if(fabs(ne1 - ne0) < ITERCONV) {
655  *logt = logt1;
656  ne0 = ne1;
657  break;
658  }
659 
660  double ne2 = ne_internal(nh, ienergy, ne1*nh, helium, &logt1, uvbg) / nh;
661  double d = ne0 + ne2 - 2.0 * ne1;
662  double pp = ne2;
663  /*This is del^2*/
664  if (d > 1e-15 || d < -1e-15)
665  pp = ne0 - (ne1 - ne0)*(ne1 - ne0) / d;
666  ne0 = pp;
667  /*Enforce positivity*/
668  if(ne0 < 0)
669  ne0 = 0;
670  }
671  if (!isfinite(ne0) || i == MAXITER)
672  endrun(1, "Ionization rate network failed to converge for nh = %g temp = %g helium=%g ienergy=%g: last ne = %g (init=%g)\n", nh, get_temp_internal(ne0, ienergy, helium), helium, ienergy, ne0, ne_init);
673  return ne0 * nh;
674 }
675 
676 /*Solve the system of equations for photo-ionization equilibrium,
677  starting with ne = nH and continuing until convergence.
678  density is gas density in protons/cm^3
679  Internal energy is in ergs/g.
680  helium is a mass fraction.
681 */
682 double
683 get_equilib_ne(double density, double ienergy, double helium, double * logt, const struct UVBG * uvbg, double ne_init)
684 {
685  /*Get hydrogen number density*/
686  double nh = density * (1-helium);
687  /* Avoid getting stuck in an alternate solution
688  * where there is never any heating in the presence of roundoff.*/
689  if(ne_init <= 0)
690  ne_init = 1.0;
691  return scipy_optimize_fixed_point(ne_init, nh, ienergy, helium, logt, uvbg);
692 }
693 
694 /*Same as above, but get electrons per proton.*/
695 double
696 get_ne_by_nh(double density, double ienergy, double helium, const struct UVBG * uvbg, double ne_init)
697 {
698  double logt;
699  return get_equilib_ne(density, ienergy, helium, &logt, uvbg, ne_init)/(density*(1-helium));
700 }
701 
702 /*Here come the cooling rates. These are in erg s^-1 cm^-3 (cgs).
703 All rates are divided by the abundance of the ions involved in the interaction.
704 So we are computing the cooling rate divided by n_e n_X. Temperatures in K.
705 
706 Cen92 rates:
707 None of these rates are original to KWH92, but are taken from Cen 1992,
708 and originally from older references. The hydrogen rates in particular are probably inaccurate.
709 Cen 1992 modified (arbitrarily) the excitation and ionisation rates for high temperatures.
710 There is no collisional excitation rate for He0 - not sure why.
711 References:
712  Black 1981, from Lotz 1967, Seaton 1959, Burgess & Seaton 1960.
713  Recombination rates are from Spitzer 1978.
714  Free-free: Spitzer 1978.
715 Collisional excitation and ionisation cooling rates are merged.
716 
717 These rates are used in the Sherwood simulation, Bolton et al 2016 1605.03462, but with updated collision and recombination rates,
718 and an improvement to the Cen 92 large temperature correction factor.
719 
720 Nyx/enzo2 rates:
721 The cooling rates used in the Nyx paper Lukic 2014, 1406.6361, in erg s^-1 cm^-3 (cgs)
722 and Enzo v.2. Originally these come from Avery Meiksin, and questions should be directed to him.
723 
724 Major differences from KWH are the use of the Scholz & Walter 1991
725 hydrogen collisional cooling rates, a less aggressive high temperature correction for helium, and
726 Shapiro & Kang 1987 for free free.
727 
728 References:
729  Scholz & Walters 1991 (0.45% accuracy)
730  Black 1981 (recombination and helium)
731  Shapiro & Kang 1987
732 
733 They use the recombination rates from Verner & Ferland 96, but do not change the cooling rates to match.
734 This is because at the temperatures where this matters, T > 10^5, all the H should be ionized anyway.
735 Ditto the ionization rates from Voronov 1997: they should also use these rates for collisional ionisation,
736 although this is hard to do in practice because Scholz & Walters don't break their rates into ionization and excitation.
737 
738 Sherwood rates:
739 Rates
740 Small numerical differences from Nyx, but they do not used Scholz & Walter, instead Cen 92 for collisional excitation,
741 and Verner & Ferland 96 for recombination.
742 */
743 
744 /*Commonly used Cen 1992 correction factor for large temperatures.*/
745 static double
746 _t5(double temp)
747 {
748  double t0;
750  t0 = 1e5;
751  /* The original Cen 92 correction is implemented in order to achieve the
752  * correct asymptotic behaviour at high temperatures. However, the rates he is correcting
753  * should be good up to 10^7 K, so he is being too aggressive.*/
754  else
755  t0 = 5e7;
756  return 1+sqrt(temp/t0);
757 }
758 
759 /*Collisional excitation cooling rate for n_H0 and n_e. Gadget-3 calls this BetaH0.*/
760 static double
762 {
763  return 7.5e-19 * exp(-118348.0/temp) / _t5(temp);
764 }
765 
766 /*Collisional excitation cooling rate for n_He+ and n_e. Gadget-3 calls this BetaHep.*/
767 static double
769 {
770  return 5.54e-17 * pow(temp, -0.397)*exp(-473638./temp)/_t5(temp);
771 }
772 
773 /*This is listed in Cen 92 but neglected in KWH 97, presumably because it is very small.*/
774 static double
776 {
777  return 9.1e-27 * pow(temp, -0.1687) * exp(-473638/temp) / _t5(temp);
778 }
779 
780 /* Collisional ionisation cooling rate for n_H0 and n_e. Gadget calls this GammaeH0.*/
781 static double
783 {
784  /*H ionization potential*/
785  return 13.5984 * eVinergs * recomb_GammaeH0(temp);
786 }
787 
788 /*Collisional ionisation cooling rate for n_He0 and n_e. Gadget calls this GammaeHe0.*/
789 static double
791 {
792  return 24.5874 * eVinergs * recomb_GammaeHe0(temp);
793 }
794 
795 /*Collisional ionisation cooling rate for n_He+ and n_e. Gadget calls this GammaeHep.*/
796 static double
798 {
799  return 54.417760 * eVinergs * recomb_GammaeHep(temp);
800 }
801 
802 /*Total collisional cooling for H0*/
803 static double
804 cool_CollisionalH0(double temp)
805 {
807  /*Formula from Eq. 23, Table 4 of Scholz & Walters, claimed good to 0.45 %.
808  Note though that they have two datasets which differ by a factor of two.
809  Differs from Cen 92 by a factor of two.*/
810  //Technically only good for T > 2000.
811  double y = log(temp);
812  //Constant is 0.75/k_B in Rydberg
813  double Ryd = 2.1798741e-11;
814  double tot = -0.75/BOLTZMANN *Ryd/temp;
815  double coeffslowT[6] = {213.7913, 113.9492, 25.06062, 2.762755, 0.1515352, 3.290382e-3};
816  double coeffshighT[6] = {271.25446, 98.019455, 14.00728, 0.9780842, 3.356289e-2, 4.553323e-4};
817  int j;
818  for(j=0; j < 6; j++)
819  tot += ((temp < 1e5)*coeffslowT[j]+(temp >=1e5)*coeffshighT[j])*pow(-y, j);
820  return 1e-20 * exp(tot);
821  }
822  else /*Everyone else splits collisional from excitation, because collisional is mostly exact*/
824 }
825 
826 /*Total collisional cooling for He0*/
827 static double
829 {
831 }
832 
833 /*Total collisional cooling for HeP*/
834 static double
836 {
838 }
839 
840 /*Recombination cooling rate for H+ and e. Gadget calls this AlphaHp.*/
841 static double
842 cool_RecombHp(double temp)
843 {
845  /*Recombination cooling rate from Black 1981: these increase rapidly for T > 5e5K. */
846  return 2.851e-27 * sqrt(temp) * (5.914 - 0.5 * log(temp) + 0.01184 * pow(temp, 1./3));
847  }
848  return 0.75 * BOLTZMANN * temp * recomb_alphaHp(temp);
849 }
850 
851 /*Recombination cooling rate for He+ and e. Gadget calls this Alphad */
852 static double
853 cool_RecombDielect(double temp)
854 {
855  /*What is this magic number?*/
856  return 6.526e-11* recomb_alphad(temp);
857 }
858 
859 /*Recombination cooling rate for He+ and e. Gadget calls this AlphaHep.*/
860 static double
861 cool_RecombHeP(double temp)
862 {
863  return 0.75 * BOLTZMANN * temp * recomb_alphaHep(temp) + cool_RecombDielect(temp);
864 }
865 
866 /*Recombination cooling rate for He+ and e. Gadget calls this AlphaHepp.*/
867 static double
868 cool_RecombHePP(double temp)
869 {
871  /*Recombination cooling rate from Black 1981: these increase rapidly for T > 5e5K. */
872  return 1.140e-26 * sqrt(temp) * (6.607 - 0.5 * log(temp) + 7.459e-3 * pow(temp, 1./3));
873  }
874  return 0.75 * BOLTZMANN * temp * recomb_alphaHepp(temp);
875 }
876 
877 /*Free-free cooling rate for electrons scattering on ions without being captured.
878 Factors here are n_e and total ionized species:
879  (FreeFree(zz=1)*(n_H+ + n_He+) + FreeFree(zz=2)*n_He++)*/
880 static double
881 cool_FreeFree(double temp, int zz)
882 {
883  double gff;
884  /*Formula for the Gaunt factor from Shapiro & Kang 1987. ZZ is 1 for H+ and He+ and 2 for He++.
885  This is almost identical to the KWH rate but not continuous.*/
887  double lt = 2 * log10(temp/zz);
888  if(lt <= log10(3.2e5))
889  gff = (0.79464 + 0.1243*lt);
890  else
891  gff = (2.13164 - 0.1240*lt);
892  }
893  else {
894  /*Formula for the Gaunt factor. KWH takes this from Spitzer 1978.*/
895  gff = 1.1+0.34*exp(-pow(5.5 - log10(temp),2) /3.);
896  }
897  return 1.426e-27*sqrt(temp)* pow(zz,2) * gff;
898 }
899 
900 static double
901 cool_FreeFree1(double temp)
902 {
903  return cool_FreeFree(temp, 1);
904 }
905 
906 /* Cooling rate for inverse Compton from the microwave background.
907  * Multiply this only by n_e. Note the CMB temperature is hardcoded in KWH92 to 2.7.
908  * Units are erg s^-1*/
909 static double
910 cool_InverseCompton(double temp, double redshift)
911 {
912  double tcmb_red = CoolingParams.CMBTemperature * (1+redshift);
913  return 4 * THOMPSON * RAD_CONST / (ELECTRONMASS * LIGHTCGS ) * pow(tcmb_red, 4) * BOLTZMANN * (temp - tcmb_red);
914 }
915 
916 /* This function modifies the photoheating rates by
917  * a density dependent factor.
918  * This is a hack to attempt to account for helium reionisation,
919  * especially for the Lyman alpha forest.
920  * It is not a good model for helium reionisation, and needs to be replaced!
921  * Takes hydrogen number density in cgs units.
922  */
923 static double
924 cool_he_reion_factor(double nHcgs, double helium, double redshift)
925 {
927  return 1.;
928  const double rho = PROTONMASS * nHcgs / (1 - helium);
929  double overden = rho/(CoolingParams.rho_crit_baryon * pow(1+redshift,3.0));
930  if (overden >= CoolingParams.HeliumHeatThresh)
933 }
934 
935 /*This is a helper for the tests*/
937 {
938  CoolingParams = cp;
939 }
940 
941 void
943 {
944  int ThisTask;
945  MPI_Comm_rank(MPI_COMM_WORLD, &ThisTask);
946  if(ThisTask == 0) {
947  /*Cooling rate network parameters*/
948  CoolingParams.CMBTemperature = param_get_double(ps, "CMBTemperature");
949  CoolingParams.cooling = (enum CoolingType) param_get_enum(ps, "CoolingRates"); // Sherwood;
950  CoolingParams.recomb = (enum RecombType) param_get_enum(ps, "RecombRates"); // Verner96;
951  CoolingParams.SelfShieldingOn = param_get_int(ps, "SelfShieldingOn");
952  CoolingParams.PhotoIonizeFactor = param_get_double(ps, "PhotoIonizeFactor");
953  CoolingParams.PhotoIonizationOn = param_get_int(ps, "PhotoIonizationOn");
954  CoolingParams.MinGasTemp = param_get_double(ps, "MinGasTemp");
955  CoolingParams.UVRedshiftThreshold = param_get_double(ps, "UVRedshiftThreshold");
956  CoolingParams.HydrogenHeatAmp = log10(param_get_double(ps, "HydrogenHeatAmp"));
957 
958  /*Helium model parameters*/
959  CoolingParams.HeliumHeatOn = param_get_int(ps, "HeliumHeatOn");
960  CoolingParams.HeliumHeatThresh = param_get_double(ps, "HeliumHeatThresh");
961  CoolingParams.HeliumHeatAmp = param_get_double(ps, "HeliumHeatAmp");
962  CoolingParams.HeliumHeatExp = param_get_double(ps, "HeliumHeatExp");
963  }
964  MPI_Bcast(&CoolingParams, sizeof(struct cooling_params), MPI_BYTE, 0, MPI_COMM_WORLD);
965 }
966 
967 /*Initialize the cooling rate module. This builds a lot of interpolation tables.
968  * Defaults: TCMB 2.7255, recomb = Verner96, cooling = Sherwood.*/
969 void
970 init_cooling_rates(const char * TreeCoolFile, const char * MetalCoolFile, Cosmology * CP)
971 {
973  CoolingParams.rho_crit_baryon = CP->OmegaBaryon * 3.0 * pow(CP->HubbleParam*HUBBLE,2.0) /(8.0*M_PI*GRAVITY);
974 
975  /* Initialize the interpolation for the self-shielding module as a function of redshift.
976  * A crash has been observed in GSL with a cspline interpolator. */
977  GrayOpac = gsl_interp_alloc(gsl_interp_linear,NGRAY);
978  gsl_interp_init(GrayOpac,GrayOpac_zz,GrayOpac_ydata, NGRAY);
979 
980  if(!TreeCoolFile || strnlen(TreeCoolFile,100) == 0) {
982  message(0, "No TreeCool file is provided. Cooling is broken. OK for DM only runs. \n");
983  }
984  else {
985  message(0, "Using uniform UVB from file %s\n", TreeCoolFile);
986  /* Load the TREECOOL into Gamma_HI->ydata, and initialise the interpolators*/
987  load_treecool(TreeCoolFile);
988  }
989 
990  /*Initialize the recombination tables*/
991  temp_tab = (double *) mymalloc("Recombination_tables", NRECOMBTAB * sizeof(double) * 14);
992 
1006 
1007  int i;
1008  for(i = 0 ; i < NRECOMBTAB; i++)
1009  {
1011  double tt = exp(temp_tab[i]);
1016  /* Note this includes dielectronic recombination*/
1025  cool_freefree1[i] = cool_FreeFree(tt, 1);
1026  }
1027 
1028  /*Initialize the metal cooling table*/
1029  InitMetalCooling(MetalCoolFile);
1030 }
1031 
1032 /* Split out the Compton cooling*/
1033 double
1034 get_compton_cooling(double density, double ienergy, double helium, double redshift, double nebynh)
1035 {
1036  double nh = density * (1 - helium);
1037  double temp = get_temp_internal(nebynh, ienergy, helium);
1038  /*Compton cooling in erg/s cm^3*/
1039  double LambdaCmptn = -1*nebynh * cool_InverseCompton(temp, redshift) / nh;
1040  return LambdaCmptn * pow(1 - helium, 2) * density / PROTONMASS;
1041 }
1042 
1043 /* Get an individual cooling or heating process. For tests.
1044  */
1045 double
1046 get_individual_cooling(enum CoolProcess process, double density, double ienergy, double helium, const struct UVBG * uvbg, double *ne_equilib)
1047 {
1048  double logt;
1049  double ne = get_equilib_ne(density, ienergy, helium, &logt, uvbg, *ne_equilib);
1050  double nh = density * (1 - helium);
1051  double nebynh = ne/nh;
1052  /*Faster than running the exp.*/
1053  double temp = get_temp_internal(nebynh, ienergy, helium);
1054  double photofac = self_shield_corr(nh, logt, uvbg->self_shield_dens);
1055  double nH0 = nH0_internal(logt, ne, uvbg, photofac);
1056  double nHp = nHp_internal(nH0);
1057  /*The helium number fraction*/
1058  double yy = helium / 4 / (1 - helium);
1059  struct he_ions He = nHe_internal(nh, logt, ne, uvbg, photofac);
1060  /*Put the abundances in units of nH to avoid underflows*/
1061  He.nHep*= yy/nh;
1062  He.nHe0*= yy/nh;
1063  He.nHepp*= yy/nh;
1064 
1065  /*Compton cooling in erg/s cm^3*/
1066  double Lambda = 0;
1067 
1068  if(process == FREEFREE) {
1070  if(CoolingParams.cooling == Enzo2Nyx) {
1071  Lambda = -1*nebynh * (cff * (nHp + He.nHep) + cool_FreeFree(temp, 2) * He.nHepp);
1072  } else {
1073  /*The factor of (zz=2)^2 has been pulled out, so if we use the Spitzer gaunt factor we don't need
1074  * to call the FreeFree function again.*/
1075  Lambda = -1*nebynh * (cff * (nHp + He.nHep) + 4 * cff * He.nHepp);
1076  }
1077  } else if(process == HEAT) {
1078  /*Total heating rate per proton in erg/s cm^3*/
1079  Lambda = (nH0 * uvbg->epsH0 + He.nHe0 * uvbg->epsHe0 + He.nHep * uvbg->epsHep)/nh;
1080  }
1081  else if(process == RECOMB) {
1082  Lambda = -1*nebynh * (get_interpolated_recomb(logt, cool_recombHp, cool_RecombHp) * nHp +
1085  }
1086  else if(process == COLLIS) {
1087  Lambda = -1*nebynh * (get_interpolated_recomb(logt, cool_collisH0, cool_CollisionalH0) * nH0 +
1090  }
1091 
1092  return Lambda * pow(1 - helium, 2) * density / PROTONMASS;
1093 }
1094 
1095 /*Get the total change in internal energy per unit time in erg/s/g for a given temperature (internal energy) and density.
1096  density is total gas density in protons/cm^3
1097  Internal energy is in ergs/g.
1098  helium is a mass fraction, 1 - HYDROGEN_MASSFRAC = 0.24 for primordial gas.
1099  Returns (heating - cooling) / nh^2.
1100  ne_equilib is the equilibrium electron abundance in units of the hydrogen number density.
1101  Note this is *not* the electron density in cgs units, as used internally.
1102  */
1103 double
1104 get_heatingcooling_rate(double density, double ienergy, double helium, double redshift, double metallicity, const struct UVBG * uvbg, double *ne_equilib)
1105 {
1106  double logt;
1107  double ne = get_equilib_ne(density, ienergy, helium, &logt, uvbg, *ne_equilib);
1108  double nh = density * (1 - helium);
1109  double nebynh = ne/nh;
1110  /*Faster than running the exp.*/
1111  double temp = get_temp_internal(nebynh, ienergy, helium);
1112  double photofac = self_shield_corr(nh, logt, uvbg->self_shield_dens);
1113 
1114  /*The helium number fraction*/
1115  double yy = helium / 4 / (1 - helium);
1116 
1117  double nH0 = nH0_internal(logt, ne, uvbg, photofac);
1118  double nHp = nHp_internal(nH0);
1119  struct he_ions He = nHe_internal(nh, logt, ne, uvbg, photofac);
1120  /*Put the abundances in units of nH to avoid underflows*/
1121  He.nHep*= yy/nh;
1122  He.nHe0*= yy/nh;
1123  He.nHepp*= yy/nh;
1124  /*Collisional ionization and excitation rate*/
1125  double LambdaCollis = nebynh * (get_interpolated_recomb(logt, cool_collisH0, cool_CollisionalH0) * nH0 +
1128  double LambdaRecomb = nebynh * (get_interpolated_recomb(logt, cool_recombHp, cool_RecombHp) * nHp +
1131  /*Free-free cooling rate*/
1132  double LambdaFF = 0;
1133 
1135 
1136  if(CoolingParams.cooling == Enzo2Nyx) {
1137  LambdaFF = nebynh * (cff * (nHp + He.nHep) + cool_FreeFree(temp, 2) * He.nHepp);
1138  } else {
1139  /*The factor of (zz=2)^2 has been pulled out, so if we use the Spitzer gaunt factor we don't need
1140  * to call the FreeFree function again.*/
1141  LambdaFF = nebynh * (cff * (nHp + He.nHep) + 4 * cff * He.nHepp);
1142  }
1143  /*Compton cooling in erg/s cm^3*/
1144  double LambdaCmptn = nebynh * cool_InverseCompton(temp, redshift) / nh;
1145  /*Total cooling rate per proton in erg/s cm^3*/
1146  double Lambda = LambdaCollis + LambdaRecomb + LambdaFF + LambdaCmptn;
1147 
1148  /*Total heating rate per proton in erg/s cm^3*/
1149  double Heat = (nH0 * uvbg->epsH0 + He.nHe0 * uvbg->epsHe0 + He.nHep * uvbg->epsHep)/nh;
1150 
1151  Heat *= cool_he_reion_factor(density, helium, redshift);
1152  /*Set external equilibrium electron density*/
1153  *ne_equilib = nebynh;
1154 
1155  /*Apply metal cooling. Does nothing if metal cooling is disabled*/
1156  double MetalCooling = metallicity * TableMetalCoolingRate(redshift, temp, nh);
1157 
1158  double LambdaNet = Heat - Lambda - MetalCooling;
1159 
1160  //message(1, "Heat = %g Lambda = %g MetalCool = %g LC = %g LR = %g LFF = %g LCmptn = %g, ne = %g, nH0 = %g, nHp = %g, nHe0 = %g, nHep = %g, nHepp = %g, nh=%g, temp=%g, ienergy=%g\n", Heat, Lambda, MetalCooling, LambdaCollis, LambdaRecomb, LambdaFF, LambdaCmptn, nebynh, nH0, nHp, nHe0, nHep, nHepp, nh, temp, ienergy);
1161 
1162  /* LambdaNet in erg cm^3 /s, Density in protons/cm^3, PROTONMASS in protons/g.
1163  * Convert to erg/s/g*/
1164  return LambdaNet * pow(1 - helium, 2) * density / PROTONMASS;
1165 }
1166 
1167 /*Get the equilibrium temperature at given internal energy.
1168  density is total gas density in protons/cm^3
1169  Internal energy is in ergs/g.
1170  helium is a mass fraction*/
1171 double
1172 get_temp(double density, double ienergy, double helium, const struct UVBG * uvbg, double * ne_init)
1173 {
1174  double logt;
1175  double ne = get_equilib_ne(density, ienergy, helium, &logt, uvbg, *ne_init);
1176  double nh = density * (1 - helium);
1177  *ne_init = ne/nh;
1178  return get_temp_internal(ne/nh, ienergy, helium);
1179 }
1180 
1181 /* Get the neutral hydrogen fraction at a given temperature and density.
1182 density is gas density in protons/cm^3
1183 Internal energy is in ergs/g.
1184 helium is a mass fraction.*/
1185 double
1186 get_neutral_fraction_phys_cgs(double density, double ienergy, double helium, const struct UVBG * uvbg, double * ne_init)
1187 {
1188  double logt;
1189  double ne = get_equilib_ne(density, ienergy, helium, &logt, uvbg, *ne_init);
1190  double nh = density * (1-helium);
1191  double photofac = self_shield_corr(nh, logt, uvbg->self_shield_dens);
1192  *ne_init = ne/nh;
1193  return nH0_internal(logt, ne, uvbg, photofac);
1194 }
1195 
1196 /* Get the helium ionization fractions at a given temperature and density.
1197  * ion is 0, 1, 2 for He, He+ and He++
1198 density is gas density in protons/cm^3
1199 Internal energy is in ergs/g.
1200 helium is a mass fraction.*/
1201 double
1202 get_helium_ion_phys_cgs(int ion, double density, double ienergy, double helium, const struct UVBG * uvbg, double ne_init)
1203 {
1204  double logt;
1205  double ne = get_equilib_ne(density, ienergy, helium, &logt, uvbg, ne_init);
1206  double yy = helium / 4 / (1 - helium);
1207  double nh = density * (1-helium);
1208  double photofac = self_shield_corr(nh, logt, uvbg->self_shield_dens);
1209  struct he_ions He = nHe_internal(nh, logt, ne, uvbg, photofac);
1210  if(ion == 0)
1211  return yy * He.nHe0 / nh;
1212  else if (ion == 1)
1213  return yy * He.nHep / nh;
1214  else
1215  return yy * He.nHepp / nh;
1216 }
int during_helium_reionization(double redshift)
static double scipy_optimize_fixed_point(double ne_init, double nh, double ienergy, double helium, double *logt, const struct UVBG *uvbg)
double recomb_alphaHp(double temp)
#define NRECOMBTAB
Definition: cooling_rates.c:95
static double cool_FreeFree(double temp, int zz)
static struct he_ions nHe_internal(double nh, double logt, double ne, const struct UVBG *uvbg, double photofac)
static double recomb_GammaeH0(double temp)
static void load_treecool(const char *TreeCoolFile)
static double * rec_GammaHe0
static double self_shield_dens(double redshift, const struct UVBG *uvbg)
static double nHp_internal(double nH0)
static double * Gamma_log1z
Definition: cooling_rates.c:88
static struct itp_type Eps_HI Eps_HeI Eps_HeII
Definition: cooling_rates.c:92
static gsl_interp * GrayOpac
Definition: cooling_rates.c:70
#define RECOMBTMIN
Definition: cooling_rates.c:97
static double * cool_recombHePP
static double * cool_freefree1
static double * cool_collisHe0
static double * cool_recombHp
static double recomb_GammaeHep(double temp)
static double cool_CollisionalExciteHe0(double temp)
double get_ne_by_nh(double density, double ienergy, double helium, const struct UVBG *uvbg, double ne_init)
double get_compton_cooling(double density, double ienergy, double helium, double redshift, double nebynh)
static double cool_InverseCompton(double temp, double redshift)
double get_equilib_ne(double density, double ienergy, double helium, double *logt, const struct UVBG *uvbg, double ne_init)
static double cool_CollisionalHe0(double temp)
static double nH0_internal(double logt, double ne, const struct UVBG *uvbg, double photofac)
static double cool_FreeFree1(double temp)
static double cool_CollisionalExciteH0(double temp)
static double get_temp_internal(double nebynh, double ienergy, double helium)
static double _Verner96Fit(double temp, double aa, double bb, double temp0, double temp1)
static double load_tree_value(char **saveptr)
static double cool_CollisionalExciteHeP(double temp)
static void init_itp_type(double *xarr, struct itp_type *Gamma, int Nelem)
static double cool_RecombHePP(double temp)
static double _Voronov96Fit(double temp, double dE, double PP, double AA, double XX, double KK)
double get_helium_ion_phys_cgs(int ion, double density, double ienergy, double helium, const struct UVBG *uvbg, double ne_init)
static const double GrayOpac_ydata[NGRAY]
Definition: cooling_rates.c:75
static double * cool_collisHeP
#define ITERCONV
static double * rec_GammaHep
static double recomb_alphad(double temp)
static double cool_CollisionalH0(double temp)
static double cool_RecombHeP(double temp)
double get_heatingcooling_rate(double density, double ienergy, double helium, double redshift, double metallicity, const struct UVBG *uvbg, double *ne_equilib)
static double * rec_alphaHp
Definition: cooling_rates.c:99
static double get_photo_rate(double redshift, struct itp_type *Gamma_tab)
double get_temp(double density, double ienergy, double helium, const struct UVBG *uvbg, double *ne_init)
#define MAXITER
static const double GrayOpac_zz[NGRAY]
Definition: cooling_rates.c:76
static double recomb_alphaHepd(double temp)
static double * cool_collisH0
static double self_shield_corr(double nh, double logt, double ssdens)
#define NGRAY
Definition: cooling_rates.c:73
static double cool_RecombDielect(double temp)
void set_cooling_params(ParameterSet *ps)
static double _t5(double temp)
static double * rec_alphaHep
Definition: cooling_rates.c:99
static double _Verner96alphaHep(double temp)
static double cool_he_reion_factor(double nHcgs, double helium, double redshift)
#define RECOMBTMAX
Definition: cooling_rates.c:96
static int NTreeCool
Definition: cooling_rates.c:86
static double cool_CollisionalHeP(double temp)
void set_coolpar(struct cooling_params cp)
static double cool_RecombHp(double temp)
double get_neutral_fraction_phys_cgs(double density, double ienergy, double helium, const struct UVBG *uvbg, double *ne_init)
static double ne_internal(double nh, double ienergy, double ne, double helium, double *logt, const struct UVBG *uvbg)
static double cool_CollisionalIonizeHeP(double temp)
struct UVBG get_global_UVBG(double redshift)
static struct itp_type Gamma_HI Gamma_HeI Gamma_HeII
Definition: cooling_rates.c:90
void init_cooling_rates(const char *TreeCoolFile, const char *MetalCoolFile, Cosmology *CP)
static double get_interpolated_recomb(double logt, double *rec_tab, double rec_func(double))
static double recomb_alphaHep(double temp)
static double recomb_alphaHepp(double temp)
static struct cooling_params CoolingParams
Definition: cooling_rates.c:68
static double * rec_GammaH0
static double cool_CollisionalIonizeH0(double temp)
static double * temp_tab
Definition: cooling_rates.c:98
static double * rec_alphaHepp
Definition: cooling_rates.c:99
static double * cool_recombHeP
static double cool_CollisionalIonizeHe0(double temp)
double get_individual_cooling(enum CoolProcess process, double density, double ienergy, double helium, const struct UVBG *uvbg, double *ne_equilib)
static double recomb_GammaeHe0(double temp)
CoolingType
Definition: cooling_rates.h:16
@ KWH92
Definition: cooling_rates.h:17
@ Enzo2Nyx
Definition: cooling_rates.h:18
CoolProcess
Definition: cooling_rates.h:94
@ RECOMB
Definition: cooling_rates.h:95
@ HEAT
Definition: cooling_rates.h:98
@ COLLIS
Definition: cooling_rates.h:96
@ FREEFREE
Definition: cooling_rates.h:97
void InitMetalCooling(const char *MetalCoolFile)
double TableMetalCoolingRate(double redshift, double temp, double nHcgs)
RecombType
Definition: cooling_rates.h:10
@ Cen92
Definition: cooling_rates.h:11
@ Verner96
Definition: cooling_rates.h:12
@ Badnell06
Definition: cooling_rates.h:13
void density(const ActiveParticles *act, int update_hsml, int DoEgyDensity, int BlackHoleOn, double MinEgySpec, const DriftKickTimes times, Cosmology *CP, struct sph_pred_data *SPH_predicted, MyFloat *GradRho, const ForceTree *const tree)
Definition: density.c:203
void message(int where, const char *fmt,...)
Definition: endrun.c:175
void endrun(int where, const char *fmt,...)
Definition: endrun.c:147
#define mymalloc(name, size)
Definition: mymalloc.h:15
double param_get_double(ParameterSet *ps, const char *name)
Definition: paramset.c:336
int param_get_int(ParameterSet *ps, const char *name)
Definition: paramset.c:368
int param_get_enum(ParameterSet *ps, const char *name)
Definition: paramset.c:378
#define RAD_CONST
Definition: physconst.h:9
#define ELECTRONMASS
Definition: physconst.h:22
#define eVinergs
Definition: physconst.h:15
#define HUBBLE
Definition: physconst.h:25
#define GRAVITY
Definition: physconst.h:5
#define GAMMA_MINUS1
Definition: physconst.h:35
#define PROTONMASS
Definition: physconst.h:21
#define THOMPSON
Definition: physconst.h:23
#define BOLEVK
Definition: physconst.h:13
#define BOLTZMANN
Definition: physconst.h:11
#define LIGHTCGS
Definition: physconst.h:18
static Cosmology * CP
Definition: power.c:27
double HubbleParam
Definition: cosmology.h:20
double OmegaCDM
Definition: cosmology.h:11
double OmegaBaryon
Definition: cosmology.h:19
Definition: cooling.h:8
double epsHep
Definition: cooling.h:13
double epsH0
Definition: cooling.h:12
double gJHep
Definition: cooling.h:10
double gJHe0
Definition: cooling.h:11
double gJH0
Definition: cooling.h:9
double self_shield_dens
Definition: cooling.h:15
double epsHe0
Definition: cooling.h:14
double zreion
Definition: cooling.h:16
double HeliumHeatThresh
Definition: cooling_rates.h:54
double HeliumHeatAmp
Definition: cooling_rates.h:55
enum RecombType recomb
Definition: cooling_rates.h:26
double UVRedshiftThreshold
Definition: cooling_rates.h:47
double rho_crit_baryon
Definition: cooling_rates.h:57
double CMBTemperature
Definition: cooling_rates.h:41
double HeliumHeatExp
Definition: cooling_rates.h:56
enum CoolingType cooling
Definition: cooling_rates.h:28
double PhotoIonizeFactor
Definition: cooling_rates.h:38
double HydrogenHeatAmp
Definition: cooling_rates.h:50
double nHe0
double nHep
double nHepp
gsl_interp * intp
Definition: cooling_rates.c:82
double * ydata
Definition: cooling_rates.c:81
static const double tt[NEXACT]
int ThisTask
Definition: test_exchange.c:23