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  if (m_p > zero) { rho_p /= m_p; }
257  else { rho_p = one; }
258  auto m_t = m_w + m_s + (a_rho_w/rho_p)*m_p;
259  auto r_eff = std::cbrt(m_t / (four_thirds_pi*a_rho_w));
260  return r_eff;
261 }
262 
263 /*! \brief Compute "dry" radius of a droplet from its insoluble components */
264 AMREX_GPU_DEVICE AMREX_FORCE_INLINE
265 static amrex::ParticleReal SD_dry_radius( const int a_idx, /*!< Particle index */
266  const int a_num_sp, /*!< number of species */
267  const int a_num_ae, /*!< number of aerosols */
268  const int* const a_sp_sol_arr, /*!< solubility of species */
269  const int* const a_ae_sol_arr, /*!< solubility of aerosols */
270  const SDPCDefn::SDSpeciesMassArr& a_sp_mass, /*!< species masses */
271  const SDPCDefn::SDAerosolMassArr& a_ae_mass, /*!< aerosol masses */
272  const amrex::ParticleReal* const a_sp_rho, /*!< species densities */
273  const amrex::ParticleReal* const a_ae_rho /*!< aerosol densities */ )
274 {
275  amrex::ParticleReal m_p = zero;
276  amrex::ParticleReal rho_p = zero;
277  for (int j = 0; j < a_num_sp; j++) {
278  if (!a_sp_sol_arr[j]) {
279  m_p += a_sp_mass[j][a_idx];
280  rho_p += a_sp_rho[j]*a_sp_mass[j][a_idx];
281  }
282  }
283  for (int j = 0; j < a_num_ae; j++) {
284  if (!a_ae_sol_arr[j]) {
285  m_p += a_ae_mass[j][a_idx];
286  rho_p += a_ae_rho[j]*a_ae_mass[j][a_idx];
287  }
288  }
289  if (m_p > zero) { rho_p /= m_p; }
290  else { rho_p = one; }
291  auto r_dry = std::cbrt(m_p / (four_thirds_pi*rho_p));
292  return r_dry;
293 }
294 
295 /*! \brief Compute total surface area of INP (ice-nucleating) components
296  *
297  * The following code is adapted from SCALE-SDM:
298  * https://github.com/Shima-Lab/SCALE-SDM_mixed-phase_Shima2019
299  * Copyright (c) 2012-2015, Team SCALE
300  * All rights reserved.
301  * BSD 2-Clause License
302  */
303 AMREX_GPU_DEVICE AMREX_FORCE_INLINE
304 static amrex::ParticleReal SD_INP_surface_area(
305  const int a_idx, /*!< Particle index */
306  const int a_num_sp, /*!< number of species */
307  const int a_num_ae, /*!< number of aerosols */
308  const int* const a_sp_is_INP, /*!< INP flag for species */
309  const int* const a_ae_is_INP, /*!< INP flag for aerosols */
310  const SDPCDefn::SDSpeciesMassArr& a_sp_mass, /*!< species masses */
311  const SDPCDefn::SDAerosolMassArr& a_ae_mass, /*!< aerosol masses */
312  const amrex::ParticleReal* const a_sp_rho, /*!< species densities */
313  const amrex::ParticleReal* const a_ae_rho /*!< aerosol densities */ )
314 {
315  amrex::ParticleReal total_area = 0.0;
316 
317  // Species contribution
318  for (int j = 0; j < a_num_sp; j++) {
319  if (a_sp_is_INP[j] && a_sp_mass[j][a_idx] > 0.0) {
320  auto vol = a_sp_mass[j][a_idx] / a_sp_rho[j];
321  auto r = std::cbrt(vol * 3.0 / (4.0 * PI));
322  total_area += 4.0 * PI * r * r;
323  }
324  }
325 
326  // Aerosol contribution
327  for (int j = 0; j < a_num_ae; j++) {
328  if (a_ae_is_INP[j] && a_ae_mass[j][a_idx] > 0.0) {
329  auto vol = a_ae_mass[j][a_idx] / a_ae_rho[j];
330  auto r = std::cbrt(vol * 3.0 / (4.0 * PI));
331  total_area += 4.0 * PI * r * r;
332  }
333  }
334 
335  return total_area;
336 }
337 
338 /*! \brief INAS density parameterization for immersion freezing
339  *
340  * Implements the Ice Nucleation Active Site (INAS) density parameterization
341  * of Niemand et al. (2012) for heterogeneous ice nucleation.
342  *
343  * Reference:
344  * - Niemand, M., et al. (2012): A Particle-Surface-Area-Based Parameterization
345  * of Immersion Freezing on Desert Dust Particles. J. Atmos. Sci., 69, 3077-3092.
346  *
347  * The following code is adapted from SCALE-SDM:
348  * https://github.com/Shima-Lab/SCALE-SDM_mixed-phase_Shima2019
349  * Copyright (c) 2012-2015, Team SCALE
350  * All rights reserved.
351  * BSD 2-Clause License
352  */
353 struct INAS_Niemand2012 {
354 
355  // Default constructor
356  INAS_Niemand2012 () = default;
357 
358  amrex::Real a0 = 1.0; /*!< INAS a0 parameter [m^-2] */
359  amrex::Real a1 = 0.517; /*!< INAS a1 parameter [degC^-1] */
360  amrex::Real a2 = 8.934; /*!< INAS a2 parameter [-] */
361 
362  amrex::Real Tfz_min = -36.0; /*!< coldest heterogeneous freezing [degC] */
363  amrex::Real Tfz_max = -12.0; /*!< warmest heterogeneous freezing [degC] */
364  amrex::Real Tfz_homogeneous = -38.0; /*!< homogeneous freezing limit [degC] */
365 
366  /*! \brief Compute INAS density at temperature T
367  * n_s(T) = a0 * exp(-a1*T + a2) [active sites per m^2]
368  * where T is in degrees Celsius */
369  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
370  amrex::Real n_s(amrex::Real T_degC) const {
371  return a0 * std::exp(-a1 * T_degC + a2);
372  }
373 
374  /*! \brief Compute cumulative freezing probability at temperature T
375  * P(T) = 1 - exp(-A * n_s(T))
376  * where A is the INP surface area */
377  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
378  amrex::Real P_freeze(amrex::Real T_degC, amrex::Real A) const {
379  return 1.0 - std::exp(-A * n_s(T_degC));
380  }
381 
382  /*! \brief Sample freezing temperature using inverse CDF method
383  *
384  * Given uniform random number u in [0,1] and INP surface area A,
385  * returns Tfz in Kelvin.
386  *
387  * Based on the singular hypothesis: each particle has a characteristic
388  * freezing temperature determined by its coldest active site.
389  */
390  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
391  amrex::Real sample_Tfz(amrex::Real u, amrex::Real A) const {
392  // No INP surface area -> homogeneous freezing
393  if (A <= 0.0) {
394  return Tfz_homogeneous + 273.15;
395  }
396 
397  // Check if particle is IN-inactive (no active sites even at Tfz_min)
398  // This corresponds to particles that won't nucleate ice heterogeneously
399  auto P_max = P_freeze(Tfz_min, A);
400  if (u > P_max) {
401  // Particle has no active sites -> homogeneous freezing
402  return Tfz_homogeneous + amrex::Real(273.15);
403  }
404 
405  // Inverse CDF: solve P(T) = u for T
406  // P(T) = 1 - exp(-A * a0 * exp(-a1*T + a2))
407  // Let y = -ln(1-u) = A * a0 * exp(-a1*T + a2)
408  // -a1*T + a2 = ln(y / (A*a0))
409  // T = (a2 - ln(y/(A*a0))) / a1
410  auto y = -std::log(amrex::Real(1) - u);
411  auto T_degC = (a2 - std::log(y / (A * a0))) / a1;
412 
413  // Clamp to valid range
414  T_degC = std::max(T_degC, Tfz_min);
415  T_degC = std::min(T_degC, Tfz_max);
416 
417  return T_degC + amrex::Real(273.15); // Convert to Kelvin
418  }
419 };
420 
421 /* \brief SD phase (pure ice, pure water, or mixed ice-water) */
422 AMREX_ENUM (SDPhase,
423  ice,
424  water,
425  mixed
426 );
427 
428 /*! \brief Liquid mass fraction of an SD: m_water/(m_water+m_ice).
429  * Returns 1 for pure water and 0 for pure ice. */
430 AMREX_GPU_DEVICE AMREX_FORCE_INLINE
431 static auto SD_liquid_fraction( const int a_p, /*!< Particle index */
432  const int a_i_w, /*!< Species index of water */
433  const int a_i_i, /*!< Species index of ice */
434  const SDPCDefn::SDSpeciesMassArr& a_sp_mass /*!< species masses */)
435 {
436  if (a_i_i < 0) { return amrex::ParticleReal(1); } // not modeling ice
437  auto m_water = a_sp_mass[a_i_w][a_p];
438  auto m_ice = a_sp_mass[a_i_i][a_p];
439  auto m_cond = m_water + m_ice;
440  return (m_cond > amrex::ParticleReal(0)) ? (m_water/m_cond) : amrex::ParticleReal(1);
441 }
442 
443 /*! \brief Is the SD pure ice, pure water, or a mixed ice-water particle? */
444 AMREX_GPU_DEVICE AMREX_FORCE_INLINE
445 static auto SD_phase( const int a_p, /*!< Particle index */
446  const int a_i_w, /*!< Species index of water */
447  const int a_i_i, /*!< Species index of ice */
448  const SDPCDefn::SDSpeciesMassArr& a_sp_mass /*!< species masses */)
449 {
450  if (a_i_i < 0) {
451  return SDPhase::water; // not modeling ice
452  } else {
453  auto m_water = a_sp_mass[a_i_w][a_p];
454  auto m_ice = a_sp_mass[a_i_i][a_p];
455  if (m_ice > 0.0) {
456  return (m_water > 0.0) ? SDPhase::mixed : SDPhase::ice;
457  } else {
458  return SDPhase::water;
459  }
460  }
461 }
462 
463 /*! \brief Types of terminal velocity models */
464 AMREX_ENUM(SDTerminalVelocityType,
465  RogersYau,
466  AtlasUlbrich,
467  CloudRainShima,
468  IceBohm
469 );
470 
471 /*! \brief Types of coalescence kernels */
472 enum struct SDCoalescenceKernelType {
473  golovin,
474  sedimentation,
475  Longs,
476  Halls
477 };
478 
479 /*! \brief Types of kernel relative velocity calculation: terminal_velocity
480  * (only terminal fall speed; default), absolute_velocity (flow velocity minus
481  * terminal), radial_velocity (radial component of the relative velocity along
482  * the pair separation vector). */
483 AMREX_ENUM(SDKernelRelativeVelocityType,
484  terminal_velocity,
485  absolute_velocity,
486  radial_velocity
487 );
488 
489 /*! \brief Types of phase change ODE time integrator */
490 enum struct SDMassChangeTIMethod {
491  RK3BS, RK4, BE, CN, DIRK2
492 };
493 
494 #endif
495 #endif
496 
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)
Dimensionless numeric literals and pure mathematical constants.
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:30
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:29
constexpr amrex::Real PI
Definition: ERF_NumericalConstants.H:39
constexpr amrex::Real four_thirds_pi
Definition: ERF_NumericalConstants.H:41
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