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