1+ /* ! \file
2+ * \brief Declarations of cooling functions. */
3+
4+ #pragma once
5+
6+ #include < cmath>
7+
8+ #include " ../global/global.h"
9+
10+ /* ! @defgroup coolcomp Cooling Component Logic
11+ *
12+ * The goal here is to collect logic for modelling cooling. Cooling recipes are
13+ * subsequently constructed from the one or more components.
14+ */
15+ /* * @{ */
16+
17+ namespace cool_component
18+ {
19+
20+ /* ! Primordial hydrogen/helium cooling curve (derived according to Katz et al. 1996.) */
21+ inline __device__ Real primordial_cool (Real n, Real T)
22+ {
23+ Real n_h, Y, y, g_ff, cool;
24+ Real n_h0, n_hp, n_he0, n_hep, n_hepp, n_e, n_e_old;
25+ Real alpha_hp, alpha_hep, alpha_d, alpha_hepp, gamma_eh0, gamma_ehe0, gamma_ehep;
26+ Real le_h0, le_hep, li_h0, li_he0, li_hep, lr_hp, lr_hep, lr_hepp, ld_hep, l_ff;
27+ Real gamma_lh0, gamma_lhe0, gamma_lhep, e_h0, e_he0, e_hep, H;
28+ int heat_flag, n_iter;
29+ Real diff, tol;
30+
31+ // set flag to 1 for photoionization & heating
32+ heat_flag = 0 ;
33+
34+ // Real X = 0.76; //hydrogen abundance by mass
35+ Y = 0.24 ; // helium abundance by mass
36+ y = Y / (4 - 4 * Y);
37+
38+ // set the hydrogen number density
39+ n_h = n;
40+
41+ // calculate the recombination and collisional ionization rates
42+ // (Table 2 from Katz 1996)
43+ alpha_hp = (8.4e-11 ) * (1.0 / sqrt (T)) * pow ((T / 1e3 ), (-0.2 )) * (1.0 / (1.0 + pow ((T / 1e6 ), (0.7 ))));
44+ alpha_hep = (1.5e-10 ) * (pow (T, (-0.6353 )));
45+ alpha_d = (1.9e-3 ) * (pow (T, (-1.5 ))) * exp (-470000.0 / T) * (1.0 + 0.3 * exp (-94000.0 / T));
46+ alpha_hepp = (3.36e-10 ) * (1.0 / sqrt (T)) * pow ((T / 1e3 ), (-0.2 )) * (1.0 / (1.0 + pow ((T / 1e6 ), (0.7 ))));
47+ gamma_eh0 = (5.85e-11 ) * sqrt (T) * exp (-157809.1 / T) * (1.0 / (1.0 + sqrt (T / 1e5 )));
48+ gamma_ehe0 = (2.38e-11 ) * sqrt (T) * exp (-285335.4 / T) * (1.0 / (1.0 + sqrt (T / 1e5 )));
49+ gamma_ehep = (5.68e-12 ) * sqrt (T) * exp (-631515.0 / T) * (1.0 / (1.0 + sqrt (T / 1e5 )));
50+ // externally evaluated integrals for photoionization rates
51+ // assumed J(nu) = 10^-22 (nu_L/nu)
52+ gamma_lh0 = 3.19851e-13 ;
53+ gamma_lhe0 = 3.13029e-13 ;
54+ gamma_lhep = 2.00541e-14 ;
55+ // externally evaluated integrals for heating rates
56+ e_h0 = 2.4796e-24 ;
57+ e_he0 = 6.86167e-24 ;
58+ e_hep = 6.21868e-25 ;
59+
60+ // assuming no photoionization, solve equations for number density of
61+ // each species
62+ n_e = n_h; // as a first guess, use the hydrogen number density
63+ n_iter = 20 ;
64+ diff = 1.0 ;
65+ tol = 1.0e-6 ;
66+ if (heat_flag) {
67+ for (int i = 0 ; i < n_iter; i++) {
68+ n_e_old = n_e;
69+ n_h0 = n_h * alpha_hp / (alpha_hp + gamma_eh0 + gamma_lh0 / n_e);
70+ n_hp = n_h - n_h0;
71+ n_hep = y * n_h /
72+ (1.0 + (alpha_hep + alpha_d) / (gamma_ehe0 + gamma_lhe0 / n_e) +
73+ (gamma_ehep + gamma_lhep / n_e) / alpha_hepp);
74+ n_he0 = n_hep * (alpha_hep + alpha_d) / (gamma_ehe0 + gamma_lhe0 / n_e);
75+ n_hepp = n_hep * (gamma_ehep + gamma_lhep / n_e) / alpha_hepp;
76+ n_e = n_hp + n_hep + 2 * n_hepp;
77+ diff = fabs (n_e_old - n_e);
78+ if (diff < tol) {
79+ break ;
80+ }
81+ }
82+ } else {
83+ n_h0 = n_h * alpha_hp / (alpha_hp + gamma_eh0);
84+ n_hp = n_h - n_h0;
85+ n_hep = y * n_h / (1.0 + (alpha_hep + alpha_d) / (gamma_ehe0) + (gamma_ehep) / alpha_hepp);
86+ n_he0 = n_hep * (alpha_hep + alpha_d) / (gamma_ehe0);
87+ n_hepp = n_hep * (gamma_ehep) / alpha_hepp;
88+ n_e = n_hp + n_hep + 2 * n_hepp;
89+ }
90+
91+ // using number densities, calculate cooling rates for
92+ // various processes (Table 1 from Katz 1996)
93+ le_h0 = (7.50e-19 ) * exp (-118348.0 / T) * (1.0 / (1.0 + sqrt (T / 1e5 ))) * n_e * n_h0;
94+ le_hep = (5.54e-17 ) * pow (T, (-0.397 )) * exp (-473638.0 / T) * (1.0 / (1.0 + sqrt (T / 1e5 ))) * n_e * n_hep;
95+ li_h0 = (1.27e-21 ) * sqrt (T) * exp (-157809.1 / T) * (1.0 / (1.0 + sqrt (T / 1e5 ))) * n_e * n_h0;
96+ li_he0 = (9.38e-22 ) * sqrt (T) * exp (-285335.4 / T) * (1.0 / (1.0 + sqrt (T / 1e5 ))) * n_e * n_he0;
97+ li_hep = (4.95e-22 ) * sqrt (T) * exp (-631515.0 / T) * (1.0 / (1.0 + sqrt (T / 1e5 ))) * n_e * n_hep;
98+ lr_hp = (8.70e-27 ) * sqrt (T) * pow ((T / 1e3 ), (-0.2 )) * (1.0 / (1.0 + pow ((T / 1e6 ), (0.7 )))) * n_e * n_hp;
99+ lr_hep = (1.55e-26 ) * pow (T, (0.3647 )) * n_e * n_hep;
100+ lr_hepp = (3.48e-26 ) * sqrt (T) * pow ((T / 1e3 ), (-0.2 )) * (1.0 / (1.0 + pow ((T / 1e6 ), (0.7 )))) * n_e * n_hepp;
101+ ld_hep = (1.24e-13 ) * pow (T, (-1.5 )) * exp (-470000.0 / T) * (1.0 + 0.3 * exp (-94000.0 / T)) * n_e * n_hep;
102+ g_ff = 1.1 + 0.34 * exp (-(5.5 - log (T)) * (5.5 - log (T)) / 3.0 ); // Gaunt factor
103+ l_ff = (1.42e-27 ) * g_ff * sqrt (T) * (n_hp + n_hep + 4 * n_hepp) * n_e;
104+
105+ // calculate total cooling rate (erg s^-1 cm^-3)
106+ cool = le_h0 + le_hep + li_h0 + li_he0 + li_hep + lr_hp + lr_hep + lr_hepp + ld_hep + l_ff;
107+
108+ // calculate total photoionization heating rate
109+ H = 0.0 ;
110+ if (heat_flag) {
111+ H = n_h0 * e_h0 + n_he0 * e_he0 + n_hep * e_hep;
112+ }
113+
114+ cool -= H;
115+
116+ return cool;
117+ }
118+
119+ /* ! \brief computes the cooling rate, based on an analytic fit to a solar metallicity
120+ * CIE cooling curve calculated using Cloudy. For log10T, this returns 0
121+ *
122+ * \return The cooling rate, lambda, in units of erg s^-1 cm^3 (it is NEVER negative)
123+ *
124+ * \note
125+ * It may not be necessary to use __forceinline__, I just used it to ensure I didn't harm existing
126+ * performance
127+ *
128+ * \note
129+ * The actual formula for the fit is first described in the appendix of
130+ * (Schneider & Robertson 2018)[https://ui.adsabs.harvard.edu/abs/2018ApJ...860..135S/abstract
131+ */
132+ __forceinline__ __device__ Real analytic_cie_lambda (Real log10T)
133+ {
134+ // fit to CIE cooling function
135+ if (log10T < 4.0 ) {
136+ return 0.0 ;
137+ } else if (log10T >= 4.0 && log10T < 5.9 ) {
138+ return pow (10.0 , (-1.3 * (log10T - 5.25 ) * (log10T - 5.25 ) - 21.25 ));
139+ } else if (log10T >= 5.9 && log10T < 7.4 ) {
140+ return pow (10.0 , (0.7 * (log10T - 7.1 ) * (log10T - 7.1 ) - 22.8 ));
141+ } else {
142+ return pow (10.0 , (0.45 * log10T - 26.065 ));
143+ }
144+ }
145+
146+ /* ! Encapsulates our model and configuration for photoelectric heating
147+ *
148+ * This implements a very simple model
149+ * - we apply uniform photoelectric heating (over all space and time) to all gas at temperatures
150+ * below 1e4 K
151+ * - this model is described within
152+ * [Kim & Ostriker 2015](https://ui.adsabs.harvard.edu/abs/2015ApJ...802...99K/abstract)
153+ *
154+ * @note
155+ * In the future, one could imagine implementing a more sophisticated model like TIGRESS
156+ * - For example the amount of heating could be coupled with the properties of clusters
157+ * within the simulation volume
158+ * - If we started to model varying mmw, we could also adopt the TIGRESS strategy to more
159+ * smoothly turn off heating at higher temperatures
160+ */
161+ struct PhotoelectricHeatingModel {
162+ /* ! This theoretically represents the mean density in the simulation volume. A value of 0.0
163+ * indicates that there is no heating.
164+ *
165+ * @note
166+ * I can't remember the precise interpretation, but I think the idea may be that it may be
167+ * used because it loosely relates to the rate of star formation...
168+ */
169+ double n_av_cgs = 0.0 ;
170+
171+ bool is_active () const { return n_av_cgs != 0.0 ; }
172+
173+ /* ! \brief computes the heating rate per unit volume, erg /s / cm^3.
174+ *
175+ * This **NEVER** returns a negative value.
176+ */
177+ __device__ Real operator ()(Real n, Real T) const { return (T < 1e4 ) ? n * n_av_cgs * 1.0e-26 : 0.0 ; }
178+ };
179+
180+ } // namespace cool_component
181+
182+ /* * @}*/ // end of group
0 commit comments