PartMC  2.6.1
scenario.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_scenario module.
7 
8 !> The scenario_t structure and associated subroutines.
10 
11  use pmc_gas_state
12  use pmc_aero_dist
13  use pmc_util
14  use pmc_env_state
15  use pmc_aero_state
16  use pmc_spec_file
17  use pmc_aero_data
18  use pmc_gas_data
19  use pmc_chamber
20  use pmc_mpi
21 #ifdef PMC_USE_MPI
22  use mpi
23 #endif
24 
25  !> Type code for an undefined or invalid loss function.
26  integer, parameter :: scenario_loss_function_invalid = 0
27  !> Type code for a zero loss function.
28  integer, parameter :: scenario_loss_function_none = 1
29  !> Type code for a constant loss function.
30  integer, parameter :: scenario_loss_function_constant = 2
31  !> Type code for a loss rate function proportional to particle volume.
32  integer, parameter :: scenario_loss_function_volume = 3
33  !> Type code for a loss rate function based on dry deposition
34  integer, parameter :: scenario_loss_function_drydep = 4
35  !> Type code for a loss rate function for chamber experiments.
36  integer, parameter :: scenario_loss_function_chamber = 5
37 
38  !> Parameter to switch between algorithms for particle loss.
39  !! A value of 0 will always use the naive algorithm, and
40  !! a value of 1 will always use the accept-reject algorithm.
41  real(kind=dp), parameter :: scenario_loss_alg_threshold = 1.0d0
42 
43  !> Scenario data.
44  !!
45  !! This is everything needed to drive the scenario being simulated.
46  !!
47  !! The temperature, pressure, emissions and background states are profiles
48  !! prescribed as functions of time by giving a number of times and
49  !! the corresponding data. Simple data such as temperature and pressure is
50  !! linearly interpolated between times, with constant interpolation
51  !! outside of the range of times. Gases and aerosols are
52  !! interpolated with gas_state_interp_1d() and
53  !! aero_dist_interp_1d(), respectively.
55  !> Temperature set-point times (s).
56  real(kind=dp), allocatable :: temp_time(:)
57  !> Temperatures at set-points (K).
58  real(kind=dp), allocatable :: temp(:)
59 
60  !> Pressure set-point times (s).
61  real(kind=dp), allocatable :: pressure_time(:)
62  !> Pressures at set-points (Pa).
63  real(kind=dp), allocatable :: pressure(:)
64 
65  !> Height set-point times (s).
66  real(kind=dp), allocatable :: height_time(:)
67  !> Heights at set-points (m).
68  real(kind=dp), allocatable :: height(:)
69 
70  !> Gas emission set-point times (s).
71  real(kind=dp), allocatable :: gas_emission_time(:)
72  !> Gas emisssion rate scales at set-points (1).
73  real(kind=dp), allocatable :: gas_emission_rate_scale(:)
74  !> Gas emission rates at set-points (mol m^{-2} s^{-1}).
75  type(gas_state_t), allocatable :: gas_emission(:)
76 
77  !> Gas-background dilution set-point times (s).
78  real(kind=dp), allocatable :: gas_dilution_time(:)
79  !> Gas-background dilution rates at set-points (s^{-1}).
80  real(kind=dp), allocatable :: gas_dilution_rate(:)
81  !> Background gas mixing ratios at set-points (ppb).
82  type(gas_state_t), allocatable :: gas_background(:)
83 
84  !> Aerosol emission set-points times (s).
85  real(kind=dp), allocatable :: aero_emission_time(:)
86  !> Aerosol emission rate scales at set-points (1).
87  real(kind=dp), allocatable :: aero_emission_rate_scale(:)
88  !> Aerosol emissions at set-points (# m^{-2} s^{-1}).
89  type(aero_dist_t), allocatable :: aero_emission(:)
90 
91  !> Aerosol-background dilution set-point times (s).
92  real(kind=dp), allocatable :: aero_dilution_time(:)
93  !> Aerosol-background dilution rates at set-points (s^{-1}).
94  real(kind=dp), allocatable :: aero_dilution_rate(:)
95  !> Aerosol background at set-points (# m^{-3}).
96  type(aero_dist_t), allocatable :: aero_background(:)
97 
98  !> Type of loss rate function.
99  integer :: loss_function_type
100  !> Chamber parameters for wall loss and sedimentation.
101  type(chamber_t) :: chamber
102  end type scenario_t
103 
104 contains
105 
106 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
107 
108  !> Initialize the time-dependent contents of the
109  !> environment. Thereafter scenario_update_env_state() should be used.
110  subroutine scenario_init_env_state(scenario, env_state, time)
111 
112  !> Scenario data.
113  type(scenario_t), intent(in) :: scenario
114  !> Environment state to update.
115  type(env_state_t), intent(inout) :: env_state
116  !> Current time (s).
117  real(kind=dp), intent(in) :: time
118 
119  env_state%temp = interp_1d(scenario%temp_time, scenario%temp, time)
120  env_state%pressure = interp_1d(scenario%pressure_time, &
121  scenario%pressure, time)
122  env_state%height = interp_1d(scenario%height_time, scenario%height, time)
123  env_state%elapsed_time = time
124  ! FIXME: should compute this at some point
125  env_state%solar_zenith_angle = 0d0
126 
127  end subroutine scenario_init_env_state
128 
129 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
130 
131  !> Update time-dependent contents of the environment.
132  !> scenario_init_env_state() should have been called at the start.
133  subroutine scenario_update_env_state(scenario, env_state, time)
134 
135  !> Scenario data.
136  type(scenario_t), intent(in) :: scenario
137  !> Environment state to update.
138  type(env_state_t), intent(inout) :: env_state
139  !> Current time (s).
140  real(kind=dp), intent(in) :: time
141 
142  !> Ambient water vapor pressure (Pa).
143  real(kind=dp) :: pmv_old, pmv_new
144  !> Ambient pressure (Pa)
145  real(kind=dp) :: pressure_old
146  !> Ambient temperature (K)
147  real(kind=dp) :: temp_old
148 
149  ! Update temperature and pressure and adjust relative humidity to maintain
150  ! water mixing ratio.
151 
152  pmv_old = env_state_sat_vapor_pressure(env_state) * env_state%rel_humid
153  pressure_old = env_state%pressure
154  temp_old = env_state%temp
155 
156  env_state%temp = interp_1d(scenario%temp_time, scenario%temp, time)
157  env_state%pressure = interp_1d(scenario%pressure_time, &
158  scenario%pressure, time)
159 
160  pmv_new = pmv_old * env_state%pressure / pressure_old
161  env_state%rel_humid = pmv_new / env_state_sat_vapor_pressure(env_state)
162 
163  env_state%height = interp_1d(scenario%height_time, scenario%height, time)
164  env_state%elapsed_time = time
165 
166  end subroutine scenario_update_env_state
167 
168 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
169 
170  !> Do gas emissions and background dilution.
171  !!
172  !! Emissions are given as an areal rate \f$e(t)\f$, and then divided
173  !! by the current box height \f$h(t)\f$ to obtain a volume
174  !! rate. There is also a dimensionless rate scaling \f$r(t)\f$. All
175  !! input functions are asusumed constant over the timestep, so the
176  !! concentration \f$c(t)\f$ change is given by
177  !! \f[
178  !! c(t) = c(0) + \frac{r t}{h} e.
179  !! \f]
180  !!
181  !! We model dilution by considering a gas concentration \f$c(t)\f$
182  !! in a box of height \f$h(t)\f$, subject to first-order dilution
183  !! with a rate \f$\lambda(t)\f$. Then the effective dilution rate is
184  !! \f[
185  !! \lambda_{\rm eff}(t) = \lambda(t) + \frac{\dot{h}(t)}{h(t)}
186  !! \f]
187  !! and the evolution of \f$c(t)\f$ is given by
188  !! \f[
189  !! \dot{c}(t) = - \lambda_{\rm eff}(t) c(t).
190  !! \f]
191  !! Solving this with separation of variables gives
192  !! \f[
193  !! \frac{c(t)}{c(0)} = \frac{h(0)}{h(t)}
194  !! \exp\left( - \int_0^t \lambda(t)\,dt\right).
195  !! \f]
196  !! If we define \f$p = c(t)/c(0)\f$ to be the remaining proportion
197  !! of the initial concentration, and \f$b\f$ to be the constant
198  !! background concentration, then we have
199  !! \f[
200  !! c(t) = p(t) c(0) + (1 - p(t)) b.
201  !! \f]
202  !! We assume constant \f$\lambda\f$ and we only do entrainment with
203  !! increasing height \f$h(t)\f$, so we have
204  !! \f[
205  !! p(t) = \min\left(1, \frac{h(0)}{h(t)}\right) \exp(-\lambda t).
206  !! \f]
207  subroutine scenario_update_gas_state(scenario, delta_t, env_state, &
208  old_env_state, gas_data, gas_state)
209 
210  !> Scenario data.
211  type(scenario_t), intent(in) :: scenario
212  !> Time increment to update over.
213  real(kind=dp), intent(in) :: delta_t
214  !> Current environment.
215  type(env_state_t), intent(in) :: env_state
216  !> Previous environment.
217  type(env_state_t), intent(in) :: old_env_state
218  !> Gas data values.
219  type(gas_data_t), intent(in) :: gas_data
220  !> Gas state to update.
221  type(gas_state_t), intent(inout) :: gas_state
222 
223  real(kind=dp) :: emission_rate_scale, dilution_rate, p
224  type(gas_state_t) :: emissions, background
225 
226  ! emissions
227  call gas_state_interp_1d(scenario%gas_emission, &
228  scenario%gas_emission_time, scenario%gas_emission_rate_scale, &
229  env_state%elapsed_time, emissions, emission_rate_scale)
230  call gas_state_mole_dens_to_ppb(emissions, env_state)
231  p = emission_rate_scale * delta_t / env_state%height
232  call gas_state_add_scaled(gas_state, emissions, p)
233 
234  ! dilution
235  call gas_state_interp_1d(scenario%gas_background, &
236  scenario%gas_dilution_time, scenario%gas_dilution_rate, &
237  env_state%elapsed_time, background, dilution_rate)
238  p = exp(- dilution_rate * delta_t)
239  if (env_state%height > old_env_state%height) then
240  p = p * old_env_state%height / env_state%height
241  end if
242  call gas_state_scale(gas_state, p)
243  call gas_state_add_scaled(gas_state, background, 1d0 - p)
244  call gas_state_ensure_nonnegative(gas_state)
245 
246  end subroutine scenario_update_gas_state
247 
248 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
249 
250  !> Do emissions and background dilution for a particle aerosol
251  !> distribution.
252  !!
253  !! See scenario_update_gas_state() for a description of the
254  !! model. We additionally scale the number concentration to account
255  !! for temperature changes.
256  subroutine scenario_update_aero_state(scenario, delta_t, env_state, &
257  old_env_state, aero_data, aero_state, n_emit, n_dil_in, n_dil_out, &
258  allow_doubling, allow_halving)
259 
260  !> Scenario data.
261  type(scenario_t), intent(in) :: scenario
262  !> Time increment to update over.
263  real(kind=dp), intent(in) :: delta_t
264  !> Current environment.
265  type(env_state_t), intent(in) :: env_state
266  !> Previous environment.
267  type(env_state_t), intent(in) :: old_env_state
268  !> Aero data values.
269  type(aero_data_t), intent(in) :: aero_data
270  !> Aero state to update.
271  type(aero_state_t), intent(inout) :: aero_state
272  !> Number of emitted particles.
273  integer, intent(out) :: n_emit
274  !> Number of diluted-in particles.
275  integer, intent(out) :: n_dil_in
276  !> Number of diluted-out particles.
277  integer, intent(out) :: n_dil_out
278  !> Whether to allow doubling of the population.
279  logical, intent(in) :: allow_doubling
280  !> Whether to allow halving of the population.
281  logical, intent(in) :: allow_halving
282 
283  real(kind=dp) :: emission_rate_scale, dilution_rate, p
284  type(aero_dist_t) :: emissions, background
285  type(aero_state_t) :: aero_state_delta
286 
287  ! emissions
288  call aero_dist_interp_1d(scenario%aero_emission, &
289  scenario%aero_emission_time, scenario%aero_emission_rate_scale, &
290  env_state%elapsed_time, emissions, emission_rate_scale)
291  p = emission_rate_scale * delta_t / env_state%height
292  call aero_state_add_aero_dist_sample(aero_state, aero_data, &
293  emissions, p, env_state%elapsed_time, allow_doubling, allow_halving, &
294  n_emit)
295 
296  ! dilution
297  call aero_dist_interp_1d(scenario%aero_background, &
298  scenario%aero_dilution_time, scenario%aero_dilution_rate, &
299  env_state%elapsed_time, background, dilution_rate)
300  p = exp(- dilution_rate * delta_t)
301  if (env_state%height > old_env_state%height) then
302  p = p * old_env_state%height / env_state%height
303  end if
304  ! loss to background
305  call aero_state_sample_particles(aero_state, aero_state_delta, &
306  aero_data, 1d0 - p, aero_info_dilution)
307  n_dil_out = aero_state_total_particles(aero_state_delta)
308  ! addition from background
309  call aero_state_add_aero_dist_sample(aero_state, aero_data, &
310  background, 1d0 - p, env_state%elapsed_time, allow_doubling, &
311  allow_halving, n_dil_in)
312 
313  ! particle loss function
314  call scenario_particle_loss(scenario, delta_t, aero_data, aero_state, &
315  env_state)
316 
317  ! update computational volume
318  call aero_weight_array_scale(aero_state%awa, &
319  old_env_state%temp * env_state%pressure &
320  / (env_state%temp * old_env_state%pressure))
321 
322  end subroutine scenario_update_aero_state
323 
324 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
325 
326  !> Do emissions and background dilution from the environment for a
327  !> binned aerosol distribution.
328  !!
329  !! See scenario_update_gas_state() for a description of the model.
330  subroutine scenario_update_aero_binned(scenario, delta_t, env_state, &
331  old_env_state, bin_grid, aero_data, aero_binned)
332 
333  !> Scenario data.
334  type(scenario_t), intent(in) :: scenario
335  !> Time increment to update over.
336  real(kind=dp), intent(in) :: delta_t
337  !> Current environment.
338  type(env_state_t), intent(in) :: env_state
339  !> Previous environment.
340  type(env_state_t), intent(in) :: old_env_state
341  !> Bin grid.
342  type(bin_grid_t), intent(in) :: bin_grid
343  !> Aero data values.
344  type(aero_data_t), intent(in) :: aero_data
345  !> Aero binned to update.
346  type(aero_binned_t), intent(inout) :: aero_binned
347 
348  real(kind=dp) :: emission_rate_scale, dilution_rate, p
349  type(aero_dist_t) :: emissions, background
350  type(aero_binned_t) :: emissions_binned, background_binned
351 
352  ! emissions
353  call aero_dist_interp_1d(scenario%aero_emission, &
354  scenario%aero_emission_time, scenario%aero_emission_rate_scale, &
355  env_state%elapsed_time, emissions, emission_rate_scale)
356  call aero_binned_add_aero_dist(emissions_binned, bin_grid, aero_data, &
357  emissions)
358  p = emission_rate_scale * delta_t / env_state%height
359  call aero_binned_add_scaled(aero_binned, emissions_binned, p)
360 
361  ! dilution
362  call aero_dist_interp_1d(scenario%aero_background, &
363  scenario%aero_dilution_time, scenario%aero_dilution_rate, &
364  env_state%elapsed_time, background, dilution_rate)
365  call aero_binned_add_aero_dist(background_binned, bin_grid, aero_data, &
366  background)
367  p = exp(- dilution_rate * delta_t)
368  if (env_state%height > old_env_state%height) then
369  p = p * old_env_state%height / env_state%height
370  end if
371  call aero_binned_scale(aero_binned, p)
372  call aero_binned_add_scaled(aero_binned, background_binned, 1d0 - p)
373 
374  end subroutine scenario_update_aero_binned
375 
376 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
377 
378  !> Evaluate a loss rate function.
379  real(kind=dp) function scenario_loss_rate(scenario, vol, density, &
380  aero_data, env_state)
381 
382  !> Scenario data.
383  type(scenario_t), intent(in) :: scenario
384  !> Volume of particle (m^3).
385  real(kind=dp), intent(in) :: vol
386  !> Density of particle (kg/m^3).
387  real(kind=dp), intent(in) :: density
388  !> Aerosol data.
389  type(aero_data_t), intent(in) :: aero_data
390  !> Environment state.
391  type(env_state_t), intent(in) :: env_state
392 
393  scenario_loss_rate = 0d0
394  if (scenario%loss_function_type == scenario_loss_function_invalid) then
395  scenario_loss_rate = 0d0
396  else if (scenario%loss_function_type == scenario_loss_function_none) then
397  scenario_loss_rate = 0d0
398  else if (scenario%loss_function_type == scenario_loss_function_constant) &
399  then
400  scenario_loss_rate = 1d-3
401  else if (scenario%loss_function_type == scenario_loss_function_volume) then
402  scenario_loss_rate = 1d15*vol
403  else if (scenario%loss_function_type == scenario_loss_function_drydep) &
404  then
406  aero_data, env_state)
407  else if (scenario%loss_function_type == scenario_loss_function_chamber) &
408  then
409  scenario_loss_rate = chamber_loss_rate_wall(scenario%chamber, vol, &
410  aero_data, env_state) &
411  + chamber_loss_rate_sedi(scenario%chamber, vol, density, &
412  aero_data, env_state)
413  else
414  call die_msg(201594391, "Unknown loss function id: " &
415  // trim(integer_to_string(scenario%loss_function_type)))
416  end if
417 
418  end function scenario_loss_rate
419 
420 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
421 
422  !> Compute and return the dry deposition rate for a given particle.
423  !! All equations used here are written in detail in the file
424  !! \c doc/deposition/deposition.tex.
425  real(kind=dp) function scenario_loss_rate_dry_dep(vol, density, aero_data, &
426  env_state)
427 
428  !> Particle volume (m^3).
429  real(kind=dp), intent(in) :: vol
430  !> Particle density (kg m^-3).
431  real(kind=dp), intent(in) :: density
432  !> Aerosol data.
433  type(aero_data_t), intent(in) :: aero_data
434  !> Environment state.
435  type(env_state_t), intent(in) :: env_state
436 
437  real(kind=dp) :: v_d
438  real(kind=dp) :: v_s
439  real(kind=dp) :: d_p
440  real(kind=dp) :: density_air
441  real(kind=dp) :: visc_d, visc_k
442  real(kind=dp) :: gas_speed, gas_mean_free_path
443  real(kind=dp) :: knud, cunning
444  real(kind=dp) :: grav
445  real(kind=dp) :: r_s, r_a
446  real(kind=dp) :: alpha, beta, gamma, a, eps_0
447  real(kind=dp) :: diff_p
448  real(kind=dp) :: von_karman
449  real(kind=dp) :: st, sc, u_star
450  real(kind=dp) :: e_b, e_im, e_in, r1
451  real(kind=dp) :: u_mean, z_ref, z_rough
452 
453  ! User set variables
454  u_mean = 5.0d0 ! Mean wind speed at reference height
455  z_ref = 20.0d0 ! Reference height
456  ! Setting for LUC = 7, SC = 1 - See Table 3
457  z_rough = .1d0 ! According to land category
458  a = 2.0d0 / 1000.0d0 ! Dependent on land type
459  alpha = 1.2d0 ! From table
460  beta = 2.0d0 ! From text
461  gamma = .54d0 ! From table
462 
463  ! particle diameter
464  d_p = aero_data_vol2diam(aero_data, vol)
465  ! density of air
466  density_air = (const%air_molec_weight * env_state%pressure) &
467  / (const%univ_gas_const * env_state%temp)
468  ! dynamic viscosity
469  visc_d = 1.8325d-5 * (416.16 / (env_state%temp + 120.0d0)) &
470  * (env_state%temp / 296.16)**1.5d0
471  ! kinematic viscosity
472  visc_k = visc_d / density_air
473  ! gas speed
474  gas_speed = &
475  sqrt((8.0d0 * const%boltzmann * env_state%temp * const%avagadro) / &
476  (const%pi * const%air_molec_weight))
477  ! gas free path
478  gas_mean_free_path = (2.0d0 * visc_d) / (density_air * gas_speed)
479  ! knudson number
480  knud = (2.0d0 * gas_mean_free_path) / d_p
481  ! cunningham correction factor
482  cunning = 1.0d0 + knud * (1.257d0 + 0.4d0 * exp(-1.1d0 / knud))
483  ! gravity
484  grav = 9.81d0
485  ! Compute V_s
486  v_s = (density * d_p**2.0d0 * grav * cunning) / (18.0d0 * visc_d)
487 
488  ! Aerodynamic resistance
489  ! For neutral stability
490  u_star = .4d0 * u_mean / log(z_ref / z_rough)
491  r_a = (1.0d0 / (.4d0 * u_star)) * log(z_ref / z_rough)
492  ! Brownian diffusion efficiency
493  diff_p = (const%boltzmann * env_state%temp * cunning) / &
494  (3.d0 * const%pi * visc_d * d_p)
495  sc = visc_k / diff_p
496  e_b = sc**(-gamma)
497 
498  ! Interception efficiency
499  ! Characteristic radius of large collectors
500  e_in = .5d0 * (d_p / a)**2.0d0
501 
502  ! Impaction efficiency
503  st = (v_s * u_star) / (grav * a)
504  e_im = (st / (alpha + st))**beta
505 
506  ! Rebound correction
507  r1 = exp(-st**.5d0)
508 
509  ! Surface resistance
510  eps_0 = 3.0d0 ! Taken to be 3
511  r_s = 1.0d0 / (eps_0 * u_star * (e_b + e_in + e_im) * r1)
512 
513  ! Dry deposition
514  v_d = v_s + (1.0d0 / (r_a + r_s + r_a * r_s * v_s))
515 
516  ! The loss rate
517  scenario_loss_rate_dry_dep = v_d / env_state%height
518 
519  end function scenario_loss_rate_dry_dep
520 
521 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
522 
523  !> Compute and return the max loss rate function for a given volume.
524  real(kind=dp) function scenario_loss_rate_max(scenario, vol, aero_data, &
525  env_state)
526 
527  !> Scenario data.
528  type(scenario_t), intent(in) :: scenario
529  !> Particle volume (m^3).
530  real(kind=dp), intent(in) :: vol
531  !> Aerosol data.
532  type(aero_data_t), intent(in) :: aero_data
533  !> Environment state.
534  type(env_state_t), intent(in) :: env_state
535 
536  !> Number of density sample points.
537  integer, parameter :: n_sample = 3
538 
539  real(kind=dp) :: d, d_min, d_max, loss
540  integer :: i
541 
542  d_min = minval(aero_data%density)
543  d_max = maxval(aero_data%density)
544 
546  do i = 1,n_sample
547  d = interp_linear_disc(d_min, d_max, n_sample, i)
548  loss = scenario_loss_rate(scenario, vol, d, aero_data, env_state)
550  end do
551 
552  end function scenario_loss_rate_max
553 
554 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
555 
556  !> Compute an upper bound on the maximum kernel value for each
557  !> bin.
558  !! Value over_scale is multiplied to the maximum sampled value
559  !! to get the upper bound. A tighter bound may be reached if over_scale
560  !! is smaller, but that also risks falling below a kernel value.
561  subroutine scenario_loss_rate_bin_max(scenario, bin_grid, aero_data, &
562  env_state, loss_max)
563 
564  !> Scenario data.
565  type(scenario_t), intent(in) :: scenario
566  !> Bin_grid.
567  type(bin_grid_t), intent(in) :: bin_grid
568  !> Aerosol data.
569  type(aero_data_t), intent(in) :: aero_data
570  !> Environment state.
571  type(env_state_t), intent(in) :: env_state
572  !> Maximum loss vals.
573  real(kind=dp), intent(out) :: loss_max(bin_grid_size(bin_grid))
574 
575  !> Number of sample points per bin.
576  integer, parameter :: n_sample = 3
577  !> Over-estimation scale factor parameter.
578  real(kind=dp), parameter :: over_scale = 2d0
579 
580  real(kind=dp) :: v_low, v_high, vol, r, r_max
581  integer :: b, i
582 
583  do b = 1,bin_grid_size(bin_grid)
584  v_low = aero_data_rad2vol(aero_data, bin_grid%edges(b))
585  v_high = aero_data_rad2vol(aero_data, bin_grid%edges(b + 1))
586  r_max = 0d0
587  do i = 1,n_sample
588  vol = interp_linear_disc(v_low, v_high, n_sample, i)
589  r = scenario_loss_rate_max(scenario, vol, aero_data, env_state)
590  r_max = max(r_max, r)
591  end do
592  loss_max(b) = r_max * over_scale
593  end do
594 
595  end subroutine scenario_loss_rate_bin_max
596 
597 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
598 
599  !> Performs stochastic particle loss for one time-step.
600  !! If a particle \c i_part has a scenario_loss_rate() value of rate, then the
601  !! probability p will be removed by this function is
602  !! <tt>1 - exp(-delta_t*rate)</tt>.
603  !! Uses an accept-reject algorithm for efficiency, in which a particle
604  !! is first sampled with rate <tt>1 - exp(-delta_t*over_rate) </tt>
605  !! and then accepted with rate
606  !! <tt>(1 - exp(-delta_t*rate))/(1 - exp(-delta_t*over_rate))</tt>.
607  subroutine scenario_particle_loss(scenario, delta_t, aero_data, aero_state, &
608  env_state)
609 
610  !> Scenario data.
611  type(scenario_t), intent(in) :: scenario
612  !> Time increment to update over.
613  real(kind=dp), intent(in) :: delta_t
614  !> Aerosol data.
615  type(aero_data_t), intent(in) :: aero_data
616  !> Aerosol state.
617  type(aero_state_t), intent(inout) :: aero_state
618  !> Environment state.
619  type(env_state_t), intent(in) :: env_state
620 
621  integer :: c, b, s, i_part
622  real(kind=dp) :: over_rate, over_prob, rand_real, rand_geom
623 
624  if (scenario%loss_function_type == scenario_loss_function_none .or. &
625  scenario%loss_function_type == scenario_loss_function_invalid) return
626 
627  if (scenario_loss_alg_threshold <= 0d0) then
628  ! use naive algorithm for everything
629  do i_part = aero_state%apa%n_part, 1, -1
630  call scenario_try_single_particle_loss(scenario, delta_t, &
631  aero_data, aero_state, env_state, i_part, 1d0)
632  end do
633  return
634  end if
635 
636  call aero_state_sort(aero_state, aero_data)
637 
638  if (.not. aero_state%aero_sorted%removal_rate_bounds_valid) then
639  call scenario_loss_rate_bin_max(scenario, &
640  aero_state%aero_sorted%bin_grid, aero_data, env_state, &
641  aero_state%aero_sorted%removal_rate_max)
642  aero_state%aero_sorted%removal_rate_bounds_valid = .true.
643  end if
644 
645  do c = 1,aero_sorted_n_class(aero_state%aero_sorted)
646  do b = 1,aero_sorted_n_bin(aero_state%aero_sorted)
647  over_rate = aero_state%aero_sorted%removal_rate_max(b)
648  if (delta_t * over_rate <= 0d0) cycle
649  over_prob = 1d0 - exp(-delta_t * over_rate)
650  if (over_prob >= scenario_loss_alg_threshold) then
651  ! use naive algorithm over bin
652  do s = aero_state%aero_sorted%size_class%inverse(b, c)%n_entry, &
653  1,-1
654  i_part = &
655  aero_state%aero_sorted%size_class%inverse(b, c)%entry(s)
656  call scenario_try_single_particle_loss(scenario, delta_t, &
657  aero_data, aero_state, env_state, i_part, 1d0)
658  end do
659  else
660  ! use accept-reject algorithm over bin
661  s = aero_state%aero_sorted%size_class%inverse(b, c)%n_entry + 1
662  do while (.true.)
663  rand_real = pmc_random()
664  if (rand_real <= 0d0) exit
665  rand_geom = -log(rand_real) / (delta_t * over_rate) + 1d0
666  if (rand_geom >= real(s, kind=dp)) exit
667  s = s - floor(rand_geom)
668 
669  ! note: floor(rand_geom) is a random geometric variable
670  ! with accept probability 1 - exp(-delta_t*over_rate)
671 
672  i_part = &
673  aero_state%aero_sorted%size_class%inverse(b, c)%entry(s)
674  call scenario_try_single_particle_loss(scenario, delta_t, &
675  aero_data, aero_state, env_state, i_part, over_prob)
676  end do
677  end if
678  end do
679  end do
680 
681  !call aero_state_check_sort(aero_state)
682 
683  end subroutine scenario_particle_loss
684 
685 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
686 
687  !> Test a candidate particle to see if it should be removed,
688  !> and remove if necessary.
689  !! Particle is removed with probability
690  !! (1d0 - exp(-delta_t*rate))/over_prob, where rate is the loss function
691  !! evaluated for the given particle.
692  subroutine scenario_try_single_particle_loss(scenario, delta_t, &
693  aero_data, aero_state, env_state, i_part, over_prob)
694 
695  !> Scenario data.
696  type(scenario_t), intent(in) :: scenario
697  !> Time increment to update over.
698  real(kind=dp), intent(in) :: delta_t
699  !> Aerosol data.
700  type(aero_data_t), intent(in) :: aero_data
701  !> Aerosol state.
702  type(aero_state_t), intent(inout) :: aero_state
703  !> Environment state.
704  type(env_state_t), intent(in) :: env_state
705  !> Index of particle to attempt removal
706  integer, intent(in) :: i_part
707  !> Overestimated removal probability used previously
708  real(kind=dp), intent(in) :: over_prob
709 
710  real(kind=dp) :: prob, rate, vol, density
711  type(aero_info_t) :: aero_info
712 
713  vol = aero_particle_volume(aero_state%apa%particle(i_part))
714  density = aero_particle_density(aero_state%apa%particle(i_part), aero_data)
715  rate = scenario_loss_rate(scenario, vol, density, aero_data, env_state)
716  prob = 1d0 - exp(-delta_t * rate)
717  call warn_assert_msg(295846288, prob <= over_prob, &
718  "particle loss upper bound estimation is too tight: " &
719  // trim(real_to_string(prob)) // " > " &
720  // trim(real_to_string(over_prob)) )
721  if (pmc_random() * over_prob > prob) return
722 
723  aero_info%id = aero_state%apa%particle(i_part)%id
724  aero_info%action = aero_info_dilution
725  aero_info%other_id = 0
726  call aero_state_remove_particle_with_info(aero_state, i_part, aero_info)
727 
728  end subroutine scenario_try_single_particle_loss
729 
730 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
731 
732  !> Whether any of the contained aerosol modes are of the given type.
733  elemental logical function scenario_contains_aero_mode_type(scenario, &
734  aero_mode_type)
735 
736  !> Scenario data.
737  type(scenario_t), intent(in) :: scenario
738  !> Aerosol mode type to test for.
739  integer, intent(in) :: aero_mode_type
740 
742  = any(aero_dist_contains_aero_mode_type(scenario%aero_emission, &
743  aero_mode_type)) &
744  .or. any(aero_dist_contains_aero_mode_type(scenario%aero_background, &
745  aero_mode_type))
746 
748 
749 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
750 
751  !> Read environment data from an spec file.
752  subroutine spec_file_read_scenario(file, gas_data, aero_data, scenario)
753 
754  !> Spec file.
755  type(spec_file_t), intent(inout) :: file
756  !> Gas data values.
757  type(gas_data_t), intent(in) :: gas_data
758  !> Aerosol data.
759  type(aero_data_t), intent(inout) :: aero_data
760  !> Scenario data.
761  type(scenario_t), intent(inout) :: scenario
762 
763  character(len=PMC_MAX_FILENAME_LEN) :: sub_filename
764  type(spec_file_t) :: sub_file
765  character(len=SPEC_LINE_MAX_VAR_LEN) :: function_name
766 
767  ! note that we have to hard-code the list for doxygen below
768 
769  !> \page input_format_scenario Input File Format: Scenario
770  !!
771  !! The scenario parameters are:
772  !! <ul>
773  !! <li> \b temp_profile (string): the name of the file from which to
774  !! read the temperature profile --- the file format should be
775  !! \subpage input_format_temp_profile
776  !! <li> \b pressure_profile (string): the name of the file from which to
777  !! read the pressure profile --- the file format should be
778  !! \subpage input_format_pressure_profile
779  !! <li> \b height_profile (string): the name of the file from which
780  !! to read the mixing layer height profile --- the file format
781  !! should be \subpage input_format_height_profile
782  !! <li> \b gas_emissions (string): the name of the file from which to
783  !! read the gas emissions profile --- the file format should be
784  !! \subpage input_format_gas_profile
785  !! <li> \b gas_background (string): the name of the file from which
786  !! to read the gas background profile --- the file format should
787  !! be \subpage input_format_gas_profile
788  !! <li> \b aero_emissions (string): the name of the file from which
789  !! to read the aerosol emissions profile --- the file format
790  !! should be \subpage input_format_aero_dist_profile
791  !! <li> \b aero_background (string): the name of the file from which
792  !! to read the aerosol background profile --- the file format
793  !! should be \subpage input_format_aero_dist_profile
794  !! <li> \b loss_function (string): the type of loss function ---
795  !! must be one of: \c none for no particle loss, \c constant
796  !! for constant loss rate, \c volume for particle loss proportional
797  !! to particle volume, \c drydep for particle loss proportional
798  !! to dry deposition velocity, or \c chamber for a chamber model.
799  !! If \c loss_function is \c chamber, then the following
800  !! parameters must also be provided:
801  !! - \subpage input_format_chamber
802  !! </ul>
803  !!
804  !! See also:
805  !! - \ref spec_file_format --- the input file text format
806 
807  ! temperature profile
808  call spec_file_read_string(file, "temp_profile", sub_filename)
809  call spec_file_open(sub_filename, sub_file)
810  call spec_file_read_timed_real_array(sub_file, "temp", &
811  scenario%temp_time, scenario%temp)
812  call spec_file_close(sub_file)
813 
814  ! pressure profile
815  call spec_file_read_string(file, "pressure_profile", sub_filename)
816  call spec_file_open(sub_filename, sub_file)
817  call spec_file_read_timed_real_array(sub_file, "pressure", &
818  scenario%pressure_time, scenario%pressure)
819  call spec_file_close(sub_file)
820 
821  ! height profile
822  call spec_file_read_string(file, "height_profile", sub_filename)
823  call spec_file_open(sub_filename, sub_file)
824  call spec_file_read_timed_real_array(sub_file, "height", &
825  scenario%height_time, scenario%height)
826  call spec_file_close(sub_file)
827 
828  ! gas emissions profile
829  call spec_file_read_string(file, "gas_emissions", sub_filename)
830  call spec_file_open(sub_filename, sub_file)
831  call spec_file_read_gas_states_times_rates(sub_file, gas_data, &
832  scenario%gas_emission_time, scenario%gas_emission_rate_scale, &
833  scenario%gas_emission)
834  call spec_file_close(sub_file)
835 
836  ! gas background profile
837  call spec_file_read_string(file, "gas_background", sub_filename)
838  call spec_file_open(sub_filename, sub_file)
839  call spec_file_read_gas_states_times_rates(sub_file, gas_data, &
840  scenario%gas_dilution_time, scenario%gas_dilution_rate, &
841  scenario%gas_background)
842  call spec_file_close(sub_file)
843 
844  ! aerosol emissions profile
845  call spec_file_read_string(file, "aero_emissions", sub_filename)
846  call spec_file_open(sub_filename, sub_file)
847  call spec_file_read_aero_dists_times_rates(sub_file, aero_data, &
848  scenario%aero_emission_time, scenario%aero_emission_rate_scale, &
849  scenario%aero_emission)
850  call spec_file_close(sub_file)
851 
852  ! aerosol background profile
853  call spec_file_read_string(file, "aero_background", sub_filename)
854  call spec_file_open(sub_filename, sub_file)
855  call spec_file_read_aero_dists_times_rates(sub_file, aero_data, &
856  scenario%aero_dilution_time, scenario%aero_dilution_rate, &
857  scenario%aero_background)
858  call spec_file_close(sub_file)
859 
860  ! loss function
861  call spec_file_read_string(file, 'loss_function', function_name)
862  if (trim(function_name) == 'none') then
863  scenario%loss_function_type = scenario_loss_function_none
864  else if (trim(function_name) == 'constant') then
865  scenario%loss_function_type = scenario_loss_function_constant
866  else if (trim(function_name) == 'volume') then
867  scenario%loss_function_type = scenario_loss_function_volume
868  else if (trim(function_name) == 'drydep') then
869  scenario%loss_function_type = scenario_loss_function_drydep
870  else if (trim(function_name) == 'chamber') then
871  scenario%loss_function_type = scenario_loss_function_chamber
872  call spec_file_read_chamber(file, scenario%chamber)
873  else
874  call spec_file_die_msg(518248400, file, &
875  "Unknown loss function type: " // trim(function_name))
876  end if
877 
878  end subroutine spec_file_read_scenario
879 
880  ! the following blocks belong in the subroutine above, but they are
881  ! outside because Doxygen 1.8.7 doesn't resolve references when
882  ! multiple \page blocks are in one subroutine
883 
884  !> \page input_format_temp_profile Input File Format: Temperature Profile
885  !!
886  !! A temperature profile input file must consist of two lines:
887  !! - the first line must begin with \c time and should be followed
888  !! by \f$N\f$ space-separated real scalars, giving the times (in
889  !! s after the start of the simulation) of the temperature set
890  !! points --- the times must be in increasing order
891  !! - the second line must begin with \c temp and should be followed
892  !! by \f$N\f$ space-separated real scalars, giving the
893  !! temperatures (in K) at the corresponding times
894  !!
895  !! The temperature profile is linearly interpolated between the
896  !! specified times, while before the first time it takes the first
897  !! temperature value and after the last time it takes the last
898  !! temperature value.
899  !!
900  !! Example:
901  !! <pre>
902  !! time 0 600 1800 # time (in s) after simulation start
903  !! temp 270 290 280 # temperature (in K)
904  !! </pre>
905  !! Here the temperature starts at 270&nbsp;K at the start of the
906  !! simulation, rises to 290&nbsp;K after 10&nbsp;min, and then
907  !! falls again to 280&nbsp;K at 30&nbsp;min. Between these times
908  !! the temperature is linearly interpolated, while after
909  !! 30&nbsp;min it is held constant at 280&nbsp;K.
910  !!
911  !! See also:
912  !! - \ref spec_file_format --- the input file text format
913  !! - \ref input_format_scenario --- the environment data
914  !! containing the temperature profile
915 
916  !> \page input_format_pressure_profile Input File Format: Pressure Profile
917  !!
918  !! A pressure profile input file must consist of two lines:
919  !! - the first line must begin with \c time and should be followed
920  !! by \f$N\f$ space-separated real scalars, giving the times (in
921  !! s after the start of the simulation) of the pressure set
922  !! points --- the times must be in increasing order
923  !! - the second line must begin with \c pressure and should be followed
924  !! by \f$N\f$ space-separated real scalars, giving the
925  !! pressures (in Pa) at the corresponding times
926  !!
927  !! The pressure profile is linearly interpolated between the
928  !! specified times, while before the first time it takes the first
929  !! pressure value and after the last time it takes the last
930  !! pressure value.
931  !!
932  !! Example:
933  !! <pre>
934  !! time 0 600 1800 # time (in s) after simulation start
935  !! pressure 1e5 9e4 7.5e4 # pressure (in Pa)
936  !! </pre>
937  !! Here the pressure starts at 1e5&nbsp;Pa at the start of the
938  !! simulation, decreases to 9e4&nbsp;Pa after 10&nbsp;min, and then
939  !! decreases further to 7.5e4&nbsp;Pa at 30&nbsp;min. Between these times
940  !! the pressure is linearly interpolated, while after
941  !! 30&nbsp;min it is held constant at 7.5e4&nbsp;Pa.
942  !!
943  !! See also:
944  !! - \ref spec_file_format --- the input file text format
945  !! - \ref input_format_scenario --- the environment data
946  !! containing the pressure profile
947 
948  !> \page input_format_height_profile Input File Format: Mixing Layer Height Profile
949  !!
950  !! A mixing layer height profile input file must consist of two
951  !! lines:
952  !! - the first line must begin with \c time and should be followed
953  !! by \f$N\f$ space-separated real scalars, giving the times (in
954  !! s after the start of the simulation) of the height set
955  !! points --- the times must be in increasing order
956  !! - the second line must begin with \c height and should be
957  !! followed by \f$N\f$ space-separated real scalars, giving the
958  !! mixing layer heights (in m) at the corresponding times
959  !!
960  !! The mixing layer height profile is linearly interpolated
961  !! between the specified times, while before the first time it
962  !! takes the first height value and after the last time it takes
963  !! the last height value.
964  !!
965  !! Example:
966  !! <pre>
967  !! time 0 600 1800 # time (in s) after simulation start
968  !! height 500 1000 800 # mixing layer height (in m)
969  !! </pre>
970  !! Here the mixing layer height starts at 500&nbsp;m at the start
971  !! of the simulation, rises to 1000&nbsp;m after 10&nbsp;min, and
972  !! then falls again to 800&nbsp;m at 30&nbsp;min. Between these
973  !! times the mixing layer height is linearly interpolated, while
974  !! after 30&nbsp;min it is held constant at 800&nbsp;m.
975  !!
976  !! See also:
977  !! - \ref spec_file_format --- the input file text format
978  !! - \ref input_format_scenario --- the environment data
979  !! containing the mixing layer height profile
980 
981 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
982 
983  !> Determines the number of bytes required to pack the given value.
984  integer function pmc_mpi_pack_size_scenario(val)
985 
986  !> Value to pack.
987  type(scenario_t), intent(in) :: val
988 
989  integer :: total_size, i, n
990 
991  total_size = &
992  pmc_mpi_pack_size_real_array(val%temp_time) &
993  + pmc_mpi_pack_size_real_array(val%temp) &
994  + pmc_mpi_pack_size_real_array(val%pressure_time) &
995  + pmc_mpi_pack_size_real_array(val%pressure) &
996  + pmc_mpi_pack_size_real_array(val%height_time) &
997  + pmc_mpi_pack_size_real_array(val%height) &
998  + pmc_mpi_pack_size_real_array(val%gas_emission_time) &
999  + pmc_mpi_pack_size_real_array(val%gas_emission_rate_scale) &
1000  + pmc_mpi_pack_size_real_array(val%gas_dilution_time) &
1001  + pmc_mpi_pack_size_real_array(val%gas_dilution_rate) &
1002  + pmc_mpi_pack_size_real_array(val%aero_emission_time) &
1003  + pmc_mpi_pack_size_real_array(val%aero_emission_rate_scale) &
1004  + pmc_mpi_pack_size_real_array(val%aero_dilution_time) &
1005  + pmc_mpi_pack_size_real_array(val%aero_dilution_rate) &
1006  + pmc_mpi_pack_size_integer(val%loss_function_type) &
1007  + pmc_mpi_pack_size_chamber(val%chamber)
1008  if (allocated(val%gas_emission_time)) then
1009  do i = 1,size(val%gas_emission)
1010  total_size = total_size &
1011  + pmc_mpi_pack_size_gas_state(val%gas_emission(i))
1012  end do
1013  end if
1014  if (allocated(val%gas_dilution_time)) then
1015  do i = 1,size(val%gas_background)
1016  total_size = total_size &
1017  + pmc_mpi_pack_size_gas_state(val%gas_background(i))
1018  end do
1019  end if
1020  if (allocated(val%aero_emission_time)) then
1021  do i = 1,size(val%aero_emission)
1022  total_size = total_size &
1023  + pmc_mpi_pack_size_aero_dist(val%aero_emission(i))
1024  end do
1025  end if
1026  if (allocated(val%aero_dilution_time)) then
1027  do i = 1,size(val%aero_background)
1028  total_size = total_size &
1029  + pmc_mpi_pack_size_aero_dist(val%aero_background(i))
1030  end do
1031  end if
1032 
1033  pmc_mpi_pack_size_scenario = total_size
1034 
1035  end function pmc_mpi_pack_size_scenario
1036 
1037 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1038 
1039  !> Packs the given value into the buffer, advancing position.
1040  subroutine pmc_mpi_pack_scenario(buffer, position, val)
1041 
1042  !> Memory buffer.
1043  character, intent(inout) :: buffer(:)
1044  !> Current buffer position.
1045  integer, intent(inout) :: position
1046  !> Value to pack.
1047  type(scenario_t), intent(in) :: val
1048 
1049 #ifdef PMC_USE_MPI
1050  integer :: prev_position, i
1051 
1052  prev_position = position
1053  call pmc_mpi_pack_real_array(buffer, position, val%temp_time)
1054  call pmc_mpi_pack_real_array(buffer, position, val%temp)
1055  call pmc_mpi_pack_real_array(buffer, position, val%pressure_time)
1056  call pmc_mpi_pack_real_array(buffer, position, val%pressure)
1057  call pmc_mpi_pack_real_array(buffer, position, val%height_time)
1058  call pmc_mpi_pack_real_array(buffer, position, val%height)
1059  call pmc_mpi_pack_real_array(buffer, position, val%gas_emission_time)
1060  call pmc_mpi_pack_real_array(buffer, position, val%gas_emission_rate_scale)
1061  call pmc_mpi_pack_real_array(buffer, position, val%gas_dilution_time)
1062  call pmc_mpi_pack_real_array(buffer, position, val%gas_dilution_rate)
1063  call pmc_mpi_pack_real_array(buffer, position, val%aero_emission_time)
1064  call pmc_mpi_pack_real_array(buffer, position, &
1065  val%aero_emission_rate_scale)
1066  call pmc_mpi_pack_real_array(buffer, position, val%aero_dilution_time)
1067  call pmc_mpi_pack_real_array(buffer, position, val%aero_dilution_rate)
1068  call pmc_mpi_pack_integer(buffer, position, val%loss_function_type)
1069  call pmc_mpi_pack_chamber(buffer, position, val%chamber)
1070  if (allocated(val%gas_emission_time)) then
1071  do i = 1,size(val%gas_emission)
1072  call pmc_mpi_pack_gas_state(buffer, position, val%gas_emission(i))
1073  end do
1074  end if
1075  if (allocated(val%gas_dilution_time)) then
1076  do i = 1,size(val%gas_background)
1077  call pmc_mpi_pack_gas_state(buffer, position, val%gas_background(i))
1078  end do
1079  end if
1080  if (allocated(val%aero_emission_time)) then
1081  do i = 1,size(val%aero_emission)
1082  call pmc_mpi_pack_aero_dist(buffer, position, val%aero_emission(i))
1083  end do
1084  end if
1085  if (allocated(val%aero_dilution_time)) then
1086  do i = 1,size(val%aero_background)
1087  call pmc_mpi_pack_aero_dist(buffer, position, val%aero_background(i))
1088  end do
1089  end if
1090  call assert(639466930, &
1091  position - prev_position <= pmc_mpi_pack_size_scenario(val))
1092 #endif
1093 
1094  end subroutine pmc_mpi_pack_scenario
1095 
1096 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1097 
1098  !> Unpacks the given value from the buffer, advancing position.
1099  subroutine pmc_mpi_unpack_scenario(buffer, position, val)
1100 
1101  !> Memory buffer.
1102  character, intent(inout) :: buffer(:)
1103  !> Current buffer position.
1104  integer, intent(inout) :: position
1105  !> Value to pack.
1106  type(scenario_t), intent(inout) :: val
1107 
1108 #ifdef PMC_USE_MPI
1109  integer :: prev_position, i
1110 
1111  prev_position = position
1112  call pmc_mpi_unpack_real_array(buffer, position, val%temp_time)
1113  call pmc_mpi_unpack_real_array(buffer, position, val%temp)
1114  call pmc_mpi_unpack_real_array(buffer, position, val%pressure_time)
1115  call pmc_mpi_unpack_real_array(buffer, position, val%pressure)
1116  call pmc_mpi_unpack_real_array(buffer, position, val%height_time)
1117  call pmc_mpi_unpack_real_array(buffer, position, val%height)
1118  call pmc_mpi_unpack_real_array(buffer, position, val%gas_emission_time)
1119  call pmc_mpi_unpack_real_array(buffer, position, &
1120  val%gas_emission_rate_scale)
1121  call pmc_mpi_unpack_real_array(buffer, position, val%gas_dilution_time)
1122  call pmc_mpi_unpack_real_array(buffer, position, val%gas_dilution_rate)
1123  call pmc_mpi_unpack_real_array(buffer, position, val%aero_emission_time)
1124  call pmc_mpi_unpack_real_array(buffer, position, &
1125  val%aero_emission_rate_scale)
1126  call pmc_mpi_unpack_real_array(buffer, position, val%aero_dilution_time)
1127  call pmc_mpi_unpack_real_array(buffer, position, val%aero_dilution_rate)
1128  call pmc_mpi_unpack_integer(buffer, position, val%loss_function_type)
1129  call pmc_mpi_unpack_chamber(buffer, position, val%chamber)
1130  if (allocated(val%gas_emission)) deallocate(val%gas_emission)
1131  if (allocated(val%gas_background)) deallocate(val%gas_background)
1132  if (allocated(val%aero_emission)) deallocate(val%aero_emission)
1133  if (allocated(val%aero_background)) deallocate(val%aero_background)
1134  if (allocated(val%gas_emission_time)) then
1135  allocate(val%gas_emission(size(val%gas_emission_time)))
1136  do i = 1,size(val%gas_emission)
1137  call pmc_mpi_unpack_gas_state(buffer, position, val%gas_emission(i))
1138  end do
1139  end if
1140  if (allocated(val%gas_dilution_time)) then
1141  allocate(val%gas_background(size(val%gas_dilution_time)))
1142  do i = 1,size(val%gas_background)
1143  call pmc_mpi_unpack_gas_state(buffer, position, &
1144  val%gas_background(i))
1145  end do
1146  end if
1147  if (allocated(val%aero_emission_time)) then
1148  allocate(val%aero_emission(size(val%aero_emission_time)))
1149  do i = 1,size(val%aero_emission)
1150  call pmc_mpi_unpack_aero_dist(buffer, position, val%aero_emission(i))
1151  end do
1152  end if
1153  if (allocated(val%aero_dilution_time)) then
1154  allocate(val%aero_background(size(val%aero_dilution_time)))
1155  do i = 1,size(val%aero_background)
1156  call pmc_mpi_unpack_aero_dist(buffer, position, &
1157  val%aero_background(i))
1158  end do
1159  end if
1160  call assert(611542570, &
1161  position - prev_position <= pmc_mpi_pack_size_scenario(val))
1162 #endif
1163 
1164  end subroutine pmc_mpi_unpack_scenario
1165 
1166 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1167 
1168 end module pmc_scenario
pmc_scenario::scenario_contains_aero_mode_type
elemental logical function scenario_contains_aero_mode_type(scenario, aero_mode_type)
Whether any of the contained aerosol modes are of the given type.
Definition: scenario.F90:735
pmc_scenario::scenario_update_aero_binned
subroutine scenario_update_aero_binned(scenario, delta_t, env_state, old_env_state, bin_grid, aero_data, aero_binned)
Do emissions and background dilution from the environment for a binned aerosol distribution.
Definition: scenario.F90:332
pmc_gas_data::gas_data_t
Constant gas data.
Definition: gas_data.F90:35
pmc_mpi
Wrapper functions for MPI.
Definition: mpi.F90:13
pmc_scenario::scenario_loss_rate
real(kind=dp) function scenario_loss_rate(scenario, vol, density, aero_data, env_state)
Evaluate a loss rate function.
Definition: scenario.F90:381
pmc_spec_file::spec_file_close
subroutine spec_file_close(file)
Close a spec file.
Definition: spec_file.F90:135
pmc_gas_state::gas_state_ensure_nonnegative
subroutine gas_state_ensure_nonnegative(gas_state)
Set any negative values to zero.
Definition: gas_state.F90:167
pmc_aero_dist::pmc_mpi_pack_aero_dist
subroutine pmc_mpi_pack_aero_dist(buffer, position, val)
Packs the given value into the buffer, advancing position.
Definition: aero_dist.F90:419
pmc_gas_state::gas_state_mole_dens_to_ppb
subroutine gas_state_mole_dens_to_ppb(gas_state, env_state)
Convert (mol m^{-3}) to (ppb).
Definition: gas_state.F90:181
pmc_scenario::scenario_loss_function_volume
integer, parameter scenario_loss_function_volume
Type code for a loss rate function proportional to particle volume.
Definition: scenario.F90:32
pmc_scenario
The scenario_t structure and associated subroutines.
Definition: scenario.F90:9
pmc_scenario::scenario_t
Scenario data.
Definition: scenario.F90:54
pmc_gas_data
The gas_data_t structure and associated subroutines.
Definition: gas_data.F90:9
pmc_chamber::pmc_mpi_pack_chamber
subroutine pmc_mpi_pack_chamber(buffer, position, val)
Packs the given value into the buffer, advancing position.
Definition: chamber.F90:206
pmc_scenario::pmc_mpi_pack_scenario
subroutine pmc_mpi_pack_scenario(buffer, position, val)
Packs the given value into the buffer, advancing position.
Definition: scenario.F90:1041
pmc_util::warn_assert_msg
subroutine warn_assert_msg(code, condition_ok, warning_msg)
Prints a warning message if condition_ok is false.
Definition: util.F90:59
pmc_util::die_msg
subroutine die_msg(code, error_msg)
Error immediately.
Definition: util.F90:134
pmc_scenario::scenario_loss_function_constant
integer, parameter scenario_loss_function_constant
Type code for a constant loss function.
Definition: scenario.F90:30
pmc_scenario::scenario_loss_function_none
integer, parameter scenario_loss_function_none
Type code for a zero loss function.
Definition: scenario.F90:28
pmc_constants::dp
integer, parameter dp
Kind of a double precision real number.
Definition: constants.F90:12
pmc_aero_dist::aero_dist_interp_1d
subroutine aero_dist_interp_1d(aero_dist_list, time_list, rate_list, time, aero_dist, rate)
Determine the current aero_dist and rate by interpolating at the current time with the lists of aero_...
Definition: aero_dist.F90:177
pmc_spec_file
Reading formatted text input.
Definition: spec_file.F90:43
pmc_gas_state::gas_state_add_scaled
subroutine gas_state_add_scaled(gas_state, gas_state_delta, alpha)
Adds the given gas_state_delta scaled by alpha.
Definition: gas_state.F90:124
pmc_util::interp_1d
real(kind=dp) function interp_1d(x_vals, y_vals, x)
1D linear interpolation.
Definition: util.F90:626
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_chamber::pmc_mpi_pack_size_chamber
integer function pmc_mpi_pack_size_chamber(val)
Determines the number of bytes required to pack the given value.
Definition: chamber.F90:187
pmc_scenario::pmc_mpi_unpack_scenario
subroutine pmc_mpi_unpack_scenario(buffer, position, val)
Unpacks the given value from the buffer, advancing position.
Definition: scenario.F90:1100
pmc_env_state::env_state_t
Current environment state.
Definition: env_state.F90:29
pmc_util::assert
subroutine assert(code, condition_ok)
Errors unless condition_ok is true.
Definition: util.F90:103
pmc_scenario::scenario_update_gas_state
subroutine scenario_update_gas_state(scenario, delta_t, env_state, old_env_state, gas_data, gas_state)
Do gas emissions and background dilution.
Definition: scenario.F90:209
pmc_scenario::scenario_update_aero_state
subroutine scenario_update_aero_state(scenario, delta_t, env_state, old_env_state, aero_data, aero_state, n_emit, n_dil_in, n_dil_out, allow_doubling, allow_halving)
Do emissions and background dilution for a particle aerosol distribution.
Definition: scenario.F90:259
pmc_aero_state
The aero_state_t structure and assocated subroutines.
Definition: aero_state.F90:9
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_scenario::scenario_loss_rate_max
real(kind=dp) function scenario_loss_rate_max(scenario, vol, aero_data, env_state)
Compute and return the max loss rate function for a given volume.
Definition: scenario.F90:526
pmc_util::interp_linear_disc
real(kind=dp) function interp_linear_disc(x_1, x_n, n, i)
Linear interpolation over discrete indices.
Definition: util.F90:663
pmc_scenario::scenario_update_env_state
subroutine scenario_update_env_state(scenario, env_state, time)
Update time-dependent contents of the environment. scenario_init_env_state() should have been called ...
Definition: scenario.F90:134
pmc_spec_file::spec_file_t
An input file with extra data for printing messages.
Definition: spec_file.F90:59
pmc_scenario::scenario_loss_rate_bin_max
subroutine scenario_loss_rate_bin_max(scenario, bin_grid, aero_data, env_state, loss_max)
Compute an upper bound on the maximum kernel value for each bin. Value over_scale is multiplied to th...
Definition: scenario.F90:563
pmc_scenario::scenario_loss_alg_threshold
real(kind=dp), parameter scenario_loss_alg_threshold
Parameter to switch between algorithms for particle loss. A value of 0 will always use the naive algo...
Definition: scenario.F90:41
pmc_gas_state
The gas_state_t structure and associated subroutines.
Definition: gas_state.F90:9
pmc_gas_state::pmc_mpi_unpack_gas_state
subroutine pmc_mpi_unpack_gas_state(buffer, position, val)
Unpacks the given value from the buffer, advancing position.
Definition: gas_state.F90:555
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::real_to_string
character(len=pmc_util_convert_string_len) function real_to_string(val)
Convert a real to a string format.
Definition: util.F90:782
pmc_aero_dist::pmc_mpi_unpack_aero_dist
subroutine pmc_mpi_unpack_aero_dist(buffer, position, val)
Unpacks the given value from the buffer, advancing position.
Definition: aero_dist.F90:449
pmc_aero_dist
The aero_dist_t structure and associated subroutines.
Definition: aero_dist.F90:18
pmc_gas_state::gas_state_scale
subroutine gas_state_scale(gas_state, alpha)
Scale a gas state.
Definition: gas_state.F90:86
pmc_chamber::pmc_mpi_unpack_chamber
subroutine pmc_mpi_unpack_chamber(buffer, position, val)
Unpacks the given value from the buffer, advancing position.
Definition: chamber.F90:233
pmc_aero_state::aero_state_remove_particle_with_info
subroutine aero_state_remove_particle_with_info(aero_state, i_part, aero_info)
Remove the given particle and record the removal.
Definition: aero_state.F90:381
pmc_aero_state::aero_state_total_particles
integer function aero_state_total_particles(aero_state, i_group, i_class)
Returns the total number of particles in an aerosol distribution.
Definition: aero_state.F90:264
pmc_chamber::chamber_t
Definition: chamber.F90:18
pmc_constants::const
type(const_t), save const
Fixed variable for accessing the constant's values.
Definition: constants.F90:73
pmc_chamber::chamber_loss_rate_sedi
real(kind=dp) function chamber_loss_rate_sedi(chamber, vol, density, aero_data, env_state)
Calculate the loss rate due to sedimentation in chamber. Based on Eq. 37b in Naumann 2003 J....
Definition: chamber.F90:118
pmc_spec_file::spec_file_open
subroutine spec_file_open(filename, file)
Open a spec file for reading.
Definition: spec_file.F90:112
pmc_env_state::env_state_sat_vapor_pressure
real(kind=dp) function env_state_sat_vapor_pressure(env_state)
Computes the current saturation vapor pressure (Pa).
Definition: env_state.F90:140
pmc_spec_file::spec_file_read_timed_real_array
subroutine spec_file_read_timed_real_array(file, name, times, vals)
Read an a time-indexed array of real data.
Definition: spec_file.F90:688
pmc_scenario::pmc_mpi_pack_size_scenario
integer function pmc_mpi_pack_size_scenario(val)
Determines the number of bytes required to pack the given value.
Definition: scenario.F90:985
pmc_env_state
The env_state_t structure and associated subroutines.
Definition: env_state.F90:9
pmc_gas_state::gas_state_t
Current state of the gas mixing ratios in the system.
Definition: gas_state.F90:33
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_aero_dist::aero_dist_t
A complete aerosol distribution, consisting of several modes.
Definition: aero_dist.F90:33
pmc_scenario::scenario_loss_function_drydep
integer, parameter scenario_loss_function_drydep
Type code for a loss rate function based on dry deposition.
Definition: scenario.F90:34
pmc_util
Common utility subroutines.
Definition: util.F90:9
pmc_scenario::scenario_init_env_state
subroutine scenario_init_env_state(scenario, env_state, time)
Initialize the time-dependent contents of the environment. Thereafter scenario_update_env_state() sho...
Definition: scenario.F90:111
pmc_gas_state::pmc_mpi_pack_gas_state
subroutine pmc_mpi_pack_gas_state(buffer, position, val)
Packs the given value into the buffer, advancing position.
Definition: gas_state.F90:532
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_dist::pmc_mpi_pack_size_aero_dist
integer function pmc_mpi_pack_size_aero_dist(val)
Determines the number of bytes required to pack the given value.
Definition: aero_dist.F90:397
pmc_aero_data::aero_data_vol2diam
real(kind=dp) elemental function aero_data_vol2diam(aero_data, v)
Convert mass-equivalent volume (m^3) to geometric diameter (m).
Definition: aero_data.F90:107
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_scenario::scenario_try_single_particle_loss
subroutine scenario_try_single_particle_loss(scenario, delta_t, aero_data, aero_state, env_state, i_part, over_prob)
Test a candidate particle to see if it should be removed, and remove if necessary....
Definition: scenario.F90:694
pmc_chamber
Definition: chamber.F90:8
pmc_gas_state::gas_state_interp_1d
subroutine gas_state_interp_1d(gas_state_list, time_list, rate_list, time, gas_state, rate)
Determine the current gas_state and rate by interpolating at the current time with the lists of gas_s...
Definition: gas_state.F90:247
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_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_bin_grid::bin_grid_t
1D grid, either logarithmic or linear.
Definition: bin_grid.F90:33
pmc_scenario::scenario_loss_rate_dry_dep
real(kind=dp) function scenario_loss_rate_dry_dep(vol, density, aero_data, env_state)
Compute and return the dry deposition rate for a given particle. All equations used here are written ...
Definition: scenario.F90:427
pmc_chamber::chamber_loss_rate_wall
real(kind=dp) function chamber_loss_rate_wall(chamber, vol, aero_data, env_state)
Calculate the loss rate due to wall diffusion in chamber. Based on Eq. 37a in Naumann 2003 J....
Definition: chamber.F90:91
pmc_scenario::scenario_particle_loss
subroutine scenario_particle_loss(scenario, delta_t, aero_data, aero_state, env_state)
Performs stochastic particle loss for one time-step. If a particle i_part has a scenario_loss_rate() ...
Definition: scenario.F90:609
pmc_scenario::scenario_loss_function_chamber
integer, parameter scenario_loss_function_chamber
Type code for a loss rate function for chamber experiments.
Definition: scenario.F90:36
pmc_aero_dist::aero_dist_contains_aero_mode_type
elemental logical function aero_dist_contains_aero_mode_type(aero_dist, aero_mode_type)
Whether any of the modes are of the given type.
Definition: aero_dist.F90:155
pmc_gas_state::pmc_mpi_pack_size_gas_state
integer function pmc_mpi_pack_size_gas_state(val)
Determines the number of bytes required to pack the given value.
Definition: gas_state.F90:519
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_scenario::scenario_loss_function_invalid
integer, parameter scenario_loss_function_invalid
Type code for an undefined or invalid loss function.
Definition: scenario.F90:26
pmc_aero_state::aero_state_t
The current collection of aerosol particles.
Definition: aero_state.F90:63
pmc_aero_state::aero_state_sort
subroutine aero_state_sort(aero_state, aero_data, bin_grid, all_procs_same)
Sorts the particles if necessary.
Definition: aero_state.F90:2939
pmc_aero_state::aero_state_add_aero_dist_sample
subroutine aero_state_add_aero_dist_sample(aero_state, aero_data, aero_dist, sample_prop, create_time, allow_doubling, allow_halving, n_part_add)
Generates a Poisson sample of an aero_dist, adding to aero_state, with the given sample proportion.
Definition: aero_state.F90:717
pmc_rand::pmc_random
real(kind=dp) function pmc_random()
Returns a random number between 0 and 1.
Definition: rand.F90:139
pmc_aero_state::aero_state_sample_particles
subroutine aero_state_sample_particles(aero_state_from, aero_state_to, aero_data, sample_prob, removal_action)
Generates a random sample by removing particles from aero_state_from and adding them to aero_state_to...
Definition: aero_state.F90:809