ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_SuperDropletPCCoalescence.H
Go to the documentation of this file.
1 #ifndef SUPERDROPLET_PC_COALESCENCE_H_
2 #define SUPERDROPLET_PC_COALESCENCE_H_
3 
4 #include <algorithm>
5 #include "ERF_Constants.H"
6 
7 #ifdef ERF_USE_PARTICLES
8 
9 /*! \brief Coalescence kernels
10 
11  Reference for Long's and Hall's kernels:
12  + Shima et al., 2009, "The super-droplet method for the numerical simulation of clouds and
13  precipitation: A particle-based and probabilistic microphysics model coupled with a non-
14  hydrostatic model." Quart. J. Roy. Meteorol. Soc., 135: 1307-1320
15  + https://github.com/Shima-Lab/SCALE-SDM_BOMEX_Sato2018/blob/master/contrib/SDM/sdm_motion.f90
16 */
17 template<typename RT, int SPACEDIM>
18 struct CollisionKernel
19 {
20  /*! \brief Golovin kernel */
21  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
22  RT golovin ( const RT a_r1, /*!< radius of droplet 1 */
23  const RT a_r2 /*!< radius of droplet 2 */ ) const noexcept
24  {
25  RT b = 1.5e03;
26  auto X_1 = (4.0*PI/3.0) * a_r1*a_r1*a_r1;
27  auto X_2 = (4.0*PI/3.0) * a_r2*a_r2*a_r2;
28  return b * (X_1 + X_2);
29  }
30 
31  /* The following code is adapted from SCALE-SDM:
32  * Copyright (c) 2012-2014, Team SCALE, All rights reserved. */
33  /*! \brief Calculate collision kernel for gravitational sedimentation
34  *
35  * This function computes the collision kernel for collisions driven by
36  * differential gravitational settling. It uses the terminal velocities
37  * of particles to determine collision probability.
38  *
39  * \param[in] a_r1 Radius of particle 1 (m)
40  * \param[in] a_r2 Radius of particle 2 (m)
41  * \param[in] a_v1 Pointer to velocity vector of particle 1 (m/s)
42  * \param[in] a_v2 Pointer to velocity vector of particle 2 (m/s)
43  * \return Collision kernel value (m³/s)
44  */
45  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
46  RT sedimentation ( const RT a_r1,
47  const RT a_r2,
48  const RT* const a_v1,
49  const RT* const a_v2 ) const noexcept
50  {
51  auto p = std::min(a_r1,a_r2) / std::max(a_r1,a_r2);
52  auto E = 0.5*p*p / ((1.0+p)*(1.0+p));
53  RT dv = 0;
54  for (int d = 0; d < SPACEDIM; d++) {
55  dv += (a_v1[d]-a_v2[d])*(a_v1[d]-a_v2[d]);
56  }
57  dv = std::sqrt(dv);
58  return PI*(a_r1+a_r2)*(a_r1+a_r2)*E*dv;
59  }
60 
61  /* The following code is adapted from SCALE-SDM:
62  * Copyright (c) 2012-2014, Team SCALE, All rights reserved. */
63  /*! \brief Calculate collision kernel using Long's formulation
64  *
65  * Long's kernel is an empirical formulation for cloud droplet collision-coalescence
66  * that accounts for different collision rates based on droplet size. It is particularly
67  * effective for smaller cloud droplets where hydrodynamic effects are important.
68  *
69  * Reference: Long, A. B., 1974: Solutions to the droplet collection equation
70  * for polynomial kernels. J. Atmos. Sci., 31, 1040-1052.
71  *
72  * \param[in] a_r1 Radius of particle 1 (m)
73  * \param[in] a_r2 Radius of particle 2 (m)
74  * \param[in] a_v1 Pointer to velocity vector of particle 1 (m/s)
75  * \param[in] a_v2 Pointer to velocity vector of particle 2 (m/s)
76  * \return Collision kernel value (m³/s)
77  */
78  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
79  RT Longs ( const RT a_r1,
80  const RT a_r2,
81  const RT* const a_v1,
82  const RT* const a_v2 ) const noexcept
83  {
84  auto r1 = std::max( a_r1, a_r2 ); // large
85  auto r2 = std::min( a_r1, a_r2 ); // small
86  auto sumr = r1 + r2;
87 
88  RT c_rate = 1.0;
89  if( r1 <= 5.E-5 ) {
90  c_rate = 4.5E+8 * ( r1*r1 ) * ( 1.0 - 3.E-6/(std::max(3.01E-6,r1)) );
91  }
92  c_rate *= (PI*sumr*sumr);
93 
94  RT dv = 0;
95  for (int d = 0; d < SPACEDIM; d++) {
96  dv += (a_v1[d]-a_v2[d])*(a_v1[d]-a_v2[d]);
97  }
98  dv = std::sqrt(dv);
99 
100  return c_rate*dv;
101  }
102 
103  /* The following code is adapted from SCALE-SDM:
104  * Copyright (c) 2012-2014, Team SCALE, All rights reserved. */
105  /*! \brief r0col data for Hall's kernel */
106  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
107  RT Halls_data_r0col (const int a_i /*!< index */) const noexcept
108  {
109  static const RT vec[15] = { 6.0,
110  8.0,
111  10.0,
112  15.0,
113  20.0,
114  25.0,
115  30.0,
116  40.0,
117  50.0,
118  60.0,
119  70.0,
120  100.0,
121  150.0,
122  200.0,
123  300.0};
124  return vec[a_i];
125  }
126 
127  /* The following code is adapted from SCALE-SDM:
128  * Copyright (c) 2012-2014, Team SCALE, All rights reserved. */
129  /*! \brief r0col data for Hall's kernel */
130  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
131  int Halls_data_r0col ( const RT a_r /*!< radius */) const noexcept
132  {
133  auto radius_microns = a_r*1.0e6; // [m] -> [mu-m]
134  int i;
135  for (i = 0; i < 15; i++) {
136  if (radius_microns <= Halls_data_r0col(i)) { return i; }
137  }
138  return i;
139  }
140 
141  /* The following code is adapted from SCALE-SDM:
142  * Copyright (c) 2012-2014, Team SCALE, All rights reserved. */
143  /*! \brief ratcol data for Hall's kernel */
144  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
145  RT Halls_data_ratcol ( const int a_i /*!< index */) const noexcept
146  {
147  static const RT vec[21] = {0.00,
148  0.05,
149  0.10,
150  0.15,
151  0.20,
152  0.25,
153  0.30,
154  0.35,
155  0.40,
156  0.45,
157  0.50,
158  0.55,
159  0.60,
160  0.65,
161  0.70,
162  0.75,
163  0.80,
164  0.85,
165  0.90,
166  0.95,
167  1.00};
168  return vec[a_i];
169  }
170 
171  /* The following code is adapted from SCALE-SDM:
172  * Copyright (c) 2012-2014, Team SCALE, All rights reserved. */
173  /*! \brief ratcol data for Hall's kernel */
174  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
175  int Halls_data_ratcol ( const RT a_r /*!< ratio */) const noexcept
176  {
177  int i;
178  for (i = 1; i < 20; i++) {
179  if (a_r <= Halls_data_ratcol(i)) { return i; }
180  }
181  return i;
182  }
183 
184  /* The following code is adapted from SCALE-SDM:
185  * Copyright (c) 2012-2014, Team SCALE, All rights reserved. */
186  /*! \brief ecoll data for Hall's kernel */
187  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
188  RT Halls_data_ecoll ( const int a_i, /*!< row index */
189  const int a_j /*!< column index */) const noexcept
190  {
191  const RT ecoll[21][15] =
192  { {0.0010,0.0010,0.0010,0.0010,0.0010,0.0010,0.0010,0.0010,0.0010,0.0010,0.0010,0.0010,0.0010,0.0010,0.0010},
193  {0.0030,0.0030,0.0030,0.0040,0.0050,0.0050,0.0050,0.0100,0.1000,0.0500,0.2000,0.5000,0.7700,0.8700,0.9700},
194  {0.0070,0.0070,0.0070,0.0080,0.0090,0.0100,0.0100,0.0700,0.4000,0.4300,0.5800,0.7900,0.9300,0.9600,1.0000},
195  {0.0090,0.0090,0.0090,0.0120,0.0150,0.0100,0.0200,0.2800,0.6000,0.6400,0.7500,0.9100,0.9700,0.9800,1.0000},
196  {0.0140,0.0140,0.0140,0.0150,0.0160,0.0300,0.0600,0.5000,0.7000,0.7700,0.8400,0.9500,0.9700,1.0000,1.0000},
197  {0.0170,0.0170,0.0170,0.0200,0.0220,0.0600,0.1000,0.6200,0.7800,0.8400,0.8800,0.9500,1.0000,1.0000,1.0000},
198  {0.0300,0.0300,0.0240,0.0220,0.0320,0.0620,0.2000,0.6800,0.8300,0.8700,0.9000,0.9500,1.0000,1.0000,1.0000},
199  {0.0250,0.0250,0.0250,0.0360,0.0430,0.1300,0.2700,0.7400,0.8600,0.8900,0.9200,1.0000,1.0000,1.0000,1.0000},
200  {0.0270,0.0270,0.0270,0.0400,0.0520,0.2000,0.4000,0.7800,0.8800,0.9000,0.9400,1.0000,1.0000,1.0000,1.0000},
201  {0.0300,0.0300,0.0300,0.0470,0.0640,0.2500,0.5000,0.8000,0.9000,0.9100,0.9500,1.0000,1.0000,1.0000,1.0000},
202  {0.0400,0.0400,0.0330,0.0370,0.0680,0.2400,0.5500,0.8000,0.9000,0.9100,0.9500,1.0000,1.0000,1.0000,1.0000},
203  {0.0350,0.0350,0.0350,0.0550,0.0790,0.2900,0.5800,0.8000,0.9000,0.9100,0.9500,1.0000,1.0000,1.0000,1.0000},
204  {0.0370,0.0370,0.0370,0.0620,0.0820,0.2900,0.5900,0.7800,0.9000,0.9100,0.9500,1.0000,1.0000,1.0000,1.0000},
205  {0.0370,0.0370,0.0370,0.0600,0.0800,0.2900,0.5800,0.7700,0.8900,0.9100,0.9500,1.0000,1.0000,1.0000,1.0000},
206  {0.0370,0.0370,0.0370,0.0410,0.0750,0.2500,0.5400,0.7600,0.8800,0.9200,0.9500,1.0000,1.0000,1.0000,1.0000},
207  {0.0370,0.0370,0.0370,0.0520,0.0670,0.2500,0.5100,0.7700,0.8800,0.9300,0.9700,1.0000,1.0000,1.0000,1.0000},
208  {0.0370,0.0370,0.0370,0.0470,0.0570,0.2500,0.4900,0.7700,0.8900,0.9500,1.0000,1.0000,1.0000,1.0000,1.0000},
209  {0.0360,0.0360,0.0360,0.0420,0.0480,0.2300,0.4700,0.7800,0.9200,1.0000,1.0200,1.0200,1.0200,1.0200,1.0200},
210  {0.0400,0.0400,0.0350,0.0330,0.0400,0.1120,0.4500,0.7900,1.0100,1.0300,1.0400,1.0400,1.0400,1.0400,1.0400},
211  {0.0330,0.0330,0.0330,0.0330,0.0330,0.1190,0.4700,0.9500,1.3000,1.7000,2.3000,2.3000,2.3000,2.3000,2.3000},
212  {0.0270,0.0270,0.0270,0.0270,0.0270,0.1250,0.5200,1.4000,2.3000,3.0000,4.0000,4.0000,4.0000,4.0000,4.0000} };
213  return ecoll[a_i][a_j];
214  }
215 
216 
217  /* The following code is adapted from SCALE-SDM:
218  * Copyright (c) 2012-2014, Team SCALE, All rights reserved. */
219  /*! \brief Hall's kernel */
220  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
221  RT Halls ( const RT a_r1, /*!< radius of particle 1 */
222  const RT a_r2, /*!< radius of particle 2 */
223  const RT* const a_v1, /*!< velocity of particle 1 */
224  const RT* const a_v2 /*!< velocity of particle 2 */) const noexcept
225  {
226  auto r1 = std::max( a_r1, a_r2 ); // large
227  auto r2 = std::min( a_r1, a_r2 ); // small
228  auto ratio = r2 / r1;
229  auto sumr = r1 + r2;
230 
231  int irr = Halls_data_r0col(r1);
232  int iqq = Halls_data_ratcol(ratio);
233 
234  RT c_rate = 1.0;
235 
236  if( irr >= 15 ) {
237 
238  RT q = ( ratio - Halls_data_ratcol(iqq-1) )
239  / ( Halls_data_ratcol(iqq) - Halls_data_ratcol(iqq-1) );
240 
241  RT ek = (1.0-q) * Halls_data_ecoll(iqq-1,14)
242  + q * Halls_data_ecoll(iqq ,14);
243 
244  c_rate = std::min( ek, 1.0 );
245 
246  } else if ((irr >= 1) && (irr < 15)) {
247 
248  RT p = ( r1*1.0e6 - Halls_data_r0col(irr-1) )
249  / ( Halls_data_r0col(irr) - Halls_data_r0col(irr-1) );
250 
251  RT q = ( ratio - Halls_data_ratcol(iqq-1) )
252  / ( Halls_data_ratcol(iqq) - Halls_data_ratcol(iqq-1) );
253 
254  c_rate = (1.0-p) * (1.0-q) * Halls_data_ecoll(iqq-1,irr-1)
255  + p * (1.0-q) * Halls_data_ecoll(iqq-1,irr )
256  + (1.0-p) * q * Halls_data_ecoll(iqq ,irr-1)
257  + p * q * Halls_data_ecoll(iqq ,irr );
258 
259  } else {
260 
261  RT q = ( ratio - Halls_data_ratcol(iqq-1) )
262  / ( Halls_data_ratcol(iqq) - Halls_data_ratcol(iqq-1) );
263 
264  c_rate = (1.0-q) * Halls_data_ecoll(iqq-1,0)
265  + q * Halls_data_ecoll(iqq ,0);
266 
267  }
268  c_rate *= (PI*sumr*sumr);
269 
270  RT dv = 0;
271  for (int d = 0; d < SPACEDIM; d++) {
272  dv += (a_v1[d]-a_v2[d])*(a_v1[d]-a_v2[d]);
273  }
274  dv = std::sqrt(dv);
275 
276  return c_rate*dv;
277  }
278 
279  /* The following code is adapted from SCALE-SDM:
280  * Copyright (c) 2012-2014, Team SCALE, All rights reserved. */
281  /*! \brief Brownian kernel */
282  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
283  RT Brownian_SeinfeldPandis ( const RT a_r1, /*!< radius of particle 1 */
284  const RT a_r2, /*!< radius of particle 2 */
285  const RT a_mass_1, /*!< total mass of particle 1 */
286  const RT a_mass_2, /*!< total mass of particle 2 */
287  const RT a_pressure, /*!< pressure */
288  const RT a_temperature /*!< temperature */ ) const noexcept
289  {
290  // diameter of droplets
291  RT diameter_1 = 2*a_r1;
292  RT diameter_2 = 2*a_r2;
293 
294  RT p_crs = a_pressure; // [Pa]
295  RT t_crs = a_temperature; // [K]
296 
297  // dynamic viscosity [Pa*s] (Pruppacher & Klett,1997)
298  RT T_degC = t_crs - 273.15; // [K] => [degC]
299  RT vis_crs = 0.0;
300  if( T_degC >= 0.0 ) {
301  vis_crs = ( 1.7180 + 4.9E-3*T_degC ) * 1.E-5;
302  } else {
303  vis_crs = ( 1.7180 + 4.9E-3*T_degC -1.2E-5*T_degC*T_degC ) * 1.E-5;
304  }
305 
306  //air mean free path [m]
307  RT lambda_crs = (2.0*vis_crs) / (p_crs*std::sqrt(8.0*mwdair*1.E-3/(PI*R_d*t_crs)));
308 
309  // temporary var
310  RT dtmp = 0.0;
311 
312  // slip correction of droplets [-]
313  dtmp = 1.2570 + 0.40 * exp(-0.550*diameter_1/lambda_crs);
314  RT slip_corr_1 = 1.0 + (2.0*lambda_crs*dtmp)/(diameter_1);
315  dtmp = 1.2570 + 0.40 * exp(-0.550*diameter_2/lambda_crs);
316  RT slip_corr_2 = 1.0 + (2.0*lambda_crs*dtmp)/(diameter_2);
317 
318  // diffusion term [m*m/s]
319  dtmp = (boltz*t_crs)/(3.0*PI*vis_crs);
320  RT diff_term_1 = dtmp * (slip_corr_1/diameter_1);
321  RT diff_term_2 = dtmp * (slip_corr_2/diameter_2);
322 
323  // velocity term [m/s]
324  dtmp = (8.0*boltz*t_crs)/PI;
325  RT vel_term_1 = std::sqrt(dtmp/a_mass_1);
326  RT vel_term_2 = std::sqrt(dtmp/a_mass_2);
327 
328  // mean free path of droplets [m]
329  dtmp = 8.0/PI;
330  RT lambda_1 = dtmp * (diff_term_1/vel_term_1);
331  RT lambda_2 = dtmp * (diff_term_2/vel_term_2);
332 
333  //length term [m]
334  dtmp = (diameter_1+lambda_1)*(diameter_1+lambda_1)*(diameter_1+lambda_1)
335  - std::exp(1.5*std::log(diameter_1*diameter_1+lambda_1*lambda_1));
336  RT length_term_1 = dtmp/(3.0*diameter_1*lambda_1) - diameter_1;
337  dtmp = (diameter_2+lambda_2)*(diameter_2+lambda_2)*(diameter_2+lambda_2)
338  - std::exp(1.5*std::log(diameter_2*diameter_2+lambda_2*lambda_2));
339  RT length_term_2 = dtmp/(3.0*diameter_2*lambda_2) - diameter_2;
340 
341  // Brownian Coagulation Coefficient K12 [m3/s]
342  RT sumdia = diameter_1 + diameter_2;
343  RT sumd = diff_term_1 + diff_term_2;
344  RT sumc = std::sqrt( vel_term_1*vel_term_1 + vel_term_2*vel_term_2 );
345  RT sumg = std::sqrt( 2.0*length_term_1*length_term_1 + 2.0*length_term_2*length_term_2 );
346 
347  dtmp = sumdia/(sumdia+2.0*sumg) + (8.0*sumd)/(sumdia*sumc);
348  RT k12 = 2.0*PI * sumdia*sumd/dtmp;
349 
350  return k12;
351  }
352 
353 };
354 
355 #endif
356 #endif
357 
constexpr amrex::Real mwdair
Definition: ERF_Constants.H:64
constexpr amrex::Real boltz
Definition: ERF_Constants.H:62
constexpr amrex::Real PI
Definition: ERF_Constants.H:6
constexpr amrex::Real R_d
Definition: ERF_Constants.H:10