ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_TwoStreamSW.H
Go to the documentation of this file.
1 #ifndef ERF_TWO_STREAM_SW_H_
2 #define ERF_TWO_STREAM_SW_H_
3 
4 #include <AMReX_GpuControl.H>
5 #include <AMReX_Math.H>
6 #include <AMReX_REAL.H>
7 #include <cmath>
8 
9 /**
10  * @file ERF_TwoStreamSW.H
11  * @brief Shortwave (solar) radiation kernels: Beer-Lambert direct beam and
12  * the two-stream layer solution for the diffuse field.
13  *
14  * The column model (ERF_TwoStreamColumn.H) treats the solar radiation as a
15  * direct beam plus a diffuse field with upward and downward streams:
16  *
17  * - The direct beam is attenuated by Beer-Lambert,
18  * F_dir(m) = S0 * mu0 * exp(-tau_cum(m) / mu0),
19  * where tau_cum is the optical depth from the top of the atmosphere to
20  * interface m and mu0 the cosine of the solar zenith angle.
21  *
22  * - Each layer converts part of the direct beam into diffuse radiation
23  * (scattering) and reflects/transmits the diffuse streams. For a
24  * homogeneous layer with optical depth tau, single-scattering albedo omega
25  * and asymmetry factor g, compute_sw_layer_two_stream() returns the
26  * two-stream reflectance and transmittance for diffuse incidence (R_dif,
27  * T_dif) and the diffuse reflectance and transmittance generated by direct
28  * incidence (R_dir, T_dir), together with the direct transmittance
29  * T_noscat = exp(-tau/mu0). The gamma coefficients are those of the
30  * practical improved flux method (Zdunkowski et al. 1980), as used by
31  * RRTMGP; the layer solution follows Meador and Weaver (1980), Eqs. 14-15.
32  *
33  * - The layers are combined with the surface by the adding method: the
34  * surface reflects the fraction alpha of the direct and diffuse flux that
35  * reaches it, and the reflected radiation is scattered and absorbed again
36  * on its way up. Heating rates follow from the divergence of the net flux
37  * F_dir + F_diff_down - F_diff_up.
38  *
39  * With omega == 0 in every layer the diffuse field reduces to the reflected
40  * direct beam only, and the absorbed surface flux reduces exactly to
41  * (1 - alpha) times the Beer-Lambert direct beam.
42  *
43  * References:
44  * -----------
45  * - Meador, W. E., and W. R. Weaver, 1980: Two-stream approximations to
46  * radiative transfer in planetary atmospheres: A unified description
47  * of existing methods and a new improvement. J. Atmos. Sci., 37,
48  * 630-643.
49  * - Zdunkowski, W. G., R. M. Welch, and G. Korb, 1980: An investigation of
50  * the structure of typical two-stream methods for the calculation of
51  * solar fluxes and heating rates in clouds. Beitr. Phys. Atmos., 53,
52  * 147-166.
53  * - Toon, O. B., C. P. McKay, T. P. Ackerman, and K. Santhanam, 1989:
54  * Rapid calculation of radiative heating rates and photodissociation
55  * rates in inhomogeneous multiple scattering atmospheres. J. Geophys.
56  * Res., 94, 16287-16301.
57  */
58 
59 /**
60  * @brief Compute Beer-Lambert direct-beam flux at a given optical depth.
61  *
62  * F_dir = S0 * cos_zenith * exp(-tau_cumulative / cos_zenith)
63  *
64  * @param[in] tau_cumulative Optical depth from the top of the atmosphere [unitless].
65  * @param[in] S0 Solar constant at the top of the atmosphere [W/m^2].
66  * @param[in] cos_zenith Cosine of the solar zenith angle [unitless].
67  *
68  * @return Downwelling direct-beam flux [W/m^2], or 0 if cos_zenith <= 0 (night).
69  */
70 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
73  amrex::Real cos_zenith)
74 {
75  if (cos_zenith <= 0.0) {
76  return 0.0;
77  }
78  return S0 * cos_zenith * std::exp(-tau_cumulative / cos_zenith);
79 }
80 
81 /**
82  * @brief Compute the shortwave heating rate of a layer from the net
83  * (downward positive) flux at its top and bottom interfaces.
84  *
85  * Q = (F_net_top - F_net_bot) / (dz * rho * cp) [K/s]
86  *
87  * Energy converging into the layer warms it. Returns 0 for unphysical
88  * inputs (dz, rho, cp <= 0) or a non-finite result.
89  *
90  * @param[in] flux_top Net downward flux at the top of the layer [W/m^2].
91  * @param[in] flux_bot Net downward flux at the bottom of the layer [W/m^2].
92  * @param[in] dz Vertical thickness of the layer [m]. Must be positive.
93  * For terrain-aware grids, pass dz = z_cc(k+1) - z_cc(k).
94  * @param[in] rho Density [kg/m^3]. Must be positive.
95  * @param[in] cp Specific heat at constant pressure [J/(kg·K)]. Must be positive.
96  *
97  * @return Heating rate [K/s]. Positive values indicate warming.
98  */
99 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
103 {
104  if (dz <= 0.0 || rho <= 0.0 || cp <= 0.0) {
105  return 0.0;
106  }
107  // Flux divergence: (W/m^2) / (m) = W/m^3
108  amrex::Real flux_divergence = (flux_top - flux_bot) / dz;
109  // Heating rate: (W/m^3) / (kg/m^3 * J/(kg*K)) = K/s
110  amrex::Real heating = flux_divergence / (rho * cp);
111  if (!amrex::Math::isfinite(heating)) {
112  return 0.0;
113  }
114  return heating;
115 }
116 
117 /**
118  * @brief Two-stream reflectance and transmittance of one homogeneous layer.
119  *
120  * All quantities are fractions of the incident flux:
121  * - R_dif, T_dif: reflectance and transmittance for diffuse incidence.
122  * - R_dir, T_dir: diffuse flux reflected upward / transmitted downward per
123  * unit direct-beam flux incident at the top of the layer (the scattered
124  * part only; the surviving direct beam is T_noscat).
125  * - T_noscat = exp(-tau / mu0): direct-beam transmittance.
126  *
127  * For a non-absorbing layer (omega = 1) R_dif + T_dif = 1 and
128  * R_dir + T_dir + T_noscat = 1. For a non-scattering layer (omega = 0)
129  * R_dif = R_dir = T_dir = 0 and T_dif = exp(-2 tau) (diffusivity factor 2).
130  */
132 {
138 };
139 
140 /**
141  * @brief Compute the two-stream layer solution for shortwave radiation.
142  *
143  * Gamma coefficients (practical improved flux method, Zdunkowski et al.
144  * 1980, as in RRTMGP):
145  * gamma1 = (8 - omega * (5 + 3 g)) / 4
146  * gamma2 = 3 omega (1 - g) / 4
147  * gamma3 = (2 - 3 g mu0) / 4
148  * gamma4 = 1 - gamma3
149  * k = sqrt(gamma1^2 - gamma2^2)
150  *
151  * Diffuse incidence (Meador and Weaver 1980):
152  * R_dif = gamma2 (1 - e^{-2 k tau}) / D
153  * T_dif = 2 k e^{-k tau} / D, D = k (1 + e^{-2 k tau}) + gamma1 (1 - e^{-2 k tau})
154  *
155  * Direct incidence (Meador and Weaver 1980, Eqs. 14-15), with
156  * alpha1 = gamma1 gamma4 + gamma2 gamma3, alpha2 = gamma1 gamma3 + gamma2 gamma4:
157  * R_dir = omega / (D (1 - k^2 mu0^2)) *
158  * [ (1 - k mu0)(alpha2 + k gamma3) - (1 + k mu0)(alpha2 - k gamma3) e^{-2 k tau}
159  * - 2 (k gamma3 - alpha2 k mu0) e^{-k tau} T_noscat ]
160  * T_dir = - omega / (D (1 - k^2 mu0^2)) *
161  * [ (1 + k mu0)(alpha1 + k gamma4) T_noscat - (1 - k mu0)(alpha1 - k gamma4) e^{-2 k tau} T_noscat
162  * - 2 (k gamma4 + alpha1 k mu0) e^{-k tau} ]
163  *
164  * The removable singularity at k mu0 = 1 is avoided by nudging mu0 slightly,
165  * and the results are clipped to the physical range (non-negative, and
166  * R_dir + T_dir <= 1 - T_noscat).
167  *
168  * @param[in] tau Optical depth of the layer [unitless].
169  * @param[in] omega Single-scattering albedo of the layer, in [0, 1].
170  * @param[in] g Asymmetry factor of the layer, in [-1, 1].
171  * @param[in] cos_zenith Cosine of the solar zenith angle, in (0, 1].
172  * @return Layer reflectances and transmittances (see TwoStreamLayerSW).
173  */
174 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
177  amrex::Real cos_zenith)
178 {
179  TwoStreamLayerSW L{0.0, 1.0, 0.0, 0.0, 1.0};
180 
181  if (!(tau > 0.0) || !amrex::Math::isfinite(tau)) {
182  return L; // Empty layer: transparent to everything
183  }
184  if (!(cos_zenith > 0.0)) {
185  // Night: no direct beam; the diffuse properties are still defined but
186  // the caller does not use them.
187  L.T_noscat = 0.0;
188  }
189 
190  amrex::Real w0 = omega;
191  if (!(w0 > 0.0) || !amrex::Math::isfinite(w0)) w0 = 0.0;
192  if (w0 > 1.0) w0 = 1.0;
193  amrex::Real asym = g;
194  if (!amrex::Math::isfinite(asym)) asym = 0.0;
195  if (asym > 1.0) asym = 1.0;
196  if (asym < -1.0) asym = -1.0;
197  amrex::Real mu0 = (cos_zenith > 0.0) ? cos_zenith : 1.0;
198  if (mu0 > 1.0) mu0 = 1.0;
199 
200  const amrex::Real gamma1 = (8.0 - w0 * (5.0 + 3.0 * asym)) / 4.0;
201  const amrex::Real gamma2 = 3.0 * w0 * (1.0 - asym) / 4.0;
202  const amrex::Real gamma3 = (2.0 - 3.0 * asym * mu0) / 4.0;
203  const amrex::Real gamma4 = 1.0 - gamma3;
204 
205  const amrex::Real alpha1 = gamma1 * gamma4 + gamma2 * gamma3;
206  const amrex::Real alpha2 = gamma1 * gamma3 + gamma2 * gamma4;
207 
208  // k^2 = gamma1^2 - gamma2^2 >= 0; guard the conservative limit omega = 1.
209  amrex::Real k_sq = (gamma1 - gamma2) * (gamma1 + gamma2);
210  if (k_sq < 1.0e-12) k_sq = 1.0e-12;
211  const amrex::Real k = std::sqrt(k_sq);
212 
213  const amrex::Real E = std::exp(-k * tau);
214  const amrex::Real E2 = E * E;
215  const amrex::Real D = k * (1.0 + E2) + gamma1 * (1.0 - E2);
216  const amrex::Real RT = 1.0 / D;
217 
218  L.R_dif = RT * gamma2 * (1.0 - E2);
219  L.T_dif = RT * 2.0 * k * E;
220  if (L.R_dif < 0.0) L.R_dif = 0.0;
221  if (L.T_dif < 0.0) L.T_dif = 0.0;
222  if (L.R_dif + L.T_dif > 1.0) {
223  const amrex::Real s = 1.0 / (L.R_dif + L.T_dif);
224  L.R_dif *= s;
225  L.T_dif *= s;
226  }
227 
228  if (!(cos_zenith > 0.0)) {
229  return L;
230  }
231 
232  // Direct beam: nudge mu0 away from the removable singularity k mu0 = 1.
233  amrex::Real k_mu = k * mu0;
234  if (std::abs(1.0 - k_mu * k_mu) < 1.0e-4) {
235  k_mu = (k_mu < 1.0) ? (1.0 - 1.0e-2) : (1.0 + 1.0e-2);
236  }
237  const amrex::Real T_noscat = std::exp(-tau / mu0);
238  L.T_noscat = T_noscat;
239 
240  const amrex::Real RT2 = w0 * RT / (1.0 - k_mu * k_mu);
241  const amrex::Real k_g3 = k * gamma3;
242  const amrex::Real k_g4 = k * gamma4;
243 
244  amrex::Real R_dir = RT2 * ((1.0 - k_mu) * (alpha2 + k_g3)
245  - (1.0 + k_mu) * (alpha2 - k_g3) * E2
246  - 2.0 * (k_g3 - alpha2 * k_mu) * E * T_noscat);
247  amrex::Real T_dir = -RT2 * ((1.0 + k_mu) * (alpha1 + k_g4) * T_noscat
248  - (1.0 - k_mu) * (alpha1 - k_g4) * E2 * T_noscat
249  - 2.0 * (k_g4 + alpha1 * k_mu) * E);
250 
251  if (!amrex::Math::isfinite(R_dir) || R_dir < 0.0) R_dir = 0.0;
252  if (!amrex::Math::isfinite(T_dir) || T_dir < 0.0) T_dir = 0.0;
253  const amrex::Real budget = 1.0 - T_noscat; // scattered + absorbed fraction
254  if (R_dir + T_dir > budget) {
255  const amrex::Real s = (R_dir + T_dir > 0.0) ? budget / (R_dir + T_dir) : 0.0;
256  R_dir *= s;
257  T_dir *= s;
258  }
259  L.R_dir = R_dir;
260  L.T_dir = T_dir;
261  return L;
262 }
263 
264 #endif // ERF_TWO_STREAM_SW_H_
amrex::Real Real
Definition: ERF_ShocInterface.H:19
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_sw_direct_flux(amrex::Real tau_cumulative, amrex::Real S0, amrex::Real cos_zenith)
Compute Beer-Lambert direct-beam flux at a given optical depth.
Definition: ERF_TwoStreamSW.H:72
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_sw_heating_rate(amrex::Real flux_top, amrex::Real flux_bot, amrex::Real dz, amrex::Real rho, amrex::Real cp)
Compute the shortwave heating rate of a layer from the net (downward positive) flux at its top and bo...
Definition: ERF_TwoStreamSW.H:101
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE TwoStreamLayerSW compute_sw_layer_two_stream(amrex::Real tau, amrex::Real omega, amrex::Real g, amrex::Real cos_zenith)
Compute the two-stream layer solution for shortwave radiation.
Definition: ERF_TwoStreamSW.H:176
@ rho
Definition: ERF_Kessler.H:25
@ omega
Definition: ERF_Morrison.H:55
@ dz
Definition: ERF_AdvanceWDM6.cpp:272
real(c_double), parameter g
Definition: ERF_module_model_constants.F90:19
real(c_double), parameter cp
Definition: ERF_module_model_constants.F90:22
Two-stream reflectance and transmittance of one homogeneous layer.
Definition: ERF_TwoStreamSW.H:132
amrex::Real T_dir
Definition: ERF_TwoStreamSW.H:136
amrex::Real T_dif
Definition: ERF_TwoStreamSW.H:134
amrex::Real R_dif
Definition: ERF_TwoStreamSW.H:133
amrex::Real R_dir
Definition: ERF_TwoStreamSW.H:135
amrex::Real T_noscat
Definition: ERF_TwoStreamSW.H:137