PartMC  2.6.1
aero_mode.F90
Go to the documentation of this file.
1 ! Copyright (C) 2005-2016 Nicole Riemer and Matthew West
2 ! Licensed under the GNU General Public License version 2 or (at your
3 ! option) any later version. See the file COPYING for details.
4 
5 !> \file
6 !> The pmc_aero_mode module.
7 
8 !> The aero_mode_t structure and associated subroutines.
10 
11  use pmc_bin_grid
12  use pmc_util
13  use pmc_constants
14  use pmc_spec_file
15  use pmc_aero_data
16  use pmc_aero_weight
17  use pmc_mpi
18  use pmc_rand
19 #ifdef PMC_USE_MPI
20  use mpi
21 #endif
22 
23  !> Maximum length of a mode name.
24  integer, parameter :: aero_mode_name_len = 300
25  !> Maximum length of a mode type.
26  integer, parameter :: aero_mode_type_len = 20
27 
28  !> Type code for an undefined or invalid mode.
29  integer, parameter :: aero_mode_type_invalid = 0
30  !> Type code for a log-normal mode.
31  integer, parameter :: aero_mode_type_log_normal = 1
32  !> Type code for an exponential mode.
33  integer, parameter :: aero_mode_type_exp = 2
34  !> Type code for a mono-disperse mode.
35  integer, parameter :: aero_mode_type_mono = 3
36  !> Type code for a sampled mode.
37  integer, parameter :: aero_mode_type_sampled = 4
38 
39  !> Type code for an undefined for invalid diameter type.
40  integer, parameter :: aero_mode_diam_type_invalid = 0
41  !> Type code for geometric diameter.
42  integer, parameter :: aero_mode_diam_type_geometric = 1
43  !> Type code for mobility equivalent diameter.
44  integer, parameter :: aero_mode_diam_type_mobility = 2
45 
46  !> An aerosol size distribution mode.
47  !!
48  !! Each mode is assumed to be fully internally mixed so that every
49  !! particle has the same composition. The composition is stored in
50  !! \c vol_frac, while the other parameters define the size
51  !! distribution (with \c type defining the type of size distribution
52  !! function). See \ref input_format_mass_frac for descriptions of
53  !! the parameters relevant to each mode type.
55  !> Mode name, used to track particle sources.
56  character(len=AERO_MODE_NAME_LEN) :: name
57  !> Mode type (given by module constants).
58  integer :: type
59  !> Characteristic radius, with meaning dependent on mode type (m).
60  real(kind=dp) :: char_radius
61  !> Log base 10 of geometric standard deviation of radius, (m).
62  real(kind=dp) :: log10_std_dev_radius
63  !> Sample bin radii [length <tt>(N + 1)</tt>] (m).
64  real(kind=dp), allocatable :: sample_radius(:)
65  !> Sample bin number concentrations [length <tt>N</tt>] (m^{-3}).
66  real(kind=dp), allocatable :: sample_num_conc(:)
67  !> Total number concentration of mode (#/m^3).
68  real(kind=dp) :: num_conc
69  !> Species fractions by volume [length \c aero_data_n_spec(aero_data)] (1).
70  real(kind=dp), allocatable :: vol_frac(:)
71  !> Species fraction standard deviation
72  !> [length \c aero_data_n_spec(aero_data)] (1).
73  real(kind=dp), allocatable :: vol_frac_std(:)
74  !> Source number.
75  integer :: source
76  end type aero_mode_t
77 
78 contains
79 
80 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
81 
82  !> Return a string representation of a kernel type.
83  character(len=AERO_MODE_TYPE_LEN) function aero_mode_type_to_string(type)
84 
85  !> Aero mode type.
86  integer, intent(in) :: type
87 
88  if (type == aero_mode_type_invalid) then
89  aero_mode_type_to_string = "invalid"
90  elseif (type == aero_mode_type_log_normal) then
91  aero_mode_type_to_string = "log_normal"
92  elseif (type == aero_mode_type_exp) then
94  elseif (type == aero_mode_type_mono) then
96  elseif (type == aero_mode_type_sampled) then
97  aero_mode_type_to_string = "sampled"
98  else
99  aero_mode_type_to_string = "unknown"
100  end if
101 
102  end function aero_mode_type_to_string
103 
104 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
105 
106  !> Returns the total number concentration of a mode. (#/m^3)
107  real(kind=dp) function aero_mode_total_num_conc(aero_mode)
108 
109  !> Aerosol mode.
110  type(aero_mode_t), intent(in) :: aero_mode
111 
113  if ((aero_mode%type == aero_mode_type_log_normal) &
114  .or. (aero_mode%type == aero_mode_type_exp) &
115  .or. (aero_mode%type == aero_mode_type_mono)) then
116  aero_mode_total_num_conc = aero_mode%num_conc
117  elseif (aero_mode%type == aero_mode_type_sampled) then
118  aero_mode_total_num_conc = sum(aero_mode%sample_num_conc)
119  else
120  call die_msg(719625922, "unknown aero_mode type: " &
121  // trim(integer_to_string(aero_mode%type)))
122  end if
123 
124  end function aero_mode_total_num_conc
125 
126 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
127 
128  !> Compute a log-normal distribution.
129  subroutine num_conc_log_normal(total_num_conc, geom_mean_radius, &
130  log10_sigma_g, bin_grid, num_conc)
131 
132  !> Total number concentration of the mode (m^{-3}).
133  real(kind=dp), intent(in) :: total_num_conc
134  !> Geometric mean radius (m).
135  real(kind=dp), intent(in) :: geom_mean_radius
136  !> log_10(geom. std. dev.) (1).
137  real(kind=dp), intent(in) :: log10_sigma_g
138  !> Bin grid.
139  type(bin_grid_t), intent(in) :: bin_grid
140  !> Number concentration (#(ln(r))d(ln(r))).
141  real(kind=dp), intent(out) :: num_conc(bin_grid_size(bin_grid))
142 
143  integer :: k
144 
145  do k = 1,bin_grid_size(bin_grid)
146  num_conc(k) = total_num_conc / (sqrt(2d0 * const%pi) &
147  * log10_sigma_g) * dexp(-(dlog10(bin_grid%centers(k)) &
148  - dlog10(geom_mean_radius))**2d0 &
149  / (2d0 * log10_sigma_g**2d0)) / dlog(10d0)
150  end do
151 
152  ! The formula above was originally for a distribution in
153  ! log_10(r), while we are using log_e(r) for our bin grid. The
154  ! division by dlog(10) at the end corrects for this.
155 
156  ! Remember that log_e(r) = log_10(r) * log_e(10).
157 
158  end subroutine num_conc_log_normal
159 
160 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
161 
162  !> Compute a log-normal distribution in volume.
163  subroutine vol_conc_log_normal(total_num_conc, &
164  geom_mean_radius, log10_sigma_g, bin_grid, aero_data, vol_conc)
165 
166  !> Total number concentration of the mode (m^{-3}).
167  real(kind=dp), intent(in) :: total_num_conc
168  !> Geometric mean radius (m).
169  real(kind=dp), intent(in) :: geom_mean_radius
170  !> log_10(geom. std. dev.) (1).
171  real(kind=dp), intent(in) :: log10_sigma_g
172  !> Bin grid.
173  type(bin_grid_t), intent(in) :: bin_grid
174  !> Aerosol data.
175  type(aero_data_t), intent(in) :: aero_data
176  !> Volume concentration (V(ln(r))d(ln(r))).
177  real(kind=dp), intent(out) :: vol_conc(bin_grid_size(bin_grid))
178 
179  real(kind=dp) :: num_conc(bin_grid_size(bin_grid))
180 
181  call num_conc_log_normal(total_num_conc, geom_mean_radius, &
182  log10_sigma_g, bin_grid, num_conc)
183 
184  vol_conc = num_conc * aero_data_rad2vol(aero_data, bin_grid%centers)
185 
186  end subroutine vol_conc_log_normal
187 
188 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
189 
190  !> Exponential distribution in volume.
191  !!
192  !! \f[ n(v) = \frac{1}{\rm mean-vol} \exp(- v / {\rm mean-vol}) \f]
193  !! Normalized so that sum(num_conc(k) * log_width) = 1
194  subroutine num_conc_exp(total_num_conc, radius_at_mean_vol, &
195  bin_grid, aero_data, num_conc)
196 
197  !> Total number concentration of the mode (m^{-3}).
198  real(kind=dp), intent(in) :: total_num_conc
199  !> Radius at mean volume (m).
200  real(kind=dp), intent(in) :: radius_at_mean_vol
201  !> Bin grid.
202  type(bin_grid_t), intent(in) :: bin_grid
203  !> Aerosol data.
204  type(aero_data_t), intent(in) :: aero_data
205  !> Number concentration (#(ln(r))d(ln(r))).
206  real(kind=dp), intent(out) :: num_conc(bin_grid_size(bin_grid))
207 
208  integer :: k
209  real(kind=dp) :: mean_vol, num_conc_vol
210 
211  mean_vol = aero_data_rad2vol(aero_data, radius_at_mean_vol)
212  do k = 1,bin_grid_size(bin_grid)
213  num_conc_vol = total_num_conc / mean_vol &
214  * exp(-(aero_data_rad2vol(aero_data, bin_grid%centers(k)) &
215  / mean_vol))
216  call vol_to_lnr(bin_grid%centers(k), num_conc_vol, num_conc(k))
217  end do
218 
219  end subroutine num_conc_exp
220 
221 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
222 
223  !> Exponential distribution in volume.
224  subroutine vol_conc_exp(total_num_conc, radius_at_mean_vol, &
225  bin_grid, aero_data, vol_conc)
226 
227  !> Total number concentration of the mode (m^{-3}).
228  real(kind=dp), intent(in) :: total_num_conc
229  !> Radius at mean volume (m).
230  real(kind=dp), intent(in) :: radius_at_mean_vol
231  !> Bin grid.
232  type(bin_grid_t), intent(in) :: bin_grid
233  !> Aerosol data.
234  type(aero_data_t), intent(in) :: aero_data
235  !> Volume concentration (V(ln(r))d(ln(r))).
236  real(kind=dp), intent(out) :: vol_conc(bin_grid_size(bin_grid))
237 
238  real(kind=dp) :: num_conc(bin_grid_size(bin_grid))
239 
240  call num_conc_exp(total_num_conc, radius_at_mean_vol, &
241  bin_grid, aero_data, num_conc)
242  vol_conc = num_conc * aero_data_rad2vol(aero_data, bin_grid%centers)
243 
244  end subroutine vol_conc_exp
245 
246 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
247 
248  !> Mono-disperse distribution.
249  !> Normalized so that sum(num_conc(k) * log_width) = 1
250  subroutine num_conc_mono(total_num_conc, radius, bin_grid, num_conc)
251 
252  !> Total number concentration of the mode (m^{-3}).
253  real(kind=dp), intent(in) :: total_num_conc
254  !> Radius of each particle (m^3).
255  real(kind=dp), intent(in) :: radius
256  !> Bin grid.
257  type(bin_grid_t), intent(in) :: bin_grid
258  !> Number concentration (#(ln(r))d(ln(r))).
259  real(kind=dp), intent(out) :: num_conc(bin_grid_size(bin_grid))
260 
261  integer :: k
262 
263  num_conc = 0d0
264  k = bin_grid_find(bin_grid, radius)
265  if ((k < 1) .or. (k > bin_grid_size(bin_grid))) then
266  call warn_msg(825666877, "monodisperse radius outside of bin_grid")
267  else
268  num_conc(k) = total_num_conc / bin_grid%widths(k)
269  end if
270 
271  end subroutine num_conc_mono
272 
273 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
274 
275  !> Mono-disperse distribution in volume.
276  subroutine vol_conc_mono(total_num_conc, radius, &
277  bin_grid, aero_data, vol_conc)
278 
279  !> Total number concentration of the mode (m^{-3}).
280  real(kind=dp), intent(in) :: total_num_conc
281  !> Radius of each particle (m^3).
282  real(kind=dp), intent(in) :: radius
283  !> Bin grid.
284  type(bin_grid_t), intent(in) :: bin_grid
285  !> Aerosol data.
286  type(aero_data_t), intent(in) :: aero_data
287  !> Volume concentration (V(ln(r))d(ln(r))).
288  real(kind=dp), intent(out) :: vol_conc(bin_grid_size(bin_grid))
289 
290  integer :: k
291 
292  vol_conc = 0d0
293  k = bin_grid_find(bin_grid, radius)
294  if ((k < 1) .or. (k > bin_grid_size(bin_grid))) then
295  call warn_msg(420930707, "monodisperse radius outside of bin_grid")
296  else
297  vol_conc(k) = total_num_conc / bin_grid%widths(k) &
298  * aero_data_rad2vol(aero_data, radius)
299  end if
300 
301  end subroutine vol_conc_mono
302 
303 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
304 
305  !> Sampled distribution, not normalized.
306  subroutine num_conc_sampled(sample_radius, sample_num_conc, bin_grid, &
307  num_conc)
308 
309  !> Sampled radius bin edges (m).
310  real(kind=dp), intent(in) :: sample_radius(:)
311  !> Sampled number concentrations (m^{-3}).
312  real(kind=dp), intent(in) :: sample_num_conc(:)
313  !> Bin grid.
314  type(bin_grid_t), intent(in) :: bin_grid
315  !> Number concentration (#(ln(r))d(ln(r))).
316  real(kind=dp), intent(out) :: num_conc(bin_grid_size(bin_grid))
317 
318  integer :: i_sample, n_sample, i_lower, i_upper, i_bin
319  real(kind=dp) :: r_lower, r_upper
320  real(kind=dp) :: r_bin_lower, r_bin_upper, r1, r2, ratio
321 
322  n_sample = size(sample_num_conc)
323  call assert(188766208, size(sample_radius) == n_sample + 1)
324  call assert(295384037, n_sample >= 1)
325 
326  num_conc = 0d0
327  do i_sample = 1,n_sample
328  r_lower = sample_radius(i_sample)
329  r_upper = sample_radius(i_sample + 1)
330  i_lower = bin_grid_find(bin_grid, r_lower)
331  i_upper = bin_grid_find(bin_grid, r_upper)
332  if (i_upper < 1) cycle
333  if (i_lower > bin_grid_size(bin_grid)) cycle
334  i_lower = max(1, i_lower)
335  i_upper = min(bin_grid_size(bin_grid), i_upper)
336  do i_bin = i_lower,i_upper
337  r_bin_lower = bin_grid%edges(i_bin)
338  r_bin_upper = bin_grid%edges(i_bin + 1)
339  r1 = max(r_lower, r_bin_lower)
340  r2 = min(r_upper, r_bin_upper)
341  ratio = (log(r2) - log(r1)) / (log(r_upper) - log(r_lower))
342  num_conc(i_bin) = num_conc(i_bin) + ratio &
343  * sample_num_conc(i_sample) / bin_grid%widths(i_bin)
344  end do
345  end do
346 
347  end subroutine num_conc_sampled
348 
349 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
350 
351  !> Sampled distribution in volume.
352  subroutine vol_conc_sampled(sample_radius, sample_num_conc, &
353  bin_grid, aero_data, vol_conc)
354 
355  !> Sampled radius bin edges (m).
356  real(kind=dp), intent(in) :: sample_radius(:)
357  !> Sampled number concentrations (m^{-3}).
358  real(kind=dp), intent(in) :: sample_num_conc(:)
359  !> Bin grid.
360  type(bin_grid_t), intent(in) :: bin_grid
361  !> Aerosol data.
362  type(aero_data_t), intent(in) :: aero_data
363  !> Volume concentration (V(ln(r))d(ln(r))).
364  real(kind=dp), intent(out) :: vol_conc(bin_grid_size(bin_grid))
365 
366  real(kind=dp) :: num_conc(bin_grid_size(bin_grid))
367 
368  call num_conc_sampled(sample_radius, sample_num_conc, bin_grid, num_conc)
369  vol_conc = num_conc * aero_data_rad2vol(aero_data, bin_grid%centers)
370 
371  end subroutine vol_conc_sampled
372 
373 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
374 
375  !> Return the binned number concentration for an aero_mode.
376  subroutine aero_mode_num_conc(aero_mode, bin_grid, aero_data, &
377  num_conc)
378 
379  !> Aero mode for which to compute number concentration.
380  type(aero_mode_t), intent(in) :: aero_mode
381  !> Bin grid.
382  type(bin_grid_t), intent(in) :: bin_grid
383  !> Aerosol data.
384  type(aero_data_t), intent(in) :: aero_data
385  !> Number concentration (#(ln(r))d(ln(r))).
386  real(kind=dp), intent(out) :: num_conc(bin_grid_size(bin_grid))
387 
388  if (aero_mode%type == aero_mode_type_log_normal) then
389  call num_conc_log_normal(aero_mode%num_conc, aero_mode%char_radius, &
390  aero_mode%log10_std_dev_radius, bin_grid, num_conc)
391  elseif (aero_mode%type == aero_mode_type_exp) then
392  call num_conc_exp(aero_mode%num_conc, &
393  aero_mode%char_radius, bin_grid, aero_data, num_conc)
394  elseif (aero_mode%type == aero_mode_type_mono) then
395  call num_conc_mono(aero_mode%num_conc, aero_mode%char_radius, &
396  bin_grid, num_conc)
397  elseif (aero_mode%type == aero_mode_type_sampled) then
398  call num_conc_sampled(aero_mode%sample_radius, &
399  aero_mode%sample_num_conc, bin_grid, num_conc)
400  else
401  call die_msg(223903246, "unknown aero_mode type: " &
402  // trim(integer_to_string(aero_mode%type)))
403  end if
404 
405  end subroutine aero_mode_num_conc
406 
407 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
408 
409  !> Return the binned per-species volume concentration for an
410  !> aero_mode.
411  subroutine aero_mode_vol_conc(aero_mode, bin_grid, aero_data, &
412  vol_conc)
413 
414  !> Aero mode for which to compute volume concentration.
415  type(aero_mode_t), intent(in) :: aero_mode
416  !> Bin grid.
417  type(bin_grid_t), intent(in) :: bin_grid
418  !> Aerosol data.
419  type(aero_data_t), intent(in) :: aero_data
420  !> Volume concentration (V(ln(r))d(ln(r))).
421  real(kind=dp), intent(out) :: vol_conc(bin_grid_size(bin_grid), &
422  aero_data_n_spec(aero_data))
423 
424  integer :: i_spec
425  real(kind=dp) :: vol_conc_total(bin_grid_size(bin_grid))
426 
427  if (aero_mode%type == aero_mode_type_log_normal) then
428  call vol_conc_log_normal(aero_mode%num_conc, &
429  aero_mode%char_radius, aero_mode%log10_std_dev_radius, &
430  bin_grid, aero_data, vol_conc_total)
431  elseif (aero_mode%type == aero_mode_type_exp) then
432  call vol_conc_exp(aero_mode%num_conc, &
433  aero_mode%char_radius, bin_grid, aero_data, vol_conc_total)
434  elseif (aero_mode%type == aero_mode_type_mono) then
435  call vol_conc_mono(aero_mode%num_conc, &
436  aero_mode%char_radius, bin_grid, aero_data, vol_conc_total)
437  elseif (aero_mode%type == aero_mode_type_sampled) then
438  call vol_conc_sampled(aero_mode%sample_radius, &
439  aero_mode%sample_num_conc, bin_grid, aero_data, vol_conc_total)
440  else
441  call die_msg(314169653, "Unknown aero_mode type: " &
442  // trim(integer_to_string(aero_mode%type)))
443  end if
444  call assert_msg(756593082, sum(aero_mode%vol_frac_std) == 0d0, &
445  "cannot convert species fractions with non-zero standard deviation " &
446  // "to binned distributions")
447  do i_spec = 1,aero_data_n_spec(aero_data)
448  vol_conc(:,i_spec) = vol_conc_total * aero_mode%vol_frac(i_spec)
449  end do
450 
451  end subroutine aero_mode_vol_conc
452 
453 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
454 
455  !> Compute weighted sampled number concentrations.
456  subroutine aero_mode_weighted_sampled_num_conc(aero_mode, aero_weight, &
457  weighted_num_conc)
458 
459  !> Aerosol mode.
460  type(aero_mode_t), intent(in) :: aero_mode
461  !> Aerosol weight.
462  type(aero_weight_t), intent(in) :: aero_weight
463  !> Weighted number concentration
464  real(kind=dp), intent(out) :: weighted_num_conc(:)
465 
466  integer :: i_sample
467  real(kind=dp) :: x0, x1
468 
469  call assert(256667423, aero_mode%type == aero_mode_type_sampled)
470  call assert(878731017, &
471  size(weighted_num_conc) == size(aero_mode%sample_num_conc))
472 
473  if (aero_weight%type == aero_weight_type_none) then
474  weighted_num_conc = aero_mode%sample_num_conc
475  elseif ((aero_weight%type == aero_weight_type_power) &
476  .or. (aero_weight%type == aero_weight_type_mfa)) then
477  do i_sample = 1,size(aero_mode%sample_num_conc)
478  x0 = log(aero_mode%sample_radius(i_sample))
479  x1 = log(aero_mode%sample_radius(i_sample + 1))
480  weighted_num_conc(i_sample) = aero_mode%sample_num_conc(i_sample) &
481  / aero_weight%exponent * (exp(- aero_weight%exponent * x0) &
482  - exp(- aero_weight%exponent * x1)) / (x1 - x0)
483  end do
484  else
485  call die_msg(576124393, "unknown aero_weight type: " &
486  // trim(integer_to_string(aero_weight%type)))
487  end if
488 
490 
491 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
492 
493  !> Return the total number of computational particles for an \c aero_mode.
494  real(kind=dp) function aero_mode_number(aero_mode, aero_weight)
495 
496  !> Aero_mode to sample radius from.
497  type(aero_mode_t), intent(in) :: aero_mode
498  !> Aero weight.
499  type(aero_weight_t), intent(in) :: aero_weight
500 
501  real(kind=dp) :: x_mean_prime
502  real(kind=dp), allocatable :: weighted_num_conc(:)
503 
504  aero_mode_number = 0d0
505  if (aero_mode%type == aero_mode_type_log_normal) then
506  if (aero_weight%type == aero_weight_type_none) then
507  aero_mode_number = aero_mode%num_conc / aero_weight%magnitude
508  elseif ((aero_weight%type == aero_weight_type_power) &
509  .or. (aero_weight%type == aero_weight_type_mfa)) then
510  x_mean_prime = log10(aero_mode%char_radius) &
511  - aero_weight%exponent * aero_mode%log10_std_dev_radius**2 &
512  * log(10d0)
513  aero_mode_number = aero_mode%num_conc / aero_weight%magnitude &
514  * exp((x_mean_prime**2 - log10(aero_mode%char_radius)**2) &
515  / (2d0 * aero_mode%log10_std_dev_radius**2))
516  else
517  call die_msg(466668240, "unknown aero_weight type: " &
518  // trim(integer_to_string(aero_weight%type)))
519  end if
520  elseif (aero_mode%type == aero_mode_type_exp) then
521  if (aero_weight%type == aero_weight_type_none) then
522  aero_mode_number = aero_mode%num_conc / aero_weight%magnitude
523  else
524  call die_msg(822252601, &
525  "cannot use exponential modes with weighting")
526  end if
527  elseif (aero_mode%type == aero_mode_type_mono) then
528  aero_mode_number = aero_mode%num_conc &
529  / aero_weight_num_conc_at_radius(aero_weight, &
530  aero_mode%char_radius)
531  elseif (aero_mode%type == aero_mode_type_sampled) then
532  allocate(weighted_num_conc(size(aero_mode%sample_num_conc)))
533  call aero_mode_weighted_sampled_num_conc(aero_mode, aero_weight, &
534  weighted_num_conc)
535  aero_mode_number = sum(weighted_num_conc) / aero_weight%magnitude
536  deallocate(weighted_num_conc)
537  else
538  call die_msg(901140225, "unknown aero_mode type: " &
539  // trim(integer_to_string(aero_mode%type)))
540  end if
541 
542  end function aero_mode_number
543 
544 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
545 
546  !> Return a radius randomly sampled from the mode distribution.
547  subroutine aero_mode_sample_radius(aero_mode, aero_data, aero_weight, &
548  radius)
549 
550  !> Aero_mode to sample radius from.
551  type(aero_mode_t), intent(in) :: aero_mode
552  !> Aerosol data.
553  type(aero_data_t), intent(in) :: aero_data
554  !> Aero weight.
555  type(aero_weight_t), intent(in) :: aero_weight
556  !> Sampled radius (m).
557  real(kind=dp), intent(out) :: radius
558 
559  real(kind=dp) :: x_mean_prime, x0, x1, x, r, inv_nc0, inv_nc1, inv_nc
560  integer :: i_sample
561  real(kind=dp), allocatable :: weighted_num_conc(:)
562 
563  if (aero_mode%type == aero_mode_type_log_normal) then
564  if (aero_weight%type == aero_weight_type_none) then
565  x_mean_prime = log10(aero_mode%char_radius)
566  elseif ((aero_weight%type == aero_weight_type_power) &
567  .or. (aero_weight%type == aero_weight_type_mfa)) then
568  x_mean_prime = log10(aero_mode%char_radius) &
569  - aero_weight%exponent * aero_mode%log10_std_dev_radius**2 &
570  * log(10d0)
571  else
572  call die_msg(517376844, "unknown aero_weight type: " &
573  // trim(integer_to_string(aero_weight%type)))
574  end if
575  radius = 10d0**rand_normal(x_mean_prime, &
576  aero_mode%log10_std_dev_radius)
577  elseif (aero_mode%type == aero_mode_type_sampled) then
578  allocate(weighted_num_conc(size(aero_mode%sample_num_conc)))
579  call aero_mode_weighted_sampled_num_conc(aero_mode, aero_weight, &
580  weighted_num_conc)
581  i_sample = sample_cts_pdf(weighted_num_conc)
582  deallocate(weighted_num_conc)
583  if ((aero_weight%type == aero_weight_type_none) &
584  .or. (((aero_weight%type == aero_weight_type_power) &
585  .or. (aero_weight%type == aero_weight_type_mfa)) &
586  .and. (aero_weight%exponent == 0d0))) then
587  x0 = log(aero_mode%sample_radius(i_sample))
588  x1 = log(aero_mode%sample_radius(i_sample + 1))
589  r = pmc_random()
590  x = (1d0 - r) * x0 + r * x1
591  radius = exp(x)
592  elseif ((aero_weight%type == aero_weight_type_power) &
593  .or. (aero_weight%type == aero_weight_type_mfa)) then
594  inv_nc0 = 1d0 / aero_weight_num_conc_at_radius(aero_weight, &
595  aero_mode%sample_radius(i_sample))
596  inv_nc1 = 1d0 / aero_weight_num_conc_at_radius(aero_weight, &
597  aero_mode%sample_radius(i_sample + 1))
598  r = pmc_random()
599  inv_nc = (1d0 - r) * inv_nc0 + r * inv_nc1
600  radius = aero_weight_radius_at_num_conc(aero_weight, 1d0 / inv_nc)
601  else
602  call die_msg(769131141, "unknown aero_weight type: " &
603  // trim(integer_to_string(aero_weight%type)))
604  end if
605  elseif (aero_mode%type == aero_mode_type_exp) then
606  if (aero_weight%type == aero_weight_type_none) then
607  radius = aero_data_vol2rad(aero_data, -aero_data_rad2vol(aero_data, &
608  aero_mode%char_radius) * log(pmc_random()))
609  elseif ((aero_weight%type == aero_weight_type_power) &
610  .or. (aero_weight%type == aero_weight_type_mfa)) then
611  call die_msg(678481276, &
612  "cannot use exponential modes with weighting")
613  else
614  call die_msg(301787712, "unknown aero_weight type: " &
615  // trim(integer_to_string(aero_weight%type)))
616  end if
617  elseif (aero_mode%type == aero_mode_type_mono) then
618  radius = aero_mode%char_radius
619  else
620  call die_msg(749122931, "Unknown aero_mode type: " &
621  // trim(integer_to_string(aero_mode%type)))
622  end if
623 
624  end subroutine aero_mode_sample_radius
625 
626 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
627 
628  !> Return an array of volumes randomly sampled from the volume fractions.
629  subroutine aero_mode_sample_vols(aero_mode, total_vol, vols)
630 
631  !> Aero_mode to sample from.
632  type(aero_mode_t), intent(in) :: aero_mode
633  !> Total volume (m^3).
634  real(kind=dp), intent(in) :: total_vol
635  !> Sampled volumes (m^3).
636  real(kind=dp), intent(out) :: vols(size(aero_mode%vol_frac))
637 
638  integer, parameter :: aero_mode_max_sample_loops = 1000000
639 
640  integer :: i_sample
641  real(kind=dp) :: offset
642 
643  ! sampling of volume fractions, normalized to sum to 1 by
644  ! projecting out the mean direction, and accepted if all
645  ! non-negative, otherwise rejected and repeated (accept-reject)
646  do i_sample = 1,aero_mode_max_sample_loops
647  call rand_normal_array_1d(aero_mode%vol_frac, aero_mode%vol_frac_std, &
648  vols)
649  ! add the correct amount of (offset * vol_frac) to vols to ensure
650  ! that sum(vols) is 1
651  offset = 1d0 - sum(vols)
652  vols = vols + offset * aero_mode%vol_frac
653  if (minval(vols) >= 0d0) exit
654  end do
655  if (i_sample == aero_mode_max_sample_loops) then
656  call die_msg(549015143, "Unable to sample non-negative volumes for " &
657  // "mode: " // trim(aero_mode%name))
658  end if
659  vols = vols / sum(vols) * total_vol
660 
661  end subroutine aero_mode_sample_vols
662 
663 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
664 
665  !> Read volume fractions from a data file.
666  subroutine spec_file_read_vol_frac(file, aero_data, vol_frac, vol_frac_std)
667 
668  !> Spec file to read mass fractions from.
669  type(spec_file_t), intent(inout) :: file
670  !> Aero_data data.
671  type(aero_data_t), intent(in) :: aero_data
672  !> Aerosol species volume fractions.
673  real(kind=dp), allocatable, intent(inout) :: vol_frac(:)
674  !> Aerosol species volume fraction standard deviations.
675  real(kind=dp), allocatable, intent(inout) :: vol_frac_std(:)
676 
677  integer :: n_species, species, i
678  character(len=SPEC_LINE_MAX_VAR_LEN), allocatable :: species_name(:)
679  real(kind=dp), allocatable :: species_data(:,:)
680  real(kind=dp) :: tot_vol_frac
681 
682  !> \page input_format_mass_frac Input File Format: Aerosol Mass Fractions
683  !!
684  !! An aerosol mass fractions file must consist of one line per
685  !! aerosol species, with each line having the species name
686  !! followed by the species mass fraction in each aerosol
687  !! particle. The valid species names are those specfied by the
688  !! \ref input_format_aero_data file, but not all species have to
689  !! be listed. Any missing species will have proportions of
690  !! zero. If the proportions do not sum to 1 then they will be
691  !! normalized before use. For example, a mass fractions file file
692  !! could contain:
693  !! <pre>
694  !! # species proportion
695  !! OC 0.3
696  !! BC 0.7
697  !! </pre>
698  !! indicating that the particles are 30% organic carbon and 70%
699  !! black carbon.
700  !!
701  !! Optionally, the standard deviation can also be provided for
702  !! each species as a second number on each line. For example,
703  !! <pre>
704  !! # species proportion std_dev
705  !! OC 0.3 0.1
706  !! BC 0.7 0.2
707  !! </pre>
708  !! indicates that the particles are on average 30% OC and 70% BC,
709  !! but may vary to have particles with 20% OC and 80% BC, or 40%
710  !! OC and 60% BC, for example. The standard deviations will be
711  !! normalized by the sum of the proportions.
712  !!
713  !! Either all species in a given file must have standard
714  !! deviations or none of them can.
715  !!
716  !! See also:
717  !! - \ref spec_file_format --- the input file text format
718  !! - \ref input_format_aero_dist --- the format for a complete
719  !! aerosol distribution with several modes
720  !! - \ref input_format_aero_mode --- the format for each mode
721  !! of an aerosol distribution
722 
723  ! read the aerosol data from the specified file
724  call spec_file_read_real_named_array(file, 0, species_name, &
725  species_data)
726 
727  ! check the data size
728  n_species = size(species_data, 1)
729  if (n_species < 1) then
730  call die_msg(628123166, 'file ' // trim(file%name) &
731  // ' must contain at least one line of data')
732  end if
733  if ((size(species_data, 2) /= 1) .and. (size(species_data, 2) /= 2)) then
734  call die_msg(427666881, 'each line in file ' // trim(file%name) &
735  // ' must contain exactly one or two data values')
736  end if
737 
738  ! copy over the data
739  if (allocated(vol_frac)) deallocate(vol_frac)
740  if (allocated(vol_frac_std)) deallocate(vol_frac_std)
741  allocate(vol_frac(aero_data_n_spec(aero_data)))
742  allocate(vol_frac_std(aero_data_n_spec(aero_data)))
743  vol_frac = 0d0
744  vol_frac_std = 0d0
745  do i = 1,n_species
746  species = aero_data_spec_by_name(aero_data, species_name(i))
747  if (species == 0) then
748  call die_msg(775942501, 'unknown species ' // trim(species_name(i)) &
749  // ' in file ' // trim(file%name))
750  end if
751  vol_frac(species) = species_data(i, 1)
752  if (size(species_data, 2) == 2) then
753  vol_frac_std(species) = species_data(i, 2)
754  end if
755  end do
756 
757  ! convert mass fractions to volume fractions
758  vol_frac = vol_frac / aero_data%density
759  vol_frac_std = vol_frac_std / aero_data%density
760 
761  ! normalize
762  tot_vol_frac = sum(vol_frac)
763  if ((minval(vol_frac) < 0d0) .or. (tot_vol_frac <= 0d0)) then
764  call die_msg(356648030, 'fractions in ' // trim(file%name) &
765  // ' are not positive')
766  end if
767  if (minval(vol_frac_std) < 0d0) then
768  call die_msg(676576501, 'standard deviations in ' // trim(file%name) &
769  // ' are not positive')
770  end if
771  vol_frac = vol_frac / tot_vol_frac
772  vol_frac_std = vol_frac_std / tot_vol_frac
773 
774  end subroutine spec_file_read_vol_frac
775 
776 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
777 
778  !> Read a size distribution from a data file.
779  subroutine spec_file_read_size_dist(file, sample_radius, sample_num_conc)
780 
781  !> Spec file to read size distribution from.
782  type(spec_file_t), intent(inout) :: file
783  !> Sample radius values (m).
784  real(kind=dp), allocatable, intent(inout) :: sample_radius(:)
785  !> Sample number concentrations (m^{-3}).
786  real(kind=dp), allocatable, intent(inout) :: sample_num_conc(:)
787 
788  character(len=SPEC_LINE_MAX_VAR_LEN), allocatable :: names(:)
789  real(kind=dp), allocatable :: data(:,:)
790  integer :: n_sample, i_sample
791 
792  !> \page input_format_size_dist Input File Format: Size Distribution
793  !!
794  !! A size distribution file must consist of two lines:
795  !! - the first line must begin with \c diam and be followed by
796  !! \f$N + 1\f$ space-separated real scalars, giving the diameters
797  !! \f$D_1,\ldots,D_{N+1}\f$ of bin edges (m) --- these must be
798  !! in increasing order, so \f$D_i < D_{i+1}\f$
799  !! - the second line must begin with \c num_conc and be followed
800  !! by \f$N\f$ space-separated real scalars, giving the number
801  !! concenrations \f$C_1,\ldots,C_N\f$ in each bin (#/m^3) ---
802  !! \f$C_i\f$ is the total number concentrations of particles
803  !! with diameters in \f$[D_i, D_{i+1}]\f$
804  !!
805  !! The resulting size distribution is taken to be piecewise
806  !! constant in log-diameter coordinates.
807  !!
808  !! Example: a size distribution could be:
809  !! <pre>
810  !! diam 1e-7 1e-6 1e-5 # bin edge diameters (m)
811  !! num_conc 1e9 1e8 # bin number concentrations (m^{-3})
812  !! </pre>
813  !! This distribution has 1e9 particles per cubic meter with
814  !! diameters between 0.1 micron and 1 micron, and 1e8 particles
815  !! per cubic meter with diameters between 1 micron and 10 micron.
816  !!
817  !! See also:
818  !! - \ref spec_file_format --- the input file text format
819  !! - \ref input_format_aero_dist --- the format for a complete
820  !! aerosol distribution with several modes
821  !! - \ref input_format_aero_mode --- the format for each mode
822  !! of an aerosol distribution
823 
824  ! read the data from the file
825  call spec_file_read_real_named_array(file, 1, names, data)
826  call spec_file_assert_msg(311818741, file, size(names) == 1, &
827  'must contain a line starting with "diam"')
828  call spec_file_check_name(file, 'diam', names(1))
829  n_sample = size(data,2) - 1
830  call spec_file_assert_msg(669011124, file, n_sample >= 1, &
831  'must have at least two diam values')
832 
833  if (allocated(sample_radius)) deallocate(sample_radius)
834  allocate(sample_radius(n_sample + 1))
835  sample_radius = diam2rad(data(1,:))
836  do i_sample = 1,n_sample
837  call spec_file_assert_msg(528089871, file, &
838  sample_radius(i_sample) < sample_radius(i_sample + 1), &
839  'diam values must be strictly increasing')
840  end do
841 
842  call spec_file_read_real_named_array(file, 1, names, data)
843  call spec_file_assert_msg(801676496, file, size(names) == 1, &
844  'must contain a line starting with "num_conc"')
845  call spec_file_check_name(file, 'num_conc', names(1))
846 
847  call spec_file_assert_msg(721029144, file, size(data, 2) == n_sample, &
848  'must have one fewer num_conc than diam values')
849 
850  if (allocated(sample_num_conc)) deallocate(sample_num_conc)
851  allocate(sample_num_conc(n_sample))
852  sample_num_conc = data(1,:)
853  do i_sample = 1,n_sample
854  call spec_file_assert_msg(356490397, file, &
855  sample_num_conc(i_sample) >= 0d0, &
856  'num_conc values must be non-negative')
857  end do
858 
859  end subroutine spec_file_read_size_dist
860 
861 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
862 
863  !> Read one mode of an aerosol distribution (number concentration,
864  !> volume fractions, and mode shape).
865  subroutine spec_file_read_aero_mode(file, aero_data, aero_mode, eof)
866 
867  !> Spec file.
868  type(spec_file_t), intent(inout) :: file
869  !> Aero_data data.
870  type(aero_data_t), intent(inout) :: aero_data
871  !> Aerosol mode.
872  type(aero_mode_t), intent(inout) :: aero_mode
873  !> If eof instead of reading data.
874  logical :: eof
875 
876  character(len=SPEC_LINE_MAX_VAR_LEN) :: tmp_str, mode_type, diam_type_str
877  character(len=SPEC_LINE_MAX_VAR_LEN) :: mass_frac_filename
878  character(len=SPEC_LINE_MAX_VAR_LEN) :: size_dist_filename
879  type(spec_line_t) :: line
880  type(spec_file_t) :: mass_frac_file, size_dist_file
881  real(kind=dp) :: diam, temp, pressure
882  integer :: diam_type, i_radius
883  real(kind=dp) :: log10_r0_mob, log10_r1_mob, log10_r2_mob, &
884  r0_mob, r2_mob, r0_geom, r2_geom, log10_r0_geom, log10_r2_geom
885 
886  ! note that doxygen's automatic list creation breaks on the list
887  ! below for some reason, so we just use html format
888 
889  !> \page input_format_aero_mode Input File Format: Aerosol Distribution Mode
890  !!
891  !! An aerosol distribution mode has the parameters:
892  !! <ul>
893  !! <li> \b mode_name (string): the name of the mode (for
894  !! informational purposes only)
895  !! <li> \b mass_frac (string): name of file from which to read the
896  !! species mass fractions --- the file format should
897  !! be \subpage input_format_mass_frac
898  !! <li> \b diam_type (string): the type of diameter for the mode
899  !! --- must be one of: \c geometric for geometric diameter;
900  !! or \c mobility for mobility equivalent diameter
901  !! <li> if \c diam_type is \c mobility then the following
902  !! parameters are:
903  !! <ul>
904  !! <li> \b temp (real, unit K): the temperate at which the
905  !! mobility diameters were measured
906  !! <li> \b pressure (real, unit Pa): the pressure at which the
907  !! mobility diameters were measured
908  !! </ul>
909  !! <li> \b mode_type (string): the functional form of the mode ---
910  !! must be one of: \c log_normal for a log-normal
911  !! distribution; \c exp for an exponential distribution; \c
912  !! mono for a mono-disperse distribution; or \c sampled for a
913  !! sampled distribution
914  !! <li> if \c mode_type is \c log_normal then the mode distribution
915  !! shape is
916  !! \f[ n(\log D) {\rm d}\log D
917  !! = \frac{N_{\rm total}}{\sqrt{2\pi} \log \sigma_{\rm g}}
918  !! \exp\left(\frac{(\log D - \log D_{\rm gn})^2}{2 \log ^2
919  !! \sigma_{\rm g}}\right)
920  !! {\rm d}\log D \f]
921  !! and the following parameters are:
922  !! <ul>
923  !! <li> \b num_conc (real, unit 1/m^3): the total number
924  !! concentration \f$N_{\rm total}\f$ of the mode
925  !! <li> \b geom_mean_diam (real, unit m): the geometric mean
926  !! diameter \f$D_{\rm gn}\f$
927  !! <li> \b log10_geom_std_dev (real, dimensionless):
928  !! \f$\log_{10}\f$ of the geometric standard deviation
929  !! \f$\sigma_{\rm g}\f$ of the diameter
930  !! </ul>
931  !! <li> if \c mode_type is \c exp then the mode distribution shape is
932  !! \f[ n(v) {\rm d}v = \frac{N_{\rm total}}{v_{\rm \mu}}
933  !! \exp\left(- \frac{v}{v_{\rm \mu}}\right)
934  !! {\rm d}v \f]
935  !! and the following parameters are:
936  !! <ul>
937  !! <li> \b num_conc (real, unit 1/m^3): the total number
938  !! concentration \f$N_{\rm total}\f$ of the mode
939  !! <li> \b diam_at_mean_vol (real, unit m): the diameter
940  !! \f$D_{\rm \mu}\f$ such that \f$v_{\rm \mu}
941  !! = \frac{\pi}{6} D^3_{\rm \mu}\f$
942  !! </ul>
943  !! <li> if \c mode_type is \c mono then the mode distribution shape
944  !! is a delta distribution at diameter \f$D_0\f$ and the
945  !! following parameters are:
946  !! <ul>
947  !! <li> \b num_conc (real, unit 1/m^3): the total number
948  !! concentration \f$N_{\rm total}\f$ of the mode
949  !! <li> \b radius (real, unit m): the radius \f$R_0\f$ of the
950  !! particles, so that \f$D_0 = 2 R_0\f$
951  !! </ul>
952  !! <li> if \c mode_type is \c sampled then the mode distribution
953  !! shape is piecewise constant (in log-diameter coordinates)
954  !! and the following parameters are:
955  !! <ul>
956  !! <li> \b size_dist (string): name of file from which to
957  !! read the size distribution --- the file format should
958  !! be \subpage input_format_size_dist
959  !! </ul>
960  !! </ul>
961  !!
962  !! Example:
963  !! <pre>
964  !! mode_name diesel # mode name (descriptive only)
965  !! mass_frac comp_diesel.dat # mass fractions in each aerosol particle
966  !! mode_type log_normal # type of distribution
967  !! num_conc 1.6e8 # particle number density (#/m^3)
968  !! geom_mean_diam 2.5e-8 # geometric mean diameter (m)
969  !! log10_geom_std_dev 0.24 # log_10 of geometric standard deviation
970  !! </pre>
971  !!
972  !! See also:
973  !! - \ref spec_file_format --- the input file text format
974  !! - \ref input_format_aero_dist --- the format for a complete
975  !! aerosol distribution with several modes
976  !! - \ref input_format_mass_frac --- the format for the mass
977  !! fractions file
978 
979  call spec_file_read_line(file, line, eof)
980  if (.not. eof) then
981  call spec_file_check_line_name(file, line, "mode_name")
982  call spec_file_check_line_length(file, line, 1)
983  tmp_str = line%data(1) ! hack to avoid gfortran warning
984  aero_mode%name = tmp_str(1:aero_mode_name_len)
985  aero_mode%source = aero_data_source_by_name(aero_data, aero_mode%name)
986 
987  call spec_file_read_string(file, 'mass_frac', mass_frac_filename)
988  call spec_file_open(mass_frac_filename, mass_frac_file)
989  call spec_file_read_vol_frac(mass_frac_file, aero_data, &
990  aero_mode%vol_frac, aero_mode%vol_frac_std)
991  call spec_file_close(mass_frac_file)
992 
993  call spec_file_read_string(file, 'diam_type', diam_type_str)
994  if (trim(diam_type_str) == 'geometric') then
996  elseif (trim(diam_type_str) == 'mobility') then
997  diam_type = aero_mode_diam_type_mobility
998  call spec_file_read_real(file, 'temp', temp)
999  call spec_file_read_real(file, 'pressure', pressure)
1000  else
1001  call spec_file_die_msg(804343794, file, &
1002  "Unknown diam_type: " // trim(diam_type_str))
1003  end if
1004 
1005  call spec_file_read_string(file, 'mode_type', mode_type)
1006  aero_mode%sample_radius = [ real(kind=dp) :: ]
1007  aero_mode%sample_num_conc = [ real(kind=dp) :: ]
1008  if (trim(mode_type) == 'log_normal') then
1009  aero_mode%type = aero_mode_type_log_normal
1010  call spec_file_read_real(file, 'num_conc', aero_mode%num_conc)
1011  call spec_file_read_real(file, 'geom_mean_diam', diam)
1012  call spec_file_read_real(file, 'log10_geom_std_dev', &
1013  aero_mode%log10_std_dev_radius)
1014  if (diam_type == aero_mode_diam_type_geometric) then
1015  aero_mode%char_radius = diam2rad(diam)
1016  elseif (diam_type == aero_mode_diam_type_mobility) then
1017  aero_mode%char_radius &
1019  diam2rad(diam), temp, pressure)
1020 
1021  ! Convert log10_std_dev_radius from mobility to geometric radius.
1022  ! We do this by finding points +/- one std dev in mobility
1023  ! radius, converting them to geometric radius, and then using
1024  ! the distance between them as a measure of 2 * std_dev in
1025  ! geometric radius.
1026  log10_r1_mob = log10(diam2rad(diam))
1027  log10_r0_mob = log10_r1_mob - aero_mode%log10_std_dev_radius
1028  log10_r2_mob = log10_r1_mob + aero_mode%log10_std_dev_radius
1029  r0_mob = 10**log10_r0_mob
1030  r2_mob = 10**log10_r2_mob
1031  r0_geom = aero_data_mobility_rad_to_geometric_rad(aero_data, &
1032  r0_mob, temp, pressure)
1033  r2_geom = aero_data_mobility_rad_to_geometric_rad(aero_data, &
1034  r2_mob, temp, pressure)
1035  log10_r0_geom = log10(r0_geom)
1036  log10_r2_geom = log10(r2_geom)
1037  aero_mode%log10_std_dev_radius &
1038  = (log10_r2_geom - log10_r0_geom) / 2d0
1039  else
1040  call die_msg(532966100, "Diameter type not handled: " &
1041  // integer_to_string(diam_type))
1042  end if
1043  elseif (trim(mode_type) == 'exp') then
1044  aero_mode%type = aero_mode_type_exp
1045  call spec_file_read_real(file, 'num_conc', aero_mode%num_conc)
1046  call spec_file_read_real(file, 'diam_at_mean_vol', diam)
1047  if (diam_type == aero_mode_diam_type_geometric) then
1048  aero_mode%char_radius = diam2rad(diam)
1049  elseif (diam_type == aero_mode_diam_type_mobility) then
1050  aero_mode%char_radius &
1052  diam2rad(diam), temp, pressure)
1053  else
1054  call die_msg(585104460, "Diameter type not handled: " &
1055  // integer_to_string(diam_type))
1056  end if
1057  elseif (trim(mode_type) == 'mono') then
1058  aero_mode%type = aero_mode_type_mono
1059  call spec_file_read_real(file, 'num_conc', aero_mode%num_conc)
1060  call spec_file_read_real(file, 'diam', diam)
1061  if (diam_type == aero_mode_diam_type_geometric) then
1062  aero_mode%char_radius = diam2rad(diam)
1063  elseif (diam_type == aero_mode_diam_type_mobility) then
1064  aero_mode%char_radius &
1066  diam2rad(diam), temp, pressure)
1067  else
1068  call die_msg(902864269, "Diameter type not handled: " &
1069  // integer_to_string(diam_type))
1070  end if
1071  elseif (trim(mode_type) == 'sampled') then
1072  aero_mode%type = aero_mode_type_sampled
1073  call spec_file_read_string(file, 'size_dist', size_dist_filename)
1074  call spec_file_open(size_dist_filename, size_dist_file)
1075  call spec_file_read_size_dist(size_dist_file, &
1076  aero_mode%sample_radius, aero_mode%sample_num_conc)
1077  call spec_file_close(size_dist_file)
1078  if (diam_type == aero_mode_diam_type_geometric) then
1079  ! do nothing
1080  elseif (diam_type == aero_mode_diam_type_mobility) then
1081  do i_radius = 1,size(aero_mode%sample_radius)
1082  aero_mode%sample_radius(i_radius) &
1084  aero_mode%sample_radius(i_radius), temp, pressure)
1085  end do
1086  else
1087  call die_msg(239088838, "Diameter type not handled: " &
1088  // integer_to_string(diam_type))
1089  end if
1090  else
1091  call spec_file_die_msg(729472928, file, &
1092  "Unknown distribution mode type: " // trim(mode_type))
1093  end if
1094  end if
1095 
1096  end subroutine spec_file_read_aero_mode
1097 
1098 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1099 
1100  !> Determines the number of bytes required to pack the given value.
1101  integer function pmc_mpi_pack_size_aero_mode(val)
1102 
1103  !> Value to pack.
1104  type(aero_mode_t), intent(in) :: val
1105 
1107  pmc_mpi_pack_size_string(val%name) &
1108  + pmc_mpi_pack_size_integer(val%type) &
1109  + pmc_mpi_pack_size_real(val%char_radius) &
1110  + pmc_mpi_pack_size_real(val%log10_std_dev_radius) &
1111  + pmc_mpi_pack_size_real_array(val%sample_radius) &
1112  + pmc_mpi_pack_size_real_array(val%sample_num_conc) &
1113  + pmc_mpi_pack_size_real(val%num_conc) &
1114  + pmc_mpi_pack_size_real_array(val%vol_frac) &
1115  + pmc_mpi_pack_size_real_array(val%vol_frac_std) &
1116  + pmc_mpi_pack_size_integer(val%source)
1117 
1118  end function pmc_mpi_pack_size_aero_mode
1119 
1120 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1121 
1122  !> Packs the given value into the buffer, advancing position.
1123  subroutine pmc_mpi_pack_aero_mode(buffer, position, val)
1124 
1125  !> Memory buffer.
1126  character, intent(inout) :: buffer(:)
1127  !> Current buffer position.
1128  integer, intent(inout) :: position
1129  !> Value to pack.
1130  type(aero_mode_t), intent(in) :: val
1131 
1132 #ifdef PMC_USE_MPI
1133  integer :: prev_position
1134 
1135  prev_position = position
1136  call pmc_mpi_pack_string(buffer, position, val%name)
1137  call pmc_mpi_pack_integer(buffer, position, val%type)
1138  call pmc_mpi_pack_real(buffer, position, val%char_radius)
1139  call pmc_mpi_pack_real(buffer, position, val%log10_std_dev_radius)
1140  call pmc_mpi_pack_real_array(buffer, position, val%sample_radius)
1141  call pmc_mpi_pack_real_array(buffer, position, val%sample_num_conc)
1142  call pmc_mpi_pack_real(buffer, position, val%num_conc)
1143  call pmc_mpi_pack_real_array(buffer, position, val%vol_frac)
1144  call pmc_mpi_pack_real_array(buffer, position, val%vol_frac_std)
1145  call pmc_mpi_pack_integer(buffer, position, val%source)
1146  call assert(497092471, &
1147  position - prev_position <= pmc_mpi_pack_size_aero_mode(val))
1148 #endif
1149 
1150  end subroutine pmc_mpi_pack_aero_mode
1151 
1152 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1153 
1154  !> Unpacks the given value from the buffer, advancing position.
1155  subroutine pmc_mpi_unpack_aero_mode(buffer, position, val)
1156 
1157  !> Memory buffer.
1158  character, intent(inout) :: buffer(:)
1159  !> Current buffer position.
1160  integer, intent(inout) :: position
1161  !> Value to pack.
1162  type(aero_mode_t), intent(inout) :: val
1163 
1164 #ifdef PMC_USE_MPI
1165  integer :: prev_position
1166 
1167  prev_position = position
1168  call pmc_mpi_unpack_string(buffer, position, val%name)
1169  call pmc_mpi_unpack_integer(buffer, position, val%type)
1170  call pmc_mpi_unpack_real(buffer, position, val%char_radius)
1171  call pmc_mpi_unpack_real(buffer, position, val%log10_std_dev_radius)
1172  call pmc_mpi_unpack_real_array(buffer, position, val%sample_radius)
1173  call pmc_mpi_unpack_real_array(buffer, position, val%sample_num_conc)
1174  call pmc_mpi_unpack_real(buffer, position, val%num_conc)
1175  call pmc_mpi_unpack_real_array(buffer, position, val%vol_frac)
1176  call pmc_mpi_unpack_real_array(buffer, position, val%vol_frac_std)
1177  call pmc_mpi_unpack_integer(buffer, position, val%source)
1178  call assert(874467577, &
1179  position - prev_position <= pmc_mpi_pack_size_aero_mode(val))
1180 #endif
1181 
1182  end subroutine pmc_mpi_unpack_aero_mode
1183 
1184 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1185 
1186 end module pmc_aero_mode
pmc_aero_mode::num_conc_exp
subroutine num_conc_exp(total_num_conc, radius_at_mean_vol, bin_grid, aero_data, num_conc)
Exponential distribution in volume.
Definition: aero_mode.F90:196
pmc_aero_mode::aero_mode_type_len
integer, parameter aero_mode_type_len
Maximum length of a mode type.
Definition: aero_mode.F90:26
pmc_aero_mode::vol_conc_mono
subroutine vol_conc_mono(total_num_conc, radius, bin_grid, aero_data, vol_conc)
Mono-disperse distribution in volume.
Definition: aero_mode.F90:278
pmc_aero_weight::aero_weight_type_none
integer, parameter aero_weight_type_none
Type code for no (or flat) weighting.
Definition: aero_weight.F90:26
pmc_aero_data::aero_data_n_spec
elemental integer function aero_data_n_spec(aero_data)
Return the number of aerosol species, or -1 if uninitialized.
Definition: aero_data.F90:236
pmc_mpi
Wrapper functions for MPI.
Definition: mpi.F90:13
pmc_aero_mode::aero_mode_type_to_string
character(len=aero_mode_type_len) function aero_mode_type_to_string(type)
Return a string representation of a kernel type.
Definition: aero_mode.F90:84
pmc_aero_mode::aero_mode_diam_type_mobility
integer, parameter aero_mode_diam_type_mobility
Type code for mobility equivalent diameter.
Definition: aero_mode.F90:44
pmc_aero_weight
The aero_weight_t structure and associated subroutines.
Definition: aero_weight.F90:9
pmc_mpi::pmc_mpi_pack_size_real
integer function pmc_mpi_pack_size_real(val)
Determines the number of bytes required to pack the given value.
Definition: mpi.F90:365
pmc_aero_mode::num_conc_mono
subroutine num_conc_mono(total_num_conc, radius, bin_grid, num_conc)
Mono-disperse distribution. Normalized so that sum(num_conc(k) * log_width) = 1.
Definition: aero_mode.F90:251
pmc_aero_mode::aero_mode_sample_vols
subroutine aero_mode_sample_vols(aero_mode, total_vol, vols)
Return an array of volumes randomly sampled from the volume fractions.
Definition: aero_mode.F90:630
pmc_spec_file::spec_file_close
subroutine spec_file_close(file)
Close a spec file.
Definition: spec_file.F90:135
pmc_bin_grid::vol_to_lnr
subroutine vol_to_lnr(r, f_vol, f_lnr)
Convert a concentration f(vol)d(vol) to f(ln(r))d(ln(r)) where vol = 4/3 pi r^3.
Definition: bin_grid.F90:68
pmc_aero_mode::aero_mode_diam_type_invalid
integer, parameter aero_mode_diam_type_invalid
Type code for an undefined for invalid diameter type.
Definition: aero_mode.F90:40
pmc_aero_mode::vol_conc_exp
subroutine vol_conc_exp(total_num_conc, radius_at_mean_vol, bin_grid, aero_data, vol_conc)
Exponential distribution in volume.
Definition: aero_mode.F90:226
pmc_aero_data::aero_data_source_by_name
integer function aero_data_source_by_name(aero_data, name)
Returns the number of the source in aero_data with the given name, or adds the source if it doesn't e...
Definition: aero_data.F90:298
pmc_aero_mode::aero_mode_type_sampled
integer, parameter aero_mode_type_sampled
Type code for a sampled mode.
Definition: aero_mode.F90:37
pmc_aero_mode::aero_mode_type_exp
integer, parameter aero_mode_type_exp
Type code for an exponential mode.
Definition: aero_mode.F90:33
pmc_rand::rand_normal
real(kind=dp) function rand_normal(mean, stddev)
Generates a normally distributed random number with the given mean and standard deviation.
Definition: rand.F90:408
pmc_util::die_msg
subroutine die_msg(code, error_msg)
Error immediately.
Definition: util.F90:134
pmc_aero_mode::vol_conc_sampled
subroutine vol_conc_sampled(sample_radius, sample_num_conc, bin_grid, aero_data, vol_conc)
Sampled distribution in volume.
Definition: aero_mode.F90:354
pmc_constants::dp
integer, parameter dp
Kind of a double precision real number.
Definition: constants.F90:12
pmc_aero_mode::pmc_mpi_pack_size_aero_mode
integer function pmc_mpi_pack_size_aero_mode(val)
Determines the number of bytes required to pack the given value.
Definition: aero_mode.F90:1102
pmc_mpi::pmc_mpi_pack_string
subroutine pmc_mpi_pack_string(buffer, position, val)
Packs the given value into the buffer, advancing position.
Definition: mpi.F90:611
pmc_spec_file
Reading formatted text input.
Definition: spec_file.F90:43
pmc_bin_grid::bin_grid_size
elemental integer function bin_grid_size(bin_grid)
Return the number of bins in the grid, or -1 if the bin grid is not allocated.
Definition: bin_grid.F90:51
pmc_mpi::pmc_mpi_pack_real
subroutine pmc_mpi_pack_real(buffer, position, val)
Packs the given value into the buffer, advancing position.
Definition: mpi.F90:586
pmc_util::assert
subroutine assert(code, condition_ok)
Errors unless condition_ok is true.
Definition: util.F90:103
pmc_aero_mode::vol_conc_log_normal
subroutine vol_conc_log_normal(total_num_conc, geom_mean_radius, log10_sigma_g, bin_grid, aero_data, vol_conc)
Compute a log-normal distribution in volume.
Definition: aero_mode.F90:165
pmc_aero_mode::aero_mode_num_conc
subroutine aero_mode_num_conc(aero_mode, bin_grid, aero_data, num_conc)
Return the binned number concentration for an aero_mode.
Definition: aero_mode.F90:378
pmc_mpi::pmc_mpi_pack_size_real_array
integer function pmc_mpi_pack_size_real_array(val)
Determines the number of bytes required to pack the given value.
Definition: mpi.F90:477
pmc_aero_mode::aero_mode_total_num_conc
real(kind=dp) function aero_mode_total_num_conc(aero_mode)
Returns the total number concentration of a mode. (#/m^3)
Definition: aero_mode.F90:108
pmc_mpi::pmc_mpi_unpack_string
subroutine pmc_mpi_unpack_string(buffer, position, val)
Unpacks the given value from the buffer, advancing position.
Definition: mpi.F90:868
pmc_aero_mode::aero_mode_type_mono
integer, parameter aero_mode_type_mono
Type code for a mono-disperse mode.
Definition: aero_mode.F90:35
pmc_spec_file::spec_file_t
An input file with extra data for printing messages.
Definition: spec_file.F90:59
pmc_util::warn_msg
subroutine warn_msg(code, warning_msg, already_warned)
Prints a warning message.
Definition: util.F90:37
pmc_spec_file::spec_file_read_real
subroutine spec_file_read_real(file, name, var)
Read a real number from a spec file that must have the given name.
Definition: spec_file.F90:563
pmc_aero_weight::aero_weight_type_power
integer, parameter aero_weight_type_power
Type code for power function weighting.
Definition: aero_weight.F90:28
pmc_util::integer_to_string
character(len=pmc_util_convert_string_len) function integer_to_string(val)
Convert an integer to a string format.
Definition: util.F90:766
pmc_util::assert_msg
subroutine assert_msg(code, condition_ok, error_msg)
Errors unless condition_ok is true.
Definition: util.F90:77
pmc_aero_weight::aero_weight_type_mfa
integer, parameter aero_weight_type_mfa
Type code for MFA weighting.
Definition: aero_weight.F90:30
pmc_util::diam2rad
real(kind=dp) elemental function diam2rad(d)
Convert diameter (m) to radius (m).
Definition: util.F90:277
pmc_spec_file::spec_file_check_name
subroutine spec_file_check_name(file, name, read_name)
Checks that the read_name is the same as name.
Definition: spec_file.F90:410
pmc_spec_file::spec_file_check_line_length
subroutine spec_file_check_line_length(file, line, length)
Check that the length of the line data is as given.
Definition: spec_file.F90:432
pmc_aero_weight::aero_weight_num_conc_at_radius
real(kind=dp) function aero_weight_num_conc_at_radius(aero_weight, radius)
Compute the number concentration at a given radius (m^{-3}).
Definition: aero_weight.F90:128
pmc_aero_mode::aero_mode_t
An aerosol size distribution mode.
Definition: aero_mode.F90:54
pmc_aero_data::aero_data_mobility_rad_to_geometric_rad
real(kind=dp) function aero_data_mobility_rad_to_geometric_rad(aero_data, mobility_rad, temp, pressure)
Convert mobility equivalent radius (m) to geometric radius (m^3).
Definition: aero_data.F90:216
pmc_constants::const
type(const_t), save const
Fixed variable for accessing the constant's values.
Definition: constants.F90:73
pmc_rand::sample_cts_pdf
integer function sample_cts_pdf(pdf)
Sample the given continuous probability density function.
Definition: rand.F90:490
pmc_aero_mode::aero_mode_type_invalid
integer, parameter aero_mode_type_invalid
Type code for an undefined or invalid mode.
Definition: aero_mode.F90:29
pmc_aero_mode::pmc_mpi_unpack_aero_mode
subroutine pmc_mpi_unpack_aero_mode(buffer, position, val)
Unpacks the given value from the buffer, advancing position.
Definition: aero_mode.F90:1156
pmc_spec_file::spec_file_open
subroutine spec_file_open(filename, file)
Open a spec file for reading.
Definition: spec_file.F90:112
pmc_aero_weight::aero_weight_t
An aerosol size distribution weighting function.
Definition: aero_weight.F90:33
pmc_aero_mode::aero_mode_name_len
integer, parameter aero_mode_name_len
Maximum length of a mode name.
Definition: aero_mode.F90:24
pmc_rand
Random number generators.
Definition: rand.F90:9
pmc_spec_file::spec_file_read_real_named_array
subroutine spec_file_read_real_named_array(file, max_lines, names, vals)
Read an array of named lines with real data. All lines must have the same number of data elements.
Definition: spec_file.F90:650
pmc_aero_mode::num_conc_sampled
subroutine num_conc_sampled(sample_radius, sample_num_conc, bin_grid, num_conc)
Sampled distribution, not normalized.
Definition: aero_mode.F90:308
pmc_aero_mode::aero_mode_diam_type_geometric
integer, parameter aero_mode_diam_type_geometric
Type code for geometric diameter.
Definition: aero_mode.F90:42
pmc_aero_data::aero_data_vol2rad
real(kind=dp) elemental function aero_data_vol2rad(aero_data, v)
Convert mass-equivalent volume (m^3) to geometric radius (m).
Definition: aero_data.F90:92
pmc_rand::rand_normal_array_1d
subroutine rand_normal_array_1d(mean, stddev, val)
Generates a vector of normally distributed random numbers with the given means and standard deviation...
Definition: rand.F90:467
pmc_aero_data::aero_data_t
Aerosol material properties and associated data.
Definition: aero_data.F90:49
pmc_mpi::pmc_mpi_unpack_integer
subroutine pmc_mpi_unpack_integer(buffer, position, val)
Unpacks the given value from the buffer, advancing position.
Definition: mpi.F90:818
pmc_constants
Physical constants.
Definition: constants.F90:9
pmc_spec_file::spec_file_read_line
subroutine spec_file_read_line(file, line, eof)
Read a spec_line from the spec_file.
Definition: spec_file.F90:215
pmc_aero_mode::pmc_mpi_pack_aero_mode
subroutine pmc_mpi_pack_aero_mode(buffer, position, val)
Packs the given value into the buffer, advancing position.
Definition: aero_mode.F90:1124
pmc_aero_mode::aero_mode_type_log_normal
integer, parameter aero_mode_type_log_normal
Type code for a log-normal mode.
Definition: aero_mode.F90:31
pmc_util
Common utility subroutines.
Definition: util.F90:9
pmc_aero_mode::aero_mode_number
real(kind=dp) function aero_mode_number(aero_mode, aero_weight)
Return the total number of computational particles for an aero_mode.
Definition: aero_mode.F90:495
pmc_spec_file::spec_file_die_msg
subroutine spec_file_die_msg(code, file, msg)
Exit with an error message containing filename and line number.
Definition: spec_file.F90:74
pmc_aero_mode
The aero_mode_t structure and associated subroutines.
Definition: aero_mode.F90:9
pmc_spec_file::spec_file_read_string
subroutine spec_file_read_string(file, name, var)
Read a string from a spec file that must have a given name.
Definition: spec_file.F90:605
pmc_aero_mode::aero_mode_weighted_sampled_num_conc
subroutine aero_mode_weighted_sampled_num_conc(aero_mode, aero_weight, weighted_num_conc)
Compute weighted sampled number concentrations.
Definition: aero_mode.F90:458
pmc_mpi::pmc_mpi_pack_size_integer
integer function pmc_mpi_pack_size_integer(val)
Determines the number of bytes required to pack the given value.
Definition: mpi.F90:345
pmc_bin_grid
The bin_grid_t structure and associated subroutines.
Definition: bin_grid.F90:9
pmc_mpi::pmc_mpi_pack_integer
subroutine pmc_mpi_pack_integer(buffer, position, val)
Packs the given value into the buffer, advancing position.
Definition: mpi.F90:561
pmc_aero_data::aero_data_rad2vol
real(kind=dp) elemental function aero_data_rad2vol(aero_data, r)
Convert geometric radius (m) to mass-equivalent volume (m^3).
Definition: aero_data.F90:122
pmc_aero_data
The aero_data_t structure and associated subroutines.
Definition: aero_data.F90:9
pmc_mpi::pmc_mpi_pack_real_array
subroutine pmc_mpi_pack_real_array(buffer, position, val)
Packs the given value into the buffer, advancing position.
Definition: mpi.F90:720
pmc_spec_file::spec_file_check_line_name
subroutine spec_file_check_line_name(file, line, name)
Check that the name of the line data is as given.
Definition: spec_file.F90:390
pmc_bin_grid::bin_grid_t
1D grid, either logarithmic or linear.
Definition: bin_grid.F90:33
pmc_spec_file::spec_file_assert_msg
subroutine spec_file_assert_msg(code, file, condition_ok, msg)
Exit with an error message containing filename and line number if condition_ok is ....
Definition: spec_file.F90:91
pmc_aero_mode::num_conc_log_normal
subroutine num_conc_log_normal(total_num_conc, geom_mean_radius, log10_sigma_g, bin_grid, num_conc)
Compute a log-normal distribution.
Definition: aero_mode.F90:131
pmc_mpi::pmc_mpi_unpack_real
subroutine pmc_mpi_unpack_real(buffer, position, val)
Unpacks the given value from the buffer, advancing position.
Definition: mpi.F90:843
pmc_mpi::pmc_mpi_unpack_real_array
subroutine pmc_mpi_unpack_real_array(buffer, position, val)
Unpacks the given value from the buffer, advancing position.
Definition: mpi.F90:978
pmc_aero_data::aero_data_spec_by_name
integer function aero_data_spec_by_name(aero_data, name)
Returns the number of the species in aero_data with the given name, or returns 0 if there is no such ...
Definition: aero_data.F90:269
pmc_mpi::pmc_mpi_pack_size_string
integer function pmc_mpi_pack_size_string(val)
Determines the number of bytes required to pack the given value.
Definition: mpi.F90:385
pmc_rand::pmc_random
real(kind=dp) function pmc_random()
Returns a random number between 0 and 1.
Definition: rand.F90:139
pmc_bin_grid::bin_grid_find
integer function bin_grid_find(bin_grid, val)
Find the bin number that contains a given value.
Definition: bin_grid.F90:144
pmc_aero_mode::aero_mode_sample_radius
subroutine aero_mode_sample_radius(aero_mode, aero_data, aero_weight, radius)
Return a radius randomly sampled from the mode distribution.
Definition: aero_mode.F90:549
pmc_aero_weight::aero_weight_radius_at_num_conc
real(kind=dp) function aero_weight_radius_at_num_conc(aero_weight, num_conc)
Compute the radius at a given number concentration (m). Inverse of aero_weight_num_conc_at_radius().
Definition: aero_weight.F90:171
pmc_aero_mode::aero_mode_vol_conc
subroutine aero_mode_vol_conc(aero_mode, bin_grid, aero_data, vol_conc)
Return the binned per-species volume concentration for an aero_mode.
Definition: aero_mode.F90:413