ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_TwoStreamLW.H
Go to the documentation of this file.
1 #ifndef ERF_TWO_STREAM_LW_H_
2 #define ERF_TWO_STREAM_LW_H_
3 
4 #include <AMReX_GpuControl.H>
5 #include <AMReX_Math.H>
6 #include <AMReX_FArrayBox.H>
7 #include <AMReX_REAL.H>
8 #include <cmath>
9 
10 /// Stefan-Boltzmann constant [W/(m^2 K^4)] used by the two-stream LW model.
11 constexpr amrex::Real stefan_boltzmann = amrex::Real(5.670374419e-8);
12 
13 /**
14  * @file ERF_TwoStreamLW.H
15  * @brief Longwave (thermal) radiation using gray-gas two-stream model.
16  *
17  * Implements a simplified, clear-sky longwave radiation model
18  * using the gray-gas two-stream approximation (Toon et al. 1989):
19  *
20  * Vertical orientation follows ERF: the vertical index k increases upward,
21  * so layer k = kmin touches the surface and k = kmax the top of the domain.
22  * Fluxes live on layer interfaces m = k - kmin (bottom of layer k) and
23  * m + 1 (top of layer k); m = 0 is the surface, m = nlev the TOA.
24  *
25  * UPWARD SWEEP (from surface to TOA):
26  * -----------
27  * F_up(m+1) = F_up(m) * exp(-tau_lw) + sigma * T(k)^4 * (1 - exp(-tau_lw))
28  *
29  * Initial condition: F_up(0) = emissivity * sigma * T_surface^4
30  * + (1 - emissivity) * F_down(0)
31  * (gray surface: emitted plus reflected downwelling flux, so the
32  * downward sweep must be completed before the upward sweep starts)
33  *
34  * DOWNWARD SWEEP (from TOA to surface):
35  * ---------------
36  * F_down(m) = F_down(m+1) * exp(-tau_lw) + sigma * T(k)^4 * (1 - exp(-tau_lw))
37  *
38  * Initial condition: F_down(nlev) = 0 (no incoming from space)
39  *
40  * NET FLUX AND HEATING:
41  * ---------------------
42  * F_net(m) = F_up(m) - F_down(m) (positive upward)
43  * Q_lw(k) = -(1 / (rho * cp)) * [F_net(m+1) - F_net(m)] / dz(k) [K/s]
44  *
45  * A layer that emits more than it absorbs has F_net increasing with
46  * height, so Q_lw < 0 (radiative cooling).
47  *
48  * Layer temperature T(k) is the absolute temperature obtained from
49  * rho*theta through the Exner function (see get_temperature_from_rhotheta()
50  * in ERF_TwoStreamRadiation.cpp), not the potential temperature.
51  *
52  * MODEL SIMPLIFICATIONS:
53  * -------------------------
54  * - Uniform optical depth (tau_lw) per layer
55  * - No scattering
56  * - No clouds
57  *
58  * References:
59  * -----------
60  * - Toon, O. B., C. P. McKay, T. P. Ackerman, and K. Santhanam, 1989:
61  * Rapid calculation of radiative heating rates and photodissociation rates
62  * in inhomogeneous multiple scattering atmospheres. J. Geophys. Res., 94,
63  * 16387-16405. https://doi.org/10.1029/JD094iD13p16387
64  *
65  * - Kirchhoff, G., 1860: Ueber den Zusammenhang zwischen den
66  * Emissionsvermögen und den Absorptionsvermögen der Körper für Wärmestrahlung.
67  * Monatsberichte der Akademie der Wissenschaften zu Berlin, 783-787.
68  */
69 
70 /**
71  * @brief Compute thermal (LW) intensity for a given temperature.
72  *
73  * Using the Stefan-Boltzmann law, compute the upwelling or downwelling
74  * thermal radiation intensity:
75  *
76  * I_rad = sigma * T^4
77  *
78  * where sigma is the Stefan-Boltzmann constant and T is absolute temperature.
79  *
80  * This is a GPU-safe inline function.
81  *
82  * @param[in] T Absolute temperature [K]. Must be positive.
83  * @param[in] sigma Stefan-Boltzmann constant [W/(m^2·K^4)].
84  *
85  * @return Radiative intensity [W/m^2]. Always non-negative.
86  *
87  * @note If T ≤ 0, returns 0 (unphysical, but defensive).
88  * @note For typical terrestrial temperatures (200-400 K), this ranges
89  * from ~1 W/m^2 to ~1500 W/m^2.
90  */
91 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
94 {
95  if (T <= 0.0) {
96  return 0.0;
97  }
98  return sigma * T * T * T * T;
99 }
100 
101 /**
102  * @brief Compute transmission coefficient through an absorbing layer.
103  *
104  * For a layer with optical depth tau_lw, the fraction of incident radiation
105  * that is transmitted (not absorbed or scattered) is:
106  *
107  * transmit = exp(-tau_lw)
108  *
109  * The fraction absorbed/scattered is:
110  *
111  * absorb = 1 - exp(-tau_lw)
112  *
113  * This is used in the two-stream upward/downward sweep formulas.
114  *
115  * @param[in] tau_lw Optical depth of the layer [unitless]. Must be ≥ 0.
116  *
117  * @return Transmission fraction [0, 1]. Satisfies transmit + absorb = 1.
118  *
119  * @note If tau_lw = 0, returns 1 (fully transparent).
120  * @note If tau_lw >> 1, returns ≈ 0 (fully opaque).
121  */
122 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
125 {
126  if (tau_lw < 0.0) {
127  return 1.0; // Unphysical, but defensive
128  }
129  return std::exp(-tau_lw);
130 }
131 
132 /**
133  * @brief Compute upwelling LW flux in one layer of a two-stream sweep.
134  *
135  * Implements the upward flux formula in the gray-gas two-stream model:
136  *
137  * F_up_current = F_up_below * exp(-tau_lw) + sigma * T^4 * (1 - exp(-tau_lw))
138  *
139  * This is called iteratively from the surface (where
140  * F_up_surface = emissivity * sigma * T_surface^4 + (1 - emissivity) * F_down_surface)
141  * upward to the TOA.
142  *
143  * @param[in] F_up_below Upwelling flux from the layer below [W/m^2].
144  * @param[in] T_layer Temperature of the current layer [K].
145  * @param[in] sigma Stefan-Boltzmann constant [W/(m^2·K^4)].
146  * @param[in] tau_lw Optical depth of the current layer [unitless].
147  *
148  * @return Upwelling flux at the top of the current layer [W/m^2].
149  *
150  * @note This function assumes each layer acts as a semi-infinite slab
151  * with uniform temperature and optical depth.
152  * @note Called within a device-side kernel; must be GPU-safe.
153  */
154 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
157  amrex::Real sigma, amrex::Real tau_lw)
158 {
159  amrex::Real transmit = compute_lw_transmit(tau_lw);
160  amrex::Real absorb = 1.0 - transmit;
161  amrex::Real I_layer = compute_thermal_intensity(T_layer, sigma);
162  return F_up_below * transmit + I_layer * absorb;
163 }
164 
165 /**
166  * @brief Compute downwelling LW flux in one layer of a two-stream sweep.
167  *
168  * Implements the downward flux formula in the gray-gas two-stream model:
169  *
170  * F_down_current = F_down_above * exp(-tau_lw) + sigma * T^4 * (1 - exp(-tau_lw))
171  *
172  * This is called iteratively from the TOA (where F_down_toa = 0)
173  * downward to the surface.
174  *
175  * @param[in] F_down_above Downwelling flux from the layer above [W/m^2].
176  * @param[in] T_layer Temperature of the current layer [K].
177  * @param[in] sigma Stefan-Boltzmann constant [W/(m^2·K^4)].
178  * @param[in] tau_lw Optical depth of the current layer [unitless].
179  *
180  * @return Downwelling flux at the bottom of the current layer [W/m^2].
181  *
182  * @note This function assumes each layer acts as a semi-infinite slab
183  * with uniform temperature and optical depth.
184  * @note Called within a device-side kernel; must be GPU-safe.
185  */
186 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
189  amrex::Real sigma, amrex::Real tau_lw)
190 {
191  amrex::Real transmit = compute_lw_transmit(tau_lw);
192  amrex::Real absorb = 1.0 - transmit;
193  amrex::Real I_layer = compute_thermal_intensity(T_layer, sigma);
194  return F_down_above * transmit + I_layer * absorb;
195 }
196 
197 /**
198  * @brief Compute LW heating rate from net flux divergence.
199  *
200  * Given the net LW flux (F_up - F_down, positive upward) at the top and
201  * bottom interfaces of a layer, compute the radiative heating rate:
202  *
203  * dF_net/dz = [F_net_top - F_net_bot] / dz
204  * Q_lw = -dF_net/dz / (rho * cp) [K/s]
205  *
206  * Net upward flux increasing with height means the layer loses more
207  * energy through its top than it gains through its bottom, i.e. the layer
208  * cools (Q_lw < 0). This is the same sign convention as the SW heating
209  * rate: energy converging into the layer warms it.
210  *
211  * **Nonuniform dz Support & Sanity Checks**
212  *
213  * This function accepts per-level dz values to support terrain-aware or
214  * nonuniform vertical grids. When dz <= 0, or rho <= 0, or cp <= 0,
215  * returns 0 (heating rate is undefined or unphysical).
216  *
217  * The computed heating rate is guarded against NaN/Inf via the same
218  * parameter validation.
219  *
220  * @param[in] F_net_top Net flux at the top of the layer [W/m^2].
221  * @param[in] F_net_bot Net flux at the bottom of the layer [W/m^2].
222  * @param[in] dz Vertical thickness of the layer [m]. Must be positive.
223  * For nonuniform grids, use per-level spacing.
224  * @param[in] rho Density [kg/m^3]. Must be positive.
225  * @param[in] cp Specific heat at constant pressure [J/(kg·K)]. Must be positive.
226  *
227  * @return Heating rate [K/s]. Positive values indicate warming.
228  * Returns 0 if inputs are unphysical (dz, rho, cp <= 0).
229  *
230  * @note The sign convention is: positive dF_net/dz (net upward flux
231  * increasing with height) corresponds to cooling (dT/dt < 0).
232  * @note For terrain-aware grids, pass dz = z_cc(k+1) - z_cc(k) for each level.
233  */
234 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
238 {
239  if (dz <= 0.0 || rho <= 0.0 || cp <= 0.0) {
240  return 0.0;
241  }
242  // Net flux divergence: (W/m^2) / (m) = W/m^3
243  amrex::Real flux_divergence = (F_net_top - F_net_bot) / dz;
244  // Heating rate: -(W/m^3) / (kg/m^3 * J/(kg*K)) = K/s
245  // Check for NaN/Inf as defensive sanity check
246  amrex::Real heating = -flux_divergence / (rho * cp);
247  if (!amrex::Math::isfinite(heating)) {
248  return 0.0;
249  }
250  return heating;
251 }
252 
253 #endif // ERF_TWO_STREAM_LW_H_
amrex::Real sigma
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:11
amrex::Real Real
Definition: ERF_ShocInterface.H:19
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_lw_flux_down(amrex::Real F_down_above, amrex::Real T_layer, amrex::Real sigma, amrex::Real tau_lw)
Compute downwelling LW flux in one layer of a two-stream sweep.
Definition: ERF_TwoStreamLW.H:188
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_thermal_intensity(amrex::Real T, amrex::Real sigma)
Compute thermal (LW) intensity for a given temperature.
Definition: ERF_TwoStreamLW.H:93
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_lw_transmit(amrex::Real tau_lw)
Compute transmission coefficient through an absorbing layer.
Definition: ERF_TwoStreamLW.H:124
constexpr amrex::Real stefan_boltzmann
Stefan-Boltzmann constant [W/(m^2 K^4)] used by the two-stream LW model.
Definition: ERF_TwoStreamLW.H:11
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_lw_heating_rate(amrex::Real F_net_top, amrex::Real F_net_bot, amrex::Real dz, amrex::Real rho, amrex::Real cp)
Compute LW heating rate from net flux divergence.
Definition: ERF_TwoStreamLW.H:236
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_lw_flux_up(amrex::Real F_up_below, amrex::Real T_layer, amrex::Real sigma, amrex::Real tau_lw)
Compute upwelling LW flux in one layer of a two-stream sweep.
Definition: ERF_TwoStreamLW.H:156
@ rho
Definition: ERF_Kessler.H:25
@ T
Definition: ERF_IndexDefines.H:128
@ dz
Definition: ERF_AdvanceWDM6.cpp:272
real(c_double), parameter cp
Definition: ERF_module_model_constants.F90:22