ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_ModalAeroWaterUpdate.H
Go to the documentation of this file.
1 
2 // RCE 07.04.13: Adapted from MIRAGE2 code / E3SM
3 #ifndef ERF_MODAL_AERO_WATERUPTAKE_H_
4 #define ERF_MODAL_AERO_WATERUPTAKE_H_
5 
6 #include <string>
7 #include <vector>
8 #include <memory>
9 #include <AMReX_GpuComplex.H>
10 #include "ERF_Constants.H"
11 #include "ERF_AeroRadProps.H"
12 #include "ERF_CloudRadProps.H"
13 #include "ERF_Mam4Constituents.H"
14 
15 using yakl::fortran::parallel_for;
16 using yakl::fortran::SimpleBounds;
17 
19  public:
20  constexpr static real third = 1./3.;
21  constexpr static real pi43 = PI*4.0/3.0;
22  constexpr static real huge_real = std::numeric_limits<real>::max();
23  constexpr static int imax = 200;
24  public:
25  static
26  void modal_aero_wateruptake_dr (int list_idx, int ncol, int nlev, int nmodes, int top_lev,
27  MamConstituents& consti, const real2d& h2ommr, const real2d& t, const real2d& pmid,
28  const real3d& dgnumdry_m, const real3d& dgnumwet_m, const real3d& qaerwat_m,
29  const real3d& wetdens_m, const real2d& clear_rh_in)
30  {
31  real2d raer("raer",ncol,nlev); // aerosol species MRs (kg/kg and #/kg)
32  real2d cldn("cldn",ncol,nlev); // layer cloud fraction (0-1)
33  real3d dgncur_a("dgncur",ncol,nlev,nmodes);
34  real3d dgncur_awet("dgncur_awet",ncol,nlev,nmodes);
35  real3d wetdens("wetdens",ncol,nlev,nmodes);
36  real3d qaerwat("qaerwat",ncol,nlev,nmodes);
37 
38  real2d dryvolmr("dryvolmr",ncol,nlev); //volume MR for aerosol mode (m3/kg)
39  real specdens;
40  real spechygro, spechygro_1;
41  real duma, dumb;
42  real sigmag;
43  real alnsg;
44  real v2ncur_a;
45  real drydens; // dry particle density (kg/m^3)
46  real2d rh("rh",ncol,nlev); // relative humidity (0-1)
47 
48  real1d es("es",ncol); // saturation vapor pressure
49  real1d qs("qs",ncol); // saturation specific humidity
50  real2d aerosol_water("aerosol_water",ncol,nlev); //sum of aerosol water (wat_a1 + wat_a2 + wat_a3 + wat_a4)
51  bool compute_wetdens;
52 
53  std::string trnum; // used to hold mode number (as characters)
54 
55  real3d naer("naer",ncol,nlev,nmodes); // aerosol number MR (bounded!) (#/kg-air)
56  real3d dryvol("dryvol",ncol,nlev,nmodes); // single-particle-mean dry volume (m3)
57  real3d drymass("drymass",ncol,nlev,nmodes); // single-particle-mean dry mass (kg)
58  real3d dryrad("dryrad",ncol,nlev,nmodes); // dry volume mean radius of aerosol (m)
59  real3d wetrad("wetrad",ncol,nlev,nmodes); // wet radius of aerosol (m)
60  real3d wetvol("wetvol",ncol,nlev,nmodes); // single-particle-mean wet volume (m3)
61  real3d wtrvol("wtrvol",ncol,nlev,nmodes); // single-particle-mean water volume in wet aerosol (m3)
62  real1d rhcrystal("rhcrystal",nmodes);
63  real1d rhdeliques("rhdeliques",nmodes);
64  real1d specdens_1("specdens_1",nmodes);
65  real3d maer("maer",ncol,nlev,nmodes); // aerosol wet mass MR (including water) (kg/kg-air)
66  real3d hygro("hygro",ncol,nlev,nmodes); // volume-weighted mean hygroscopicity (--)
67 
68  yakl::memset(naer, huge_real);
69  yakl::memset(dryvol, huge_real);
70  yakl::memset(drymass, huge_real);
71  yakl::memset(dryrad, huge_real);
72  yakl::memset(wetrad, huge_real);
73  yakl::memset(wetvol, huge_real);
74  yakl::memset(wtrvol, huge_real);
75 
76  yakl::memset(rhcrystal, huge_real);
77  yakl::memset(rhdeliques, huge_real);
78  yakl::memset(specdens_1, huge_real);
79 
80  yakl::memset(maer, 0.0);
81  yakl::memset(hygro, 0.);
82 
83  //by default set compute_wetdens to be true
84  compute_wetdens = true;
85 
86  dgncur_a = dgnumdry_m;
87  dgncur_awet = dgnumwet_m;
88  qaerwat = qaerwat_m;
89  wetdens = wetdens_m;
90 
91  // retrieve aerosol properties
92  for (auto m = 1; m <= nmodes; ++m) {
93  yakl::memset(dryvolmr, 0.);
94 
95  // get mode properties
96  consti.get_mode_props(list_idx, m-1, sigmag, rhcrystal(m), rhdeliques(m));
97 
98  // get mode species
99  int nspec;
100  consti.get_mode_nspec(list_idx, m-1, nspec);
101  for(auto l = 0; l < nspec; ++l) {
102  // get species interstitial mixing ratio ('a')
103  consti.rad_cnst_get_mam_mmr_by_idx(list_idx, m-1, l, "a", raer);
104  consti.get_mam_density_aer(list_idx, m-1, l, specdens);
105  consti.get_mam_hygro_aer(list_idx, m-1, l, spechygro);
106 
107  if (l == 1) {
108  // save off these values to be used as defaults
109  specdens_1(m) = specdens;
110  spechygro_1 = spechygro;
111  }
112 
113  top_lev = 1;
114  for(auto k = top_lev; k <= nlev; ++k) {
115  for(auto i = 1; i <= ncol; ++i) {
116  duma = raer(i,k);
117  maer(i,k,m) = maer(i,k,m) + duma;
118  dumb = duma/specdens;
119  dryvolmr(i,k) = dryvolmr(i,k) + dumb;
120  hygro(i,k,m) = hygro(i,k,m) + dumb*spechygro;
121  } // i = 1, ncol
122  } // k = top_lev, pver
123  } // l = 1, nspec
124 
125  alnsg = log(sigmag);
126  top_lev = 1;
127  for(auto k = top_lev; k <= nlev; ++k) {
128  for(auto i = 1; i <= ncol; ++i) {
129 
130  if (dryvolmr(i,k) > 1.0e-30) {
131  hygro(i,k,m) = hygro(i,k,m)/dryvolmr(i,k);
132  } else {
133  hygro(i,k,m) = spechygro_1;
134  }
135 
136  // dry aerosol properties
137  v2ncur_a = 1. / ( (PI/6.)*(std::pow(dgncur_a(i,k,m),3.))*std::exp(4.5*std::pow(alnsg, 2.)) );
138  // naer = aerosol number (#/kg)
139  naer(i,k,m) = dryvolmr(i,k)*v2ncur_a;
140 
141  // compute mean (1 particle) dry volume and mass for each mode
142  // old coding is replaced because the new (1/v2ncur_a) is equal to
143  // the mean particle volume
144  // also moletomass forces maer >= 1.0e-30, so (maer/dryvolmr)
145  // should never cause problems (but check for maer < 1.0e-31 anyway)
146  if (maer(i,k,m) > 1.0e-31) {
147  drydens = maer(i,k,m)/dryvolmr(i,k);
148  } else {
149  drydens = 1.0;
150  }
151 
152  dryvol(i,k,m) = 1.0/v2ncur_a;
153  drymass(i,k,m) = drydens*dryvol(i,k,m);
154  dryrad(i,k,m) = std::pow((dryvol(i,k,m)/pi43), third);
155  } // i = 1, ncol
156  } // k = top_lev, pver
157  } // modes
158 
159  // specify clear air relative humidity
160  bool has_clear_rh = false;
161  if (has_clear_rh) {
162  // use input relative humidity
163  // check that values are reasonable and apply upper limit
164  for(auto k = top_lev; k <= nlev; --k) {
165  for(auto i = 1; i <= ncol; ++i) {
166  rh(i,k) = clear_rh_in(i,k);
167  if ( rh(i,k) < 0 ) {
168  amrex::Print() << "modal_aero_wateruptake_dr: clear_rh_in is negative - rh:" << rh(i,k);
169  exit(EXIT_FAILURE);
170  }
171  // limit RH to 98% to be consistent with behavior when clear_rh_in is not provided
172  rh(i,k) = std::min(rh(i,k), 0.98);
173  } // i
174  }
175  } else {
176  // estimate clear air relative humidity using cloud fraction
177  for(auto k = top_lev; k <= nlev; ++k) {
178  for(auto i = 1; i <= ncol; ++i) {
179  WaterVaporSat::qsat(t(i,k), pmid(i,k), es(i), qs(i));
180  if (qs(i) > h2ommr(i,k)) {
181  rh(i,k) = h2ommr(i,k)/qs(i);
182  } else {
183  rh(i,k) = 0.98;
184  }
185 
186  rh(i,k) = std::max(rh(i,k), 0.0);
187  rh(i,k) = std::min(rh(i,k), 0.98);
188  if (cldn(i,k) < 0.998) {
189  rh(i,k) = (rh(i,k) - cldn(i,k)) / (1.0 - cldn(i,k));
190  }
191  rh(i,k) = std::max(rh(i,k), 0.0);
192  } // i = 1, ncol
193  } // k = top_lev, pver
194  }
195 
196  // compute aerosol wet radius and aerosol water
197  modal_aero_wateruptake_sub(ncol, nlev, nmodes, top_lev,
198  rhcrystal, rhdeliques, dryrad,
199  hygro, rh, dryvol,
200  wetrad, wetvol, wtrvol);
201 
202  for(auto m = 1; m <= nmodes; ++m) {
203  for(auto k = top_lev; k <= nlev; ++k) {
204  for(auto i = 1; i <= ncol; ++i) {
205 
206  dgncur_awet(i,k,m) = dgncur_a(i,k,m) * (wetrad(i,k,m)/dryrad(i,k,m));
207  qaerwat(i,k,m) = rhoh2o*naer(i,k,m)*wtrvol(i,k,m);
208 
209  // compute aerosol wet density (kg/m3)
210  if(compute_wetdens) {
211  if (wetvol(i,k,m) > 1.0e-30) {
212  wetdens(i,k,m) = (drymass(i,k,m) + rhoh2o*wtrvol(i,k,m))/wetvol(i,k,m);
213  }
214  else {
215  wetdens(i,k,m) = specdens_1(m);
216  }
217  }
218  } //i = 1, ncol
219  } // k = top_lev, pver
220  } // m = 1, nmodes
221  }
222 
223  // Purpose: Compute aerosol wet radius
224  static
225  void modal_aero_wateruptake_sub (int ncol, int nlev, int nmodes, int top_lev,
226  const real1d& rhcrystal, const real1d& rhdeliques, const real3d& dryrad,
227  const real3d& hygro, const real2d& rh, const real3d& dryvol,
228  const real3d& wetrad, const real3d& wetvol, const real3d& wtrvol)
229  {
230  // loop over all aerosol modes
231  for(auto m = 1; m <= nmodes; ++m) {
232  real hystfac = 1.0 / std::max(1.0e-5, (rhdeliques(m) - rhcrystal(m)));
233  for(auto k = top_lev; k <= nlev; ++k) {
234  for(auto i = 1; i <= ncol; ++i) {
235 
236  // compute wet radius for each mode
237  modal_aero_kohler(dryrad(i,k,m), hygro(i,k,m), rh(i,k), wetrad(i,k,m));
238 
239  wetrad(i,k,m) = std::max(wetrad(i,k,m), dryrad(i,k,m));
240  wetvol(i,k,m) = pi43*std::pow(wetrad(i,k,m), 3.);
241  wetvol(i,k,m) = std::max(wetvol(i,k,m), dryvol(i,k,m));
242  wtrvol(i,k,m) = wetvol(i,k,m) - dryvol(i,k,m);
243  wtrvol(i,k,m) = std::max(wtrvol(i,k,m), 0.0);
244 
245  // apply simple treatment of deliquesence/crystallization hysteresis
246  // for rhcrystal < rh < rhdeliques, aerosol water is a fraction of
247  // the "upper curve" value, and the fraction is a linear function of rh
248  if (rh(i,k) < rhcrystal(m)) {
249  wetrad(i,k,m) = dryrad(i,k,m);
250  wetvol(i,k,m) = dryvol(i,k,m);
251  wtrvol(i,k,m) = 0.0;
252  }
253  else if (rh(i,k) < rhdeliques(m)) {
254  wtrvol(i,k,m) = wtrvol(i,k,m)*hystfac*(rh(i,k) - rhcrystal(m));
255  wtrvol(i,k,m) = std::max(wtrvol(i,k,m), 0.0);
256  wetvol(i,k,m) = dryvol(i,k,m) + wtrvol(i,k,m);
257  wetrad(i,k,m) = std::pow(wetvol(i,k,m)/pi43, 1./3.);
258  }
259  } // columns
260  } // levels
261  } // modes
262  }
263 
264  // calculates equilibrium radius r of haze droplets as function of
265  // dry particle mass and relative humidity s using kohler solution
266  // given in pruppacher and klett (eqn 6-35)
267 
268  // for multiple aerosol types, assumes an internal mixture of aerosols
269  YAKL_INLINE
270  static void modal_aero_kohler (const real& rdry_in,
271  const real& hygro, const real& s,
272  real& rwet_out)
273  {
274  const real eps = 1.e-4;
275  const real mw = 18.;
276  const real rhow = 1.;
277  const real surften = 76.;
278  const real tair = 273.;
279  const real third = 1./3.;
280  const real ugascon = 8.3e7;
281 
282  //effect of organics on surface tension is neglected
283  auto a=2.e4*mw*surften/(ugascon*tair*rhow);
284 
285  auto rdry = rdry_in*1.0e6; // convert (m) to (microns)
286  auto vol = std::pow(rdry, 3.); // vol is r**3, not volume
287  auto b = vol*hygro;
288 
289  //quartic
290  auto ss = std::max(std::min(s,1.-eps), 1.e-10); // relative humidity
291  auto slog = std::log(ss); // log relative humidity
292  auto p43 = -a/slog;
293  auto p42 = 0.;
294  auto p41 = b/slog-vol;
295  auto p40 = a*vol/slog;
296 
297  // cubic for rh=1
298  auto p32 = 0.;
299  auto p31 = -b/a;
300  auto p30 = -vol;
301 
302  real r, r3, r4;
303  if(vol <= 1.e-12) {
304  r=rdry;
305  return;
306  }
307 
308  auto p = std::abs(p31)/(rdry*rdry);
309  if(p < eps) {
310  //approximate solution for small particles
311  r=rdry*(1.+p*third/(1.-slog*rdry/a));
312  }
313  else {
314  amrex::GpuComplex<real> cx4[4];
315  makoh_quartic(cx4,p43,p42,p41,p40);
316  //find smallest real(r8) solution
317  r = 1000.*rdry;
318  int nsol = -1;
319  for(int n=0; n<4; ++n) {
320  auto xr=cx4[n].real();
321  auto xi=cx4[n].imag();
322  if(abs(xi) > abs(xr)*eps) continue;
323  if(xr > r) continue;
324  if(xr < rdry*(1.-eps)) continue;
325  if(xr != xr) continue;
326  r=xr;
327  nsol=n;
328  }
329  if(nsol == -1) {
330  amrex::Print() << "kohlerc: no real solution found(quartic), nsol= " << nsol << "\n";
331  r=rdry;
332  }
333  }
334 
335  if(s > 1.-eps) {
336  // save quartic solution at s=1-eps
337  r4=r;
338  // cubic for rh=1
339  p=abs(p31)/(rdry*rdry);
340  if(p < eps) {
341  r=rdry*(1.+p*third);
342  }
343  else {
344  amrex::GpuComplex<real> cx3[3];
345  makoh_cubic(cx3,p32,p31,p30);
346  //find smallest real(r8) solution
347  r=1000.*rdry;
348  int nsol = -1;
349  for(auto n=0; n<3; ++n) {
350  auto xr = cx3[n].real();
351  auto xi = cx3[n].imag();
352  if(abs(xi) > abs(xr)*eps) continue;
353  if(xr > r) continue;
354  if(xr < rdry*(1.-eps)) continue;
355  if(xr != xr) continue;
356  r=xr;
357  nsol=n;
358  }
359  if(nsol == -1) {
360  amrex::Print() << "kohlerc: no real solution found (cubic), nsol= " << nsol << "\n";
361  r=rdry;
362  }
363  }
364  r3=r;
365  //now interpolate between quartic, cubic solutions
366  r=(r4*(1.-s)+r3*(s-1.+eps))/eps;
367  }
368  // bound and convert from microns to m
369  r = std::min(r,30.); // upper bound based on 1 day lifetime
370  rwet_out = r*1.e-6;
371  }
372 
373  // solves x**3 + p2 x**2 + p1 x + p0 = 0
374  // where p0, p1, p2 are real
375  static
376  void makoh_cubic (amrex::GpuComplex<real> cx[],
377  const real& p2,
378  const real& p1,
379  const real& p0)
380  {
381  const real eps = 1.0e-20;
382  const real third=1./3.;
383 
384  auto ci = amrex::GpuComplex<real>(0., 1.);
385  auto sqrt3 = amrex::GpuComplex<real>(std::sqrt(3.), 0.);
386  auto cw = 0.5*(-1.+ci*sqrt3);
387  auto cwsq = 0.5*(-1.-ci*sqrt3);
388 
389  if(p1 == 0.) {
390  // completely insoluble particle
391  cx[0] = amrex::GpuComplex<real>(std::pow(-p0, third), 0.);
392  cx[1] = cx[0];
393  cx[2] = cx[1];
394  }
395  else {
396  auto q = amrex::GpuComplex<real>(p1/3., 0.);
397  auto r = amrex::GpuComplex<real>(p0/2., 0.);
398  auto crad = r*r+q*q*q;
399  crad = amrex::sqrt(crad);
400 
401  auto cy = r-crad;
402  if (amrex::abs(cy) > eps) cy = amrex::pow(cy, third);
403  auto cq = q;
404  auto cz = -cq/cy;
405 
406  cx[0] = -cy-cz;
407  cx[1] = -cw*cy-cwsq*cz;
408  cx[2] = -cwsq*cy-cw*cz;
409  }
410  }
411 
412  // solves x**4 + p3 x**3 + p2 x**2 + p1 x + p0 = 0
413  // where p0, p1, p2, p3 are real
414  static
415  void makoh_quartic (amrex::GpuComplex<real> cx[],
416  const real& p3,
417  const real& p2,
418  const real& p1,
419  const real& p0)
420  {
421  const real third=1./3.;
422  auto q = -p2*p2/36.+(p3*p1-4*p0)/12.;
423  auto r = -std::pow((p2/6), 3.0) + p2*(p3*p1-4.*p0)/48.
424  + (4.*p0*p2-p0*p3*p3-p1*p1)/16.;
425 
426  auto crad = amrex::sqrt(amrex::GpuComplex<real>(r*r+q*q*q, 0.));
427  auto cb = r-crad;
428 
429  if (cb.real() == 0. && cb.imag() == 0.) {
430  // insoluble particle
431  cx[0] = amrex::pow(amrex::GpuComplex<real>(-p1, 0.0), third);
432  cx[1] = cx[0];
433  cx[2] = cx[0];
434  cx[3] = cx[0];
435  }
436  else {
437  cb = amrex::pow(cb, third);
438  auto cy = -cb+q/cb+p2/6.;
439  auto cb0 = amrex::sqrt(cy*cy-p0);
440  auto cb1 = (p3*cy-p1)/(2.*cb0);
441 
442  cb = p3/2.+cb1;
443  crad = cb*cb-4.*(cy+cb0);
444  crad = amrex::sqrt(crad);
445  cx[1] = (-cb+crad)/2.;
446  cx[2] = (-cb-crad)/2.;
447  cb = p3/2.-cb1;
448  crad = cb*cb-4.*(cy-cb0);
449  crad = amrex::sqrt(crad);
450  cx[3] = (-cb+crad)/2.;
451  cx[4] = (-cb-crad)/2.;
452  }
453  }
454 };
455 #endif
constexpr amrex::Real rhoh2o
Definition: ERF_Constants.H:94
constexpr amrex::Real PI
Definition: ERF_Constants.H:6
Definition: ERF_Mam4Constitutents.H:18
void get_mode_nspec(int list_idx, int m_idx, int &nspec) const
Definition: ERF_Mam4Constitutents.H:394
void rad_cnst_get_mam_mmr_by_idx(int list_idx, int mode_idx, int spec_idx, const std::string &phase, real2d &mmr) const
Definition: ERF_Mam4Constitutents.H:639
void get_mam_density_aer(int list_idx, int mode_idx, int spec_idx, real &density_aer) const
Definition: ERF_Mam4Constitutents.H:889
void get_mam_hygro_aer(int list_idx, int mode_idx, int spec_idx, real &hygro_aer) const
Definition: ERF_Mam4Constitutents.H:903
void get_mode_props(int list_idx, int mode_idx, real &sigmag, real &rhcrystal, real &rhdeliques) const
Definition: ERF_Mam4Constitutents.H:808
Definition: ERF_ModalAeroWaterUpdate.H:18
static void makoh_cubic(amrex::GpuComplex< real > cx[], const real &p2, const real &p1, const real &p0)
Definition: ERF_ModalAeroWaterUpdate.H:376
constexpr static real pi43
Definition: ERF_ModalAeroWaterUpdate.H:21
constexpr static int imax
Definition: ERF_ModalAeroWaterUpdate.H:23
static void makoh_quartic(amrex::GpuComplex< real > cx[], const real &p3, const real &p2, const real &p1, const real &p0)
Definition: ERF_ModalAeroWaterUpdate.H:415
constexpr static real third
Definition: ERF_ModalAeroWaterUpdate.H:20
constexpr static real huge_real
Definition: ERF_ModalAeroWaterUpdate.H:22
static void modal_aero_wateruptake_sub(int ncol, int nlev, int nmodes, int top_lev, const real1d &rhcrystal, const real1d &rhdeliques, const real3d &dryrad, const real3d &hygro, const real2d &rh, const real3d &dryvol, const real3d &wetrad, const real3d &wetvol, const real3d &wtrvol)
Definition: ERF_ModalAeroWaterUpdate.H:225
static void modal_aero_wateruptake_dr(int list_idx, int ncol, int nlev, int nmodes, int top_lev, MamConstituents &consti, const real2d &h2ommr, const real2d &t, const real2d &pmid, const real3d &dgnumdry_m, const real3d &dgnumwet_m, const real3d &qaerwat_m, const real3d &wetdens_m, const real2d &clear_rh_in)
Definition: ERF_ModalAeroWaterUpdate.H:26
static YAKL_INLINE void modal_aero_kohler(const real &rdry_in, const real &hygro, const real &s, real &rwet_out)
Definition: ERF_ModalAeroWaterUpdate.H:270
AMREX_GPU_HOST_DEVICE static AMREX_FORCE_INLINE void qsat(const amrex::Real &t, const amrex::Real &p, amrex::Real &es, amrex::Real &qs, amrex::Real *gam=nullptr, amrex::Real *dqsdt=nullptr, amrex::Real *enthalpy=nullptr)
Definition: ERF_WaterVaporSaturation.H:152