ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_SuperDropletPCDefinitions.H
Go to the documentation of this file.
1 #ifndef SUPERDROPLET_PC_DEFN_H_
2 #define SUPERDROPLET_PC_DEFN_H_
3 
4 #ifdef ERF_USE_PARTICLES
5 
6 #include <AMReX_Enum.H>
7 #include <AMReX_Array.H>
8 #include <AMReX_REAL.H>
9 #include <AMReX_Particles.H>
10 #include "ERF_IndexDefines.H"
11 #include "ERF_Constants.H"
12 #include "ERFPC.H"
13 #include "ERF_SDInitialization.H"
14 
15 namespace SDPCDefn {
16  using SDSpeciesMassArr = amrex::GpuArray<amrex::ParticleReal*,SupDropInit::num_species_max>;
17  using SDAerosolMassArr = amrex::GpuArray<amrex::ParticleReal*,SupDropInit::num_aerosols_max>;
18 
19  /*! \brief Real-type struct-of-array attributes of a super-droplet */
20  using SuperDropletsRealIdx = ERFParticlesRealIdx;
21 
22  /*! \brief Int-type struct-of-array attributes of a super-droplet */
23  using SuperDropletsIntIdx = ERFParticlesIntIdx;
24 
25  /*! \brief Int-type struct-of-array attributes added during runtime */
26  struct SuperDropletsIntIdxSoA_RT
27  {
28  enum {
29  active = 0, /*!< Active/inactive */
30  ncomps
31  };
32  };
33 
34  /*! \brief Real-type struct-of-array attributes added during runtime */
35  struct SuperDropletsRealIdxSoA_RT
36  {
37  enum {
38  radius = 0, /*!< Radius of physical particles */
39  multiplicity, /*!< Number of droplets that this super-droplet represents
40  (Real type to handle large values) */
41  term_vel, /*!< Terminal velocity */
42 #ifdef ERF_USE_ML_UPHYS_DIAGNOSTICS
43  cond_tendency, /*!< Condensation/evaporation tendency */
44 #endif
45  uid, /*!< unique ID */
46  ncomps
47  };
48  };
49 
50  /*! \brief Real-type struct-of-array attributes added during runtime for ice */
51  struct SDIceRealIdxSoA_RT
52  {
53  enum {
54  T_fz = 0, /*!< freezing temperature */
55  a, /*!< equatorial radius */
56  c, /*!< porous radius */
57  m_rime, /*!< rime mass */
58  n_mono, /*!< number of primary ice crystals */
59  ncomps
60  };
61  };
62 }
63 
64 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
65 static int ridx_a(const int a_i, /*!< Species index */
66  const int a_num_a, /*!< Number of aerosols */
67  const int a_num_s /*!< Number of species */)
68 {
69  amrex::ignore_unused(a_num_a);
70  amrex::ignore_unused(a_num_s);
71  return SDPCDefn::SuperDropletsRealIdxSoA_RT::ncomps
72  + a_i;
73 }
74 
75 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
76 static int ridx_s(const int a_i, /*!< Species index */
77  const int a_num_a, /*!< Number of aerosols */
78  const int a_num_s /*!< Number of species */)
79 {
80  amrex::ignore_unused(a_num_s);
81  return SDPCDefn::SuperDropletsRealIdxSoA_RT::ncomps
82  + a_num_a
83  + a_i;
84 }
85 
86 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
87 static int idx_a(const int a_i, /*!< Species index */
88  const int a_num_a, /*!< Number of aerosols */
89  const int a_num_s /*!< Number of species */)
90 {
91  return SDPCDefn::SuperDropletsRealIdx::ncomps + ridx_a(a_i,a_num_a,a_num_s);
92 }
93 
94 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
95 static int idx_s(const int a_i, /*!< Species index */
96  const int a_num_a, /*!< Number of aerosols */
97  const int a_num_s /*!< Number of species */)
98 {
99  return SDPCDefn::SuperDropletsRealIdx::ncomps + ridx_s(a_i,a_num_a,a_num_s);
100 }
101 
102 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
103 static int ridx_ice_Tfz(const int a_num_a, /*!< Number of aerosols */
104  const int a_num_s /*!< Number of species */)
105 {
106  return SDPCDefn::SuperDropletsRealIdxSoA_RT::ncomps
107  + a_num_a
108  + a_num_s
109  + SDPCDefn::SDIceRealIdxSoA_RT::T_fz;
110 }
111 
112 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
113 static int ridx_ice_a(const int a_num_a, /*!< Number of aerosols */
114  const int a_num_s /*!< Number of species */)
115 {
116  return SDPCDefn::SuperDropletsRealIdxSoA_RT::ncomps
117  + a_num_a
118  + a_num_s
119  + SDPCDefn::SDIceRealIdxSoA_RT::a;
120 }
121 
122 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
123 static int ridx_ice_c(const int a_num_a, /*!< Number of aerosols */
124  const int a_num_s /*!< Number of species */)
125 {
126  return SDPCDefn::SuperDropletsRealIdxSoA_RT::ncomps
127  + a_num_a
128  + a_num_s
129  + SDPCDefn::SDIceRealIdxSoA_RT::c;
130 }
131 
132 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
133 static int ridx_ice_mrime(const int a_num_a, /*!< Number of aerosols */
134  const int a_num_s /*!< Number of species */)
135 {
136  return SDPCDefn::SuperDropletsRealIdxSoA_RT::ncomps
137  + a_num_a
138  + a_num_s
139  + SDPCDefn::SDIceRealIdxSoA_RT::m_rime;
140 }
141 
142 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
143 static int ridx_ice_nmono(const int a_num_a, /*!< Number of aerosols */
144  const int a_num_s /*!< Number of species */)
145 {
146  return SDPCDefn::SuperDropletsRealIdxSoA_RT::ncomps
147  + a_num_a
148  + a_num_s
149  + SDPCDefn::SDIceRealIdxSoA_RT::n_mono;
150 }
151 
152 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
153 static int idx_ice_Tfz(const int a_num_a, /*!< Number of aerosols */
154  const int a_num_s /*!< Number of species */)
155 {
156  return SDPCDefn::SuperDropletsRealIdx::ncomps + ridx_ice_Tfz(a_num_a,a_num_s);
157 }
158 
159 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
160 static int idx_ice_a(const int a_num_a, /*!< Number of aerosols */
161  const int a_num_s /*!< Number of species */)
162 {
163  return SDPCDefn::SuperDropletsRealIdx::ncomps + ridx_ice_a(a_num_a,a_num_s);
164 }
165 
166 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
167 static int idx_ice_c(const int a_num_a, /*!< Number of aerosols */
168  const int a_num_s /*!< Number of species */)
169 {
170  return SDPCDefn::SuperDropletsRealIdx::ncomps + ridx_ice_c(a_num_a,a_num_s);
171 }
172 
173 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
174 static int idx_ice_mrime(const int a_num_a, /*!< Number of aerosols */
175  const int a_num_s /*!< Number of species */)
176 {
177  return SDPCDefn::SuperDropletsRealIdx::ncomps + ridx_ice_mrime(a_num_a,a_num_s);
178 }
179 
180 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
181 static int idx_ice_nmono(const int a_num_a, /*!< Number of aerosols */
182  const int a_num_s /*!< Number of species */)
183 {
184  return SDPCDefn::SuperDropletsRealIdx::ncomps + ridx_ice_nmono(a_num_a,a_num_s);
185 }
186 
187 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
188 static auto ice_rho(const amrex::ParticleReal a_a, /*!< polar radius */
189  const amrex::ParticleReal a_c, /*!< equatorial radius */
190  const amrex::ParticleReal a_mass /*!< mass */ )
191 {
192  auto vol = static_cast<amrex::ParticleReal>(four_thirds_pi)*a_a*a_a*a_c;
193  if (vol == amrex::ParticleReal(0)) {
194  AMREX_ALWAYS_ASSERT(a_mass == amrex::ParticleReal(0));
195  return amrex::ParticleReal(0);
196  } else {
197  return a_mass/vol;
198  }
199 }
200 
201 /*! \brief Compute total mass of a droplet from its components */
202 AMREX_GPU_DEVICE AMREX_FORCE_INLINE
203 static amrex::ParticleReal SD_total_mass( const int a_idx, /*!< Particle index */
204  const int a_num_sp, /*!< number of species */
205  const int a_num_ae, /*!< number of aerosols */
206  const SDPCDefn::SDSpeciesMassArr& a_sp_mass, /*!< species masses */
207  const SDPCDefn::SDAerosolMassArr& a_ae_mass /*!< aerosol masses */ )
208 {
209  amrex::ParticleReal retval = zero;
210  for (int j = 0; j < a_num_sp; j++) {
211  retval += a_sp_mass[j][a_idx];
212  }
213  for (int j = 0; j < a_num_ae; j++) {
214  retval += a_ae_mass[j][a_idx];
215  }
216  return retval;
217 }
218 
219 /*! \brief Compute effective radius of a droplet from its components */
220 AMREX_GPU_DEVICE AMREX_FORCE_INLINE
221 static amrex::ParticleReal SD_effective_radius( const int a_idx, /*!< Particle index */
222  const int a_idx_w, /*!< Species index of water */
223  amrex::ParticleReal a_rho_w, /*!< density of water */
224  const int a_num_sp, /*!< number of species */
225  const int a_num_ae, /*!< number of aerosols */
226  const int* const a_sp_sol_arr, /*!< solubility of species */
227  const int* const a_ae_sol_arr, /*!< solubility of aerosols */
228  const SDPCDefn::SDSpeciesMassArr& a_sp_mass, /*!< species masses */
229  const SDPCDefn::SDAerosolMassArr& a_ae_mass, /*!< aerosol masses */
230  const amrex::ParticleReal* const a_sp_rho, /*!< species densities */
231  const amrex::ParticleReal* const a_ae_rho /*!< aerosol densities */ )
232 {
233  amrex::ParticleReal m_w = a_sp_mass[a_idx_w][a_idx];
234  amrex::ParticleReal m_s = zero;
235  amrex::ParticleReal m_p = zero;
236  amrex::ParticleReal rho_p = zero;
237  for (int j = 0; j < a_num_sp; j++) {
238  if (j != a_idx_w) {
239  if (a_sp_sol_arr[j]) {
240  m_s += a_sp_mass[j][a_idx];
241  } else {
242  m_p += a_sp_mass[j][a_idx];
243  rho_p += a_sp_rho[j]*a_sp_mass[j][a_idx];
244  }
245  }
246  }
247  for (int j = 0; j < a_num_ae; j++) {
248  if (a_ae_sol_arr[j]) {
249  m_s += a_ae_mass[j][a_idx];
250  } else {
251  m_p += a_ae_mass[j][a_idx];
252  rho_p += a_ae_rho[j]*a_ae_mass[j][a_idx];
253  }
254  }
255  if (m_p > zero) { rho_p /= m_p; }
256  else { rho_p = one; }
257  auto m_t = m_w + m_s + (a_rho_w/rho_p)*m_p;
258  auto r_eff = std::cbrt(m_t / (four_thirds_pi*a_rho_w));
259  return r_eff;
260 }
261 
262 /*! \brief Compute "dry" radius of a droplet from its insoluble components */
263 AMREX_GPU_DEVICE AMREX_FORCE_INLINE
264 static amrex::ParticleReal SD_dry_radius( const int a_idx, /*!< Particle index */
265  const int a_num_sp, /*!< number of species */
266  const int a_num_ae, /*!< number of aerosols */
267  const int* const a_sp_sol_arr, /*!< solubility of species */
268  const int* const a_ae_sol_arr, /*!< solubility of aerosols */
269  const SDPCDefn::SDSpeciesMassArr& a_sp_mass, /*!< species masses */
270  const SDPCDefn::SDAerosolMassArr& a_ae_mass, /*!< aerosol masses */
271  const amrex::ParticleReal* const a_sp_rho, /*!< species densities */
272  const amrex::ParticleReal* const a_ae_rho /*!< aerosol densities */ )
273 {
274  amrex::ParticleReal m_p = zero;
275  amrex::ParticleReal rho_p = zero;
276  for (int j = 0; j < a_num_sp; j++) {
277  if (!a_sp_sol_arr[j]) {
278  m_p += a_sp_mass[j][a_idx];
279  rho_p += a_sp_rho[j]*a_sp_mass[j][a_idx];
280  }
281  }
282  for (int j = 0; j < a_num_ae; j++) {
283  if (!a_ae_sol_arr[j]) {
284  m_p += a_ae_mass[j][a_idx];
285  rho_p += a_ae_rho[j]*a_ae_mass[j][a_idx];
286  }
287  }
288  if (m_p > zero) { rho_p /= m_p; }
289  else { rho_p = one; }
290  auto r_dry = std::cbrt(m_p / (four_thirds_pi*rho_p));
291  return r_dry;
292 }
293 
294 /*! \brief Compute total surface area of INP (ice-nucleating) components
295  *
296  * The following code is adapted from SCALE-SDM:
297  * https://github.com/Shima-Lab/SCALE-SDM_mixed-phase_Shima2019
298  * Copyright (c) 2012-2015, Team SCALE
299  * All rights reserved.
300  * BSD 2-Clause License
301  */
302 AMREX_GPU_DEVICE AMREX_FORCE_INLINE
303 static amrex::ParticleReal SD_INP_surface_area(
304  const int a_idx, /*!< Particle index */
305  const int a_num_sp, /*!< number of species */
306  const int a_num_ae, /*!< number of aerosols */
307  const int* const a_sp_is_INP, /*!< INP flag for species */
308  const int* const a_ae_is_INP, /*!< INP flag for aerosols */
309  const SDPCDefn::SDSpeciesMassArr& a_sp_mass, /*!< species masses */
310  const SDPCDefn::SDAerosolMassArr& a_ae_mass, /*!< aerosol masses */
311  const amrex::ParticleReal* const a_sp_rho, /*!< species densities */
312  const amrex::ParticleReal* const a_ae_rho /*!< aerosol densities */ )
313 {
314  amrex::ParticleReal total_area = 0.0;
315 
316  // Species contribution
317  for (int j = 0; j < a_num_sp; j++) {
318  if (a_sp_is_INP[j] && a_sp_mass[j][a_idx] > 0.0) {
319  auto vol = a_sp_mass[j][a_idx] / a_sp_rho[j];
320  auto r = std::cbrt(vol * 3.0 / (4.0 * PI));
321  total_area += 4.0 * PI * r * r;
322  }
323  }
324 
325  // Aerosol contribution
326  for (int j = 0; j < a_num_ae; j++) {
327  if (a_ae_is_INP[j] && a_ae_mass[j][a_idx] > 0.0) {
328  auto vol = a_ae_mass[j][a_idx] / a_ae_rho[j];
329  auto r = std::cbrt(vol * 3.0 / (4.0 * PI));
330  total_area += 4.0 * PI * r * r;
331  }
332  }
333 
334  return total_area;
335 }
336 
337 /*! \brief INAS density parameterization for immersion freezing
338  *
339  * Implements the Ice Nucleation Active Site (INAS) density parameterization
340  * of Niemand et al. (2012) for heterogeneous ice nucleation.
341  *
342  * Reference:
343  * - Niemand, M., et al. (2012): A Particle-Surface-Area-Based Parameterization
344  * of Immersion Freezing on Desert Dust Particles. J. Atmos. Sci., 69, 3077-3092.
345  *
346  * The following code is adapted from SCALE-SDM:
347  * https://github.com/Shima-Lab/SCALE-SDM_mixed-phase_Shima2019
348  * Copyright (c) 2012-2015, Team SCALE
349  * All rights reserved.
350  * BSD 2-Clause License
351  */
352 struct INAS_Niemand2012 {
353 
354  // Default constructor
355  INAS_Niemand2012 () = default;
356 
357  amrex::Real a0 = 1.0; /*!< INAS a0 parameter [m^-2] */
358  amrex::Real a1 = 0.517; /*!< INAS a1 parameter [degC^-1] */
359  amrex::Real a2 = 8.934; /*!< INAS a2 parameter [-] */
360 
361  amrex::Real Tfz_min = -36.0; /*!< coldest heterogeneous freezing [degC] */
362  amrex::Real Tfz_max = -12.0; /*!< warmest heterogeneous freezing [degC] */
363  amrex::Real Tfz_homogeneous = -38.0; /*!< homogeneous freezing limit [degC] */
364 
365  /*! \brief Compute INAS density at temperature T
366  * n_s(T) = a0 * exp(-a1*T + a2) [active sites per m^2]
367  * where T is in degrees Celsius */
368  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
369  amrex::Real n_s(amrex::Real T_degC) const {
370  return a0 * std::exp(-a1 * T_degC + a2);
371  }
372 
373  /*! \brief Compute cumulative freezing probability at temperature T
374  * P(T) = 1 - exp(-A * n_s(T))
375  * where A is the INP surface area */
376  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
377  amrex::Real P_freeze(amrex::Real T_degC, amrex::Real A) const {
378  return 1.0 - std::exp(-A * n_s(T_degC));
379  }
380 
381  /*! \brief Sample freezing temperature using inverse CDF method
382  *
383  * Given uniform random number u in [0,1] and INP surface area A,
384  * returns Tfz in Kelvin.
385  *
386  * Based on the singular hypothesis: each particle has a characteristic
387  * freezing temperature determined by its coldest active site.
388  */
389  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
390  amrex::Real sample_Tfz(amrex::Real u, amrex::Real A) const {
391  // No INP surface area -> homogeneous freezing
392  if (A <= 0.0) {
393  return Tfz_homogeneous + 273.15;
394  }
395 
396  // Check if particle is IN-inactive (no active sites even at Tfz_min)
397  // This corresponds to particles that won't nucleate ice heterogeneously
398  auto P_max = P_freeze(Tfz_min, A);
399  if (u > P_max) {
400  // Particle has no active sites -> homogeneous freezing
401  return Tfz_homogeneous + amrex::Real(273.15);
402  }
403 
404  // Inverse CDF: solve P(T) = u for T
405  // P(T) = 1 - exp(-A * a0 * exp(-a1*T + a2))
406  // Let y = -ln(1-u) = A * a0 * exp(-a1*T + a2)
407  // -a1*T + a2 = ln(y / (A*a0))
408  // T = (a2 - ln(y/(A*a0))) / a1
409  auto y = -std::log(amrex::Real(1) - u);
410  auto T_degC = (a2 - std::log(y / (A * a0))) / a1;
411 
412  // Clamp to valid range
413  T_degC = std::max(T_degC, Tfz_min);
414  T_degC = std::min(T_degC, Tfz_max);
415 
416  return T_degC + amrex::Real(273.15); // Convert to Kelvin
417  }
418 };
419 
420 /* \brief SD phase (pure ice, pure water, or mixed ice-water) */
421 AMREX_ENUM (SDPhase,
422  ice,
423  water,
424  mixed
425 );
426 
427 /*! \brief Liquid mass fraction of an SD: m_water/(m_water+m_ice).
428  * Returns 1 for pure water and 0 for pure ice. */
429 AMREX_GPU_DEVICE AMREX_FORCE_INLINE
430 static auto SD_liquid_fraction( const int a_p, /*!< Particle index */
431  const int a_i_w, /*!< Species index of water */
432  const int a_i_i, /*!< Species index of ice */
433  const SDPCDefn::SDSpeciesMassArr& a_sp_mass /*!< species masses */)
434 {
435  if (a_i_i < 0) { return amrex::ParticleReal(1); } // not modeling ice
436  auto m_water = a_sp_mass[a_i_w][a_p];
437  auto m_ice = a_sp_mass[a_i_i][a_p];
438  auto m_cond = m_water + m_ice;
439  return (m_cond > amrex::ParticleReal(0)) ? (m_water/m_cond) : amrex::ParticleReal(1);
440 }
441 
442 /*! \brief Is the SD pure ice, pure water, or a mixed ice-water particle? */
443 AMREX_GPU_DEVICE AMREX_FORCE_INLINE
444 static auto SD_phase( const int a_p, /*!< Particle index */
445  const int a_i_w, /*!< Species index of water */
446  const int a_i_i, /*!< Species index of ice */
447  const SDPCDefn::SDSpeciesMassArr& a_sp_mass /*!< species masses */)
448 {
449  if (a_i_i < 0) {
450  return SDPhase::water; // not modeling ice
451  } else {
452  auto m_water = a_sp_mass[a_i_w][a_p];
453  auto m_ice = a_sp_mass[a_i_i][a_p];
454  if (m_ice > 0.0) {
455  return (m_water > 0.0) ? SDPhase::mixed : SDPhase::ice;
456  } else {
457  return SDPhase::water;
458  }
459  }
460 }
461 
462 /*! \brief Types of terminal velocity models */
463 AMREX_ENUM(SDTerminalVelocityType,
464  RogersYau,
465  AtlasUlbrich,
466  CloudRainShima,
467  IceBohm
468 );
469 
470 /*! \brief Types of coalescence kernels */
471 enum struct SDCoalescenceKernelType {
472  golovin,
473  sedimentation,
474  Longs,
475  Halls
476 };
477 
478 /*! \brief Types of kernel relative velocity calculation: terminal_velocity
479  * (only terminal fall speed; default), absolute_velocity (flow velocity minus
480  * terminal), radial_velocity (radial component of the relative velocity along
481  * the pair separation vector). */
482 AMREX_ENUM(SDKernelRelativeVelocityType,
483  terminal_velocity,
484  absolute_velocity,
485  radial_velocity
486 );
487 
488 /*! \brief Types of phase change ODE time integrator */
489 enum struct SDMassChangeTIMethod {
490  RK3BS, RK4, BE, CN, DIRK2
491 };
492 
493 #endif
494 #endif
495 
constexpr amrex::Real one
Definition: ERF_Constants.H:9
constexpr amrex::Real zero
Definition: ERF_Constants.H:8
constexpr amrex::Real PI
Definition: ERF_Constants.H:42
constexpr amrex::Real four_thirds_pi
Definition: ERF_Constants.H:44
AMREX_ENUM(InitType, None, Input_Sounding, NCFile, WRFInput, Metgrid, Uniform, ConstantDensity, ConstantDensityLinearTheta, Isentropic, MoistBaseState, HindCast)
Initial-condition source used to populate the ERF state.
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
amrex::Real Real
Definition: ERF_ShocInterface.H:19
real(c_double), parameter a2
Definition: ERF_module_model_constants.F90:95