afivo-streamer 1.1
1D/2D/3D streamer simulations with AMR
Loading...
Searching...
No Matches
m_photoi_mc.f90
Go to the documentation of this file.
1!> Module that provides routines for Monte Carlo photoionization
3#include "../afivo/src/cpp_macros.h"
4 use m_streamer
5 use m_lookup_table
6 use m_af_all
8
9 implicit none
10 private
11
12 !> Type to quickly look up absorption lengths from a table
13 type, public :: phmc_tbl_t
14 type(lt_t) :: tbl !< The lookup table
15 !> Value of full integral over the absorption function (can be less than
16 !> one if non-ionizing absorption is present)
17 real(dp) :: full_integral
18 end type phmc_tbl_t
19
20 !> Whether physical photons are used
21 logical, public, protected :: phmc_physical_photons = .true.
22
23 !> Whether a constant or adaptive grid spacing is used
24 logical, protected :: phmc_const_dx = .true.
25
26 !> Minimum grid spacing for photoionization
27 real(dp), protected :: phmc_min_dx = 1e-9_dp
28
29 !> At which grid spacing photons are absorbed compared to their mean distance
30 real(dp), protected :: phmc_absorp_fac = 0.25_dp
31
32 !> Number of photons to use
33 integer, protected :: phmc_num_photons = 5000*1000
34
35 !> Minimal photon weight
36 real(dp), protected :: phmc_min_weight = 1.0_dp
37
38 !> Table for photoionization
39 type(phmc_tbl_t), public, protected :: phmc_tbl
40
41 interface
42 real(dp) function absfunc_t(dist, p_o2, p_h2o)
43 import
44 real(dp), intent(in) :: dist !< Distance
45 real(dp), intent(in) :: p_o2 !< Partial pressure of oxygen (bar)
46 real(dp), intent(in) :: p_h2o !< Partial pressure of H2O (bar)
47 end function absfunc_t
48 end interface
49
50 ! Public methods
51 public :: phmc_initialize
53 public :: phmc_get_table_air
54 public :: phmc_do_absorption
55 public :: phmc_set_src
56 ! public :: phe_mc_set_src
57
58contains
59
60 !> Initialize photoionization parameters
61 subroutine phmc_initialize(cfg, is_used, source_type)
62 use m_config
63 use m_streamer
64 use m_gas
65 type(cfg_t), intent(inout) :: cfg !< The configuration for the simulation
66 logical, intent(in) :: is_used !< Whether Monte Carlo photoionization is used
67 character(len=*), intent(in) :: source_type
68 integer :: ix
69 real(dp) :: p_o2, p_h2o
70
71 !< [photoi_mc_parameters]
72 call cfg_add_get(cfg, "photoi_mc%physical_photons", phmc_physical_photons, &
73 "Whether physical photons are used")
74 call cfg_add_get(cfg, "photoi_mc%min_weight", phmc_min_weight, &
75 "Minimal photon weight (default: 1.0)")
76 call cfg_add_get(cfg, "photoi_mc%const_dx", phmc_const_dx, &
77 "Whether a constant grid spacing is used for photoionization")
78 call cfg_add_get(cfg, "photoi_mc%min_dx", phmc_min_dx, &
79 "Minimum grid spacing for photoionization")
80 call cfg_add_get(cfg, "photoi_mc%absorp_fac", phmc_absorp_fac, &
81 "At which grid spacing photons are absorbed compared to their mean distance")
82 call cfg_add_get(cfg, "photoi_mc%num_photons", phmc_num_photons, &
83 "Maximum number of discrete photons to use")
84 !< [photoi_mc_parameters]
85
86 if (phmc_absorp_fac <= 0.0_dp) error stop "photoi_mc%absorp_fac <= 0.0"
87 if (phmc_num_photons < 1) error stop "photoi_mc%num_photons < 1"
88
89 if (is_used) then
90 ! Create table for photoionization
91 ix = gas_index("O2")
92 if (ix == -1) then
93 error stop "Photoionization: no oxygen present"
94 else
95 p_o2 = gas_fractions(ix) * gas_pressure
96 end if
97
98 ix = gas_index("H2O")
99 if (ix == -1) then
100 p_h2o = 0.0_dp
101 else
102 p_h2o = gas_fractions(ix) * gas_pressure
103 end if
104
105 select case (source_type)
106 case ("Zheleznyak")
107 ! Standard Zheleznyak model for dry air
108 call phmc_get_table_air(phmc_tbl, p_o2, p_h2o, absfunc_zheleznyak)
109 case ("Naidis_humid")
110 ! Naidis model for humid air
111 call phmc_get_table_air(phmc_tbl, p_o2, p_h2o, absfunc_naidis_humid)
112 case ("Aints_humid")
113 ! Aints model for humid air
114 call phmc_get_table_air(phmc_tbl, p_o2, p_h2o, absfunc_aints_humid)
115 case default
116 error stop "Invalid photoionization source_type"
117 end select
118
120
121 if (st_use_dielectric) then
122 if (.not. phmc_const_dx) &
123 error stop "phmc_initialize: with dielectric use const_dx"
124 if (phmc_absorp_fac > 1e-9_dp) &
125 error stop "Use phmc_absorp_fac <= 1e-9 with dielectric"
126 end if
127 end if
128
129 end subroutine phmc_initialize
130
131 !> Print the grid spacing used for the absorption of photons
132 subroutine phmc_print_grid_spacing(dr_base)
133 real(dp), intent(in) :: dr_base
134 real(dp) :: lengthscale, dx
135 integer :: lvl
136
137 ! Get a typical length scale for the absorption of photons
138 lengthscale = lt_get_col(phmc_tbl%tbl, 1, phmc_absorp_fac)
139
140 ! Determine at which level we estimate the photoionization source term. This
141 ! depends on the typical length scale for absorption.
142 lvl = get_lvl_length(dr_base, lengthscale)
143 dx = dr_base * (0.5_dp**(lvl-1))
144
145 if (phmc_const_dx) then
146 write(*, "(A,E12.3)") " Monte-Carlo photoionization spacing: ", dx
147 else
148 write(*, "(A)") " Monte-Carlo photoionization uses adaptive spacing"
149 end if
150 end subroutine phmc_print_grid_spacing
151
152 !> Compute the photonization table for air. If the absorption function is
153 !> f(r), this table contains r as a function of F (the cumulative absorption
154 !> function, between 0 and 1). Later on a distance can be sampled by drawing a
155 !> uniform 0,1 random number and finding the corresponding distance.
156 subroutine phmc_get_table_air(phmc_tbl, p_O2, p_H2O, absfunc)
157 !< The photonization table
158 type(phmc_tbl_t), intent(inout) :: phmc_tbl
159 !> Partial pressure of oxygen (bar)
160 real(dp), intent(in) :: p_o2
161 !> Partial pressure of H2O (bar)
162 real(dp), intent(in) :: p_h2o
163 !> Absorption function to use
164 procedure(absfunc_t) :: absfunc
165
166 ! Large distance, at which all photons have been absorbed
167 real(dp) :: large_distance = 1e3_dp
168 integer :: n
169 integer, parameter :: tbl_size = 500
170 real(dp), allocatable :: fsum(:), dist(:)
171
172 allocate(fsum(tbl_size))
173 allocate(dist(tbl_size))
174
175 call integrate_absfunc(p_o2, p_h2o, absfunc, 1.0_dp, large_distance, &
176 tbl_size, fsum, dist, n)
177
178 ! The full integral can be less than one (e.g. with Naidis_humid).
179 ! Normalize, and store the factor, which will be used when generating
180 ! photons.
181 phmc_tbl%full_integral = fsum(n)
182 fsum(1:n) = fsum(1:n) / fsum(n)
183
184 phmc_tbl%tbl = lt_create(0.0_dp, 1.0_dp, tbl_size, 1)
185 call lt_set_col(phmc_tbl%tbl, 1, fsum(1:n), dist(1:n))
186
187 write(*, "(A,E12.3,A)") " Average photon absorption length", &
188 1e3_dp * sum(dist(1:n-1))/(n-1), " mm"
189 end subroutine phmc_get_table_air
190
191 subroutine integrate_absfunc(p_O2, p_H2O, absfunc, F_max, r_max, n_steps, fsum, r, n)
192 real(dp), intent(in) :: p_o2 !< Partial pressure of oxygen (bar)
193 real(dp), intent(in) :: p_h2o !< Partial pressure of H2O (bar)
194 procedure(absfunc_t) :: absfunc
195 real(dp), intent(in) :: f_max
196 real(dp), intent(in) :: r_max
197 integer, intent(in) :: n_steps
198 real(dp), intent(inout) :: fsum(n_steps)
199 real(dp), intent(inout) :: r(n_steps)
200 integer, intent(inout) :: n
201 real(dp) :: df, drdf
202
203 df = f_max / (n_steps-1)
204 fsum(1) = 0.0_dp
205 r(1) = 0.0_dp
206
207 do n = 2, n_steps
208 drdf = rk4_drdf(r(n-1), df, p_o2, p_h2o, absfunc)
209 r(n) = r(n-1) + df * drdf
210 fsum(n) = (n-1) * df
211 if (fsum(n) > f_max .or. r(n) > r_max) exit
212 end do
213
214 ! We went past F_max or r_max, or the loop completed fully. Valid data is
215 ! up to index n
216 n = n - 1
217 end subroutine integrate_absfunc
218
219 !> Runge Kutta 4 method to compute dr/dF, where r is the absorption distance
220 !> and F is the cumulative absorption function (between 0 and 1)
221 real(dp) function rk4_drdf(r, df, p_o2, p_h2o, absfunc)
222 real(dp), intent(in) :: r !> Initial point
223 real(dp), intent(in) :: df !> grid size
224 real(dp), intent(in) :: p_o2 !< Partial pressure of oxygen (bar)
225 real(dp), intent(in) :: p_h2o !< Partial pressure of H2O (bar)
226 procedure(absfunc_t) :: absfunc
227 real(dp) :: drdf
228 real(dp) :: sum_drdf
229 real(dp), parameter :: one_sixth = 1 / 6.0_dp
230
231 ! Step 1 (at initial r)
232 drdf = 1 / absfunc(r, p_o2, p_h2o)
233 sum_drdf = drdf
234
235 ! Step 2 (at initial r + dr/2)
236 drdf = 1 / absfunc(r + 0.5_dp * df * drdf, p_o2, p_h2o)
237 sum_drdf = sum_drdf + 2 * drdf
238
239 ! Step 3 (at initial r + dr/2)
240 drdf = 1 / absfunc(r + 0.5_dp * df * drdf, p_o2, p_h2o)
241 sum_drdf = sum_drdf + 2 * drdf
242
243 ! Step 4 (at initial r + dr)
244 drdf = 1 / absfunc(r + df * drdf, p_o2, p_h2o)
245 sum_drdf = sum_drdf + drdf
246
247 ! Combine r derivatives at steps
248 rk4_drdf = one_sixth * sum_drdf
249 end function rk4_drdf
250
251 !> Absorption function for photons in air according to Zheleznyak's model
252 !>
253 !> Reference: "Photoionization of nitrogen and oxygen mixtures by radiation
254 !> from a gas discharge", Zheleznyak and Mnatsakanian, 1982.
255 real(dp) function absfunc_zheleznyak(dist, p_o2, p_h2o) result(f)
257 real(dp), intent(in) :: dist !< Distance
258 real(dp), intent(in) :: p_o2 !< Partial pressure of oxygen (bar)
259 real(dp), intent(in) :: p_h2o !< Partial pressure of H2O (bar)
260 real(dp) :: r
261 real(dp), parameter :: k1 = 3.5_dp / uc_torr_to_bar
262 real(dp), parameter :: k2 = 200 / uc_torr_to_bar
263 real(dp), parameter :: eps = epsilon(1.0_dp)
264
265 ! Note: r is here distance times p_O2
266 r = p_o2 * dist
267
268 if (r * k1 < eps) then
269 ! Use Taylor series around r = 0
270 f = (k2 - k1 + 0.5_dp * (k1**2 - k2**2) * r) &
271 * p_o2 / log(k2/k1)
272 else
273 ! Avoid division by zero
274 f = max(1e-100_dp, &
275 (exp(-k1 * r) - exp(-k2 * r)) / (dist * log(k2/k1)))
276 end if
277 end function absfunc_zheleznyak
278
279 !> Absorption function for photons in humid according to Naidis model
280 !>
281 !> Reference: "On photoionization produced by discharges in air", Naidis, 2006
282 real(dp) function absfunc_naidis_humid(dist, p_o2, p_h2o) result(f)
283 real(dp), intent(in) :: dist !< Distance
284 real(dp), intent(in) :: p_o2 !< Partial pressure of oxygen (bar)
285 real(dp), intent(in) :: p_h2o !< Partial pressure of H2O (bar)
286 real(dp), parameter :: k3 = 0.26e2_dp / uc_torr_to_bar
287
288 f = max(1e-100_dp, &
289 exp(-k3 * p_h2o * dist) * absfunc_zheleznyak(dist, p_o2, p_h2o))
290 end function absfunc_naidis_humid
291
292 !> Absorption function for photons in humid according to Aints model
293 !>
294 !> Reference: "Absorption of Photo-Ionizing Radiation of Corona Discharges in Air",
295 !> Aints et al, 2008. See also Guo et al, PSST 2025.
296 real(dp) function absfunc_aints_humid(dist, p_o2, p_h2o) result(f)
297 real(dp), intent(in) :: dist !< Distance
298 real(dp), intent(in) :: p_o2 !< Partial pressure of oxygen (bar)
299 real(dp), intent(in) :: p_h2o !< Partial pressure of H2O (bar)
300 real(dp), parameter :: k1 = 3.5_dp / uc_torr_to_bar
301 real(dp), parameter :: k2 = 200 / uc_torr_to_bar
302 real(dp), parameter :: k4 = 0.13e2 / uc_torr_to_bar
303 real(dp), parameter :: k5 = 0.57e2 / uc_torr_to_bar
304 real(dp) :: k_a, k_b
305 real(dp), parameter :: eps = epsilon(1.0_dp)
306
307 k_a = k1 * p_o2 + k4 * p_h2o
308 k_b = k2 * p_o2 + k5 * p_h2o
309
310 if (dist * k_a < eps) then
311 ! Use Taylor series around dist = 0
312 f = (k_b - k_a + 0.5_dp * (k_a**2 - k_b**2) * dist) / log(k_b/k_a)
313 else
314 ! Avoid division by zero
315 f = max(1e-100_dp, &
316 (exp(-k_a * dist) - exp(-k_b * dist)) / (dist * log(k_b/k_a)))
317 end if
318 end function absfunc_aints_humid
319
320 !> Determine the lowest level at which the grid spacing is smaller than 'length'.
321 integer function get_lvl_length(dr_base, length)
322 real(dp), intent(in) :: dr_base !< cell spacing at lvl 1
323 real(dp), intent(in) :: length !< Some length
324 real(dp), parameter :: invlog2 = 1 / log(2.0_dp)
325 real(dp) :: ratio
326
327 ratio = dr_base / length
328 if (ratio <= 1) then
329 get_lvl_length = 1
330 else
331 get_lvl_length = 1 + ceiling(log(ratio) * invlog2)
332 end if
333 end function get_lvl_length
334
335 !> As get_lvl_length but with a random choice between lvl and lvl-1
336 integer function get_rlvl_length(dr_base, length, rng)
337 use m_random
338 real(dp), intent(in) :: dr_base !< cell spacing at lvl 1
339 real(dp), intent(in) :: length !< Some length
340 type(rng_t), intent(inout) :: rng !< Random number generator
341 real(dp), parameter :: invlog2 = 1 / log(2.0_dp)
342 real(dp) :: ratio, tmp
343
344 ratio = dr_base / length
345 if (ratio <= 1) then
346 get_rlvl_length = 1
347 else
348 tmp = log(ratio) * invlog2
349 get_rlvl_length = floor(tmp)
350 if (rng%unif_01() < tmp - get_rlvl_length) &
351 get_rlvl_length = get_rlvl_length + 1
352 end if
353 end function get_rlvl_length
354
355 !> Given a list of photon production positions (xyz_in), compute where they
356 !> end up (xyz_out).
357 subroutine phmc_do_absorption(xyz_in, xyz_out, n_dim, n_photons, tbl, prng)
358 use m_lookup_table
359 use m_random
360 use omp_lib
361 integer, intent(in) :: n_photons !< Number of photons
362 !> Input (x,y,z) values
363 real(dp), intent(in) :: xyz_in(3, n_photons)
364 !> Output (x,y,z) values
365 real(dp), intent(out) :: xyz_out(3, n_photons)
366 integer, intent(in) :: n_dim !< 2 or 3 dimensional
367 !< Lookup table
368 type(lt_t), intent(in) :: tbl
369 type(prng_t), intent(inout) :: prng !< Random number generator
370 integer :: n, proc_id
371 real(dp) :: rr, dist
372
373 !$omp parallel private(n, rr, dist, proc_id)
374 proc_id = 1+omp_get_thread_num()
375
376 if (n_dim == 2) then
377 !$omp do
378 do n = 1, n_photons
379 rr = prng%rngs(proc_id)%unif_01()
380 dist = lt_get_col(tbl, 1, rr)
381 ! Pick a random point on a sphere, and ignore the last dimension
382 xyz_out(1:3, n) = xyz_in(1:3, n) + &
383 prng%rngs(proc_id)%sphere(dist)
384 end do
385 !$omp end do
386 else if (n_dim == 3) then
387 !$omp do
388 do n = 1, n_photons
389 rr = prng%rngs(proc_id)%unif_01()
390 dist = lt_get_col(tbl, 1, rr)
391 xyz_out(:, n) = xyz_in(:, n) + prng%rngs(proc_id)%sphere(dist)
392 end do
393 !$omp end do
394 else
395 print *, "phmc_do_absorption: unknown n_dim", n_dim
396 stop
397 end if
398 !$omp end parallel
399
400 end subroutine phmc_do_absorption
401
402 ! !> Given a list of photon production positions (xyz_in), compute where they
403 ! !> end up (xyz_out).
404 ! subroutine phe_mc_get_endloc(xyz_in, xyz_out, n_dim, n_photons, prng)
405 ! use m_lookup_table
406 ! use m_random
407 ! use omp_lib
408 ! integer, intent(in) :: n_photons !< Number of photons
409 ! !> Input (x,y,z) values
410 ! real(dp), intent(in) :: xyz_in(3, n_photons)
411 ! !> Output (x,y,z) values
412 ! real(dp), intent(out) :: xyz_out(3, n_photons)
413 ! integer, intent(in) :: n_dim !< 2 or 3 dimensional
414 ! type(PRNG_t), intent(inout) :: prng !< Random number generator
415 ! integer :: n, proc_id
416 ! real(dp) :: rr, dist
417
418 ! !$omp parallel private(n, rr, dist, proc_id)
419 ! proc_id = 1+omp_get_thread_num()
420
421 ! if (n_dim == 2) then
422 ! !$omp do
423 ! do n = 1, n_photons
424 ! rr = prng%rngs(proc_id)%unif_01()
425 ! dist = 0.00001_dp
426 ! ! Pick a random point on a sphere, and ignore the last dimension
427 ! xyz_out(1:3, n) = xyz_in(1:3, n) + &
428 ! prng%rngs(proc_id)%sphere(dist)
429 ! end do
430 ! !$omp end do
431 ! else if (n_dim == 3) then
432 ! !$omp do
433 ! do n = 1, n_photons
434 ! rr = prng%rngs(proc_id)%unif_01()
435 ! dist = 0.00001_dp
436 ! xyz_out(:, n) = xyz_in(:, n) + prng%rngs(proc_id)%sphere(dist)
437 ! end do
438 ! !$omp end do
439 ! else
440 ! print *, "phmc_do_absorption: unknown n_dim", n_dim
441 ! stop
442 ! end if
443 ! !$omp end parallel
444
445 ! end subroutine phe_mc_get_endloc
446
447 !> Set the source term due to photoionization. At most phmc_num_photons
448 !> discrete photons are produced.
449 subroutine phmc_set_src(tree, rng, i_src, i_photo, use_cyl, dt)
450 use m_random
451 use m_lookup_table
452 use m_dielectric
453 use omp_lib
454
455 type(af_t), intent(inout) :: tree !< Tree
456 type(rng_t), intent(inout) :: rng !< Random number generator
457 !> Input variable that contains photon production per cell
458 integer, intent(in) :: i_src
459 !> Input variable that contains photoionization source rate
460 integer, intent(in) :: i_photo
461 !> Use cylindrical coordinates (only in 2D)
462 logical, intent(in) :: use_cyl
463 !> Time step, if present use "physical" photons
464 real(dp), intent(in), optional :: dt
465
466 integer :: lvl, id, nc, min_lvl
467 integer :: ijk, n, n_used
468 integer :: proc_id, n_procs
469 integer :: pho_lvl, max_num_photons
470 real(dp) :: dr(ndim), dt_fac, dist, n_produced
471 real(dp) :: sum_production_rate, pi_lengthscale
472 real(dp), allocatable :: xyz_src(:, :)
473 real(dp), allocatable :: xyz_abs(:, :)
474#if NDIM == 2
475 real(dp) :: tmp, r(3)
476 real(dp), parameter :: pi = acos(-1.0_dp)
477#endif
478 type(prng_t) :: prng
479 type(af_loc_t), allocatable :: ph_loc(:)
480 real(dp), parameter :: small_value = 1e-100_dp
481
482 if (ndim == 3 .and. use_cyl) error stop "phmc_set_src: use_cyl is .true."
483
484 if (st_use_dielectric) then
486 end if
487
488 nc = tree%n_cell
489
490 ! Initialize parallel random numbers
491 n_procs = omp_get_max_threads()
492 call prng%init_parallel(n_procs, rng)
493
494 ! Compute the sum of the photon production rate
495 call af_tree_sum_cc(tree, i_src, sum_production_rate)
496
497 ! dt_fac is used to convert a discrete number of photons to a number of
498 ! photons per unit time. Depending on the arguments dt and phmc_num_photons,
499 ! 'physical' photons with a weight of phmc_min_weight, or super-photons with
500 ! a weight larger (or smaller) than one can be used.
501 if (present(dt)) then
502 ! Create "physical" photons when less than phmc_num_photons are produced,
503 ! otherwise create approximately phmc_num_photons
504 n_produced = dt * sum_production_rate / phmc_min_weight
505
506 if (n_produced < phmc_num_photons) then
507 dt_fac = dt/phmc_min_weight
508 else
509 dt_fac = phmc_num_photons / (sum_production_rate + small_value)
510 end if
511 else
512 ! Create approximately phmc_num_photons by setting dt_fac like this
513 dt_fac = phmc_num_photons / (sum_production_rate + small_value)
514 end if
515
516 ! Over-estimate number of photons because of stochastic production
517 max_num_photons = nint(1.2_dp * dt_fac * sum_production_rate + 1000)
518
519 call phmc_generate_photons(tree, dt_fac, i_src, xyz_src, &
520 max_num_photons, n_used, prng)
521
522 allocate(xyz_abs(3, n_used))
523
524 if (use_cyl) then ! 2D only
525 ! Get location of absorption. On input, xyz is set to (r, z, 0). On
526 ! output, the coordinates thus correspond to (x, z, y)
527 call phmc_do_absorption(xyz_src, xyz_abs, 3, n_used, phmc_tbl%tbl, prng)
528
529 !$omp do
530 do n = 1, n_used
531 ! Convert back to (r,z) coordinates
532 xyz_abs(1, n) = sqrt(xyz_abs(1, n)**2 + xyz_abs(3, n)**2)
533 xyz_abs(2, n) = xyz_abs(2, n)
534 end do
535 !$omp end do
536
537 if (st_use_dielectric) then
538 ! Handle photons that collide with dielectrics separately
539 call dielectric_photon_absorption(tree, xyz_src, xyz_abs, &
540 2, n_used, 1.0_dp/dt_fac)
541 end if
542 else
543 ! Get location of absorbption
544 call phmc_do_absorption(xyz_src, xyz_abs, ndim, n_used, phmc_tbl%tbl, prng)
545
546 if (st_use_dielectric) then
547 ! Handle photons that collide with dielectrics separately
548 call dielectric_photon_absorption(tree, xyz_src, xyz_abs, &
549 ndim, n_used, 1.0_dp/dt_fac)
550 end if
551 end if
552
553 call periodify_coordinates(n_used, xyz_abs)
554
555 allocate(ph_loc(n_used))
556
557 if (phmc_const_dx) then
558 ! Get a typical length scale for the absorption of photons
559 pi_lengthscale = lt_get_col(phmc_tbl%tbl, 1, phmc_absorp_fac)
560
561 ! Determine at which level we estimate the photoionization source term. This
562 ! depends on the typical length scale for absorption.
563 pho_lvl = get_lvl_length(maxval(tree%dr_base), pi_lengthscale)
564
565 !$omp parallel do
566 do n = 1, n_used
567 ph_loc(n) = af_get_loc(tree, xyz_abs(1:ndim, n), pho_lvl)
568 end do
569 !$omp end parallel do
570 else
571 !$omp parallel private(n, dist, lvl, proc_id)
572 proc_id = 1+omp_get_thread_num()
573 !$omp do
574 do n = 1, n_used
575 dist = phmc_absorp_fac * norm2(xyz_abs(1:ndim, n) - xyz_src(1:ndim, n))
576 if (dist < phmc_min_dx) dist = phmc_min_dx
577 lvl = get_rlvl_length(maxval(tree%dr_base), dist, prng%rngs(proc_id))
578 ph_loc(n) = af_get_loc(tree, xyz_abs(1:ndim, n), lvl)
579 end do
580 !$omp end do
581 !$omp end parallel
582 end if
583
584 ! Clear variable i_photo, in which we will store the photoionization source term
585 call af_tree_clear_cc(tree, i_photo)
586
587 do n = 1, n_used
588 id = ph_loc(n)%id
589 if (id > af_no_box) then
590#if NDIM == 1
591 i = ph_loc(n)%ix(1)
592 dr = tree%boxes(id)%dr
593
594 tree%boxes(id)%cc(ijk, i_photo) = &
595 tree%boxes(id)%cc(ijk, i_photo) + &
596 phmc_tbl%full_integral/(dt_fac * product(dr))
597#elif NDIM == 2
598 i = ph_loc(n)%ix(1)
599 j = ph_loc(n)%ix(2)
600 dr = tree%boxes(id)%dr
601
602 if (use_cyl) then
603 tmp = dt_fac * 2 * pi
604 r(1:2) = af_r_cc(tree%boxes(id), [i, j])
605 tree%boxes(id)%cc(i, j, i_photo) = &
606 tree%boxes(id)%cc(i, j, i_photo) + &
607 phmc_tbl%full_integral/(tmp * product(dr) * r(1))
608 else
609 tree%boxes(id)%cc(ijk, i_photo) = &
610 tree%boxes(id)%cc(ijk, i_photo) + &
611 phmc_tbl%full_integral/(dt_fac * product(dr))
612 end if
613#elif NDIM == 3
614 i = ph_loc(n)%ix(1)
615 j = ph_loc(n)%ix(2)
616 k = ph_loc(n)%ix(3)
617 dr = tree%boxes(id)%dr
618
619 tree%boxes(id)%cc(ijk, i_photo) = &
620 tree%boxes(id)%cc(ijk, i_photo) + &
621 phmc_tbl%full_integral/(dt_fac * product(dr))
622#endif
623 end if
624 end do
625
626 ! Set ghost cells on highest level with photon source
627 if (phmc_const_dx) then
628 min_lvl = pho_lvl
629 else
630 min_lvl = 1
631 end if
632
633 !$omp parallel private(lvl, i, id)
634 ! Prolong to finer grids
635 do lvl = min_lvl, tree%highest_lvl-1
636 !$omp do
637 do i = 1, size(tree%lvls(lvl)%parents)
638 id = tree%lvls(lvl)%parents(i)
639 call af_gc_box(tree, id, [i_photo])
640 end do
641 !$omp end do
642
643 !$omp do
644 do i = 1, size(tree%lvls(lvl)%parents)
645 id = tree%lvls(lvl)%parents(i)
646 call af_prolong_linear_from(tree%boxes, id, i_photo, add=.true.)
647 end do
648 !$omp end do
649 end do
650 !$omp end parallel
651
652 call prng%update_seed(rng)
653 end subroutine phmc_set_src
654
655 !> Map coordinates to a periodic domain
656 subroutine periodify_coordinates(n_points, coords)
657 integer, intent(in) :: n_points
658 real(dp), intent(inout) :: coords(3, n_points)
659 real(dp) :: xrel
660
661 integer :: i_dim, n
662
663 do i_dim = 1, ndim
664 if (st_periodic(i_dim)) then
665 do n = 1, n_points
666 xrel = coords(i_dim, n) - st_domain_origin(i_dim)
667 coords(i_dim, n) = st_domain_origin(i_dim) + &
668 modulo(xrel, st_domain_len(i_dim))
669 end do
670 end if
671 end do
672 end subroutine periodify_coordinates
673
674! !> Whole photoemission procedure
675! subroutine phe_mc_set_src(tree, rng, i_src, i_photo, use_cyl, dt)
676! use m_random
677! use m_lookup_table
678! use m_dielectric2
679! use omp_lib
680
681! type(af_t), intent(inout) :: tree !< Tree
682! type(RNG_t), intent(inout) :: rng !< Random number generator
683! !> Input variable that contains photon production per cell
684! integer, intent(in) :: i_src
685! !> Input variable that contains photoionization source rate
686! integer, intent(in) :: i_photo
687! !> Use cylindrical coordinates (only in 2D)
688! logical, intent(in) :: use_cyl
689! !> Time step, if present use "physical" photons
690! real(dp), intent(in), optional :: dt
691
692! integer :: nc
693! integer :: n, n_used
694! integer :: proc_id, n_procs
695! integer :: pho_lvl, max_num_photons
696! real(dp) :: tmp, dt_fac, r(3)
697! real(dp) :: sum_production_rate, pi_lengthscale
698! real(dp), allocatable :: xyz_src(:, :)
699! real(dp), allocatable :: xyz_abs(:, :)
700! #if NDIM == 2
701! real(dp), parameter :: pi = acos(-1.0_dp)
702! #endif
703! type(PRNG_t) :: prng
704! type(af_loc_t), allocatable :: ph_loc(:)
705! real(dp), parameter :: small_value = 1e-100_dp
706
707! if (NDIM == 3 .and. use_cyl) error stop "phmc_set_src: use_cyl is .true."
708
709! if (ST_use_dielectric) then
710! call dielectric2_reset_photons()
711! end if
712
713! nc = tree%n_cell
714
715! ! Initialize parallel random numbers
716! n_procs = omp_get_max_threads()
717! call prng%init_parallel(n_procs, rng)
718
719! ! Compute the sum of the photon production rate
720! call af_tree_sum_cc(tree, i_src, sum_production_rate)
721
722! ! dt_fac is used to convert a discrete number of photons to a number of
723! ! photons per unit time. Depending on the arguments dt and phmc_num_photons,
724! ! 'physical' photons with a weight of 1.0, or super-photons with a weight
725! ! larger (or smaller) than one can be used.
726! if (present(dt)) then
727! ! Create "physical" photons when less than phmc_num_photons are produced,
728! ! otherwise create approximately phmc_num_photons
729! dt_fac = min(dt, phmc_num_photons / (sum_production_rate + small_value))
730! else
731! ! Create approximately phmc_num_photons by setting dt_fac like this
732! dt_fac = phmc_num_photons / (sum_production_rate + small_value)
733! end if
734
735! ! Over-estimate number of photons because of stochastic production
736! max_num_photons = nint(1.2_dp * dt_fac * sum_production_rate + 1000)
737
738! call phmc_generate_photons(tree, dt_fac, i_src, xyz_src, &
739! max_num_photons, n_used, prng)
740
741! allocate(xyz_abs(3, n_used))
742
743! if (use_cyl) then ! 2D only
744! ! Get location of absorption. On input, xyz is set to (r, z, 0). On
745! ! output, the coordinates thus correspond to (x, z, y)
746! call phe_mc_get_endloc(xyz_src, xyz_abs, 3, n_used, prng)
747
748! !$omp do
749! do n = 1, n_used
750! ! Convert back to (r,z) coordinates
751! xyz_abs(1, n) = sqrt(xyz_abs(1, n)**2 + xyz_abs(3, n)**2)
752! xyz_abs(2, n) = xyz_abs(2, n)
753! xyz_src(2, n) = xyz_src(3, n)
754! end do
755! !$omp end do
756
757! if (ST_use_dielectric) then
758! ! Handle photons that collide with dielectrics separately
759! call dielectric2_photon_absorption(tree, xyz_src, xyz_abs, &
760! 2, n_used, 1.0_dp/dt_fac)
761! end if
762! else
763! ! Get location of absorbption
764! call phe_mc_get_endloc(xyz_src, xyz_abs, NDIM, n_used, prng)
765
766! if (ST_use_dielectric) then
767! ! Handle photons that collide with dielectrics separately
768! call dielectric2_photon_absorption(tree, xyz_src, xyz_abs, &
769! NDIM, n_used, 1.0_dp/dt_fac)
770! end if
771! end if
772
773! call prng%update_seed(rng)
774! end subroutine phe_mc_set_src
775
776
777 subroutine phmc_generate_photons(tree, dt_fac, i_src, xyz_src, n_max, n_used, prng)
778 use omp_lib
779 use m_random
780 type(af_t), intent(in) :: tree
781 !> Time step to convert photon production rate to number of photons
782 real(dp), intent(in) :: dt_fac
783 !> Index of cell-centered variable containing photon production rate
784 integer, intent(in) :: i_src
785 !> Origin of the produced photons
786 real(dp), allocatable, intent(inout) :: xyz_src(:, :)
787 !> Maximum number of photons that will be produced
788 integer, intent(in) :: n_max
789 !> Number of photons that have been created
790 integer, intent(out) :: n_used
791 !> Parallel random number generator
792 type(prng_t), intent(inout) :: prng
793
794 integer :: proc_id, n_procs
795 integer :: lvl, ix, id, ijk, n, nc, i_ph
796 integer :: n_create, my_num_photons
797 real(dp) :: tmp, dr(ndim), r(3)
798 integer, allocatable :: photons_per_thread(:), photon_thread(:)
799 integer, allocatable :: ix_offset(:)
800 real(dp), allocatable :: xyz_tmp(:, :)
801
802 nc = tree%n_cell
803 n_procs = omp_get_max_threads()
804
805 allocate(xyz_src(3, n_max))
806 allocate(photons_per_thread(n_procs))
807 allocate(photon_thread(n_max))
808
809 ! Loop over all leaves and create photons using random numbers
810 n_used = 0
811
812 !$omp parallel private(lvl, ix, id, IJK, n, r, dr, i_ph, proc_id, &
813 !$omp tmp, n_create, my_num_photons)
814 proc_id = 1+omp_get_thread_num()
815 my_num_photons = 0
816
817 do lvl = 1, tree%highest_lvl
818 dr = af_lvl_dr(tree, lvl)
819 !$omp do
820 do ix = 1, size(tree%lvls(lvl)%leaves)
821 id = tree%lvls(lvl)%leaves(ix)
822
823 do kji_do(1,nc)
824
825#if NDIM == 2
826 if (tree%boxes(id)%coord_t == af_cyl) then
827 tmp = af_cyl_radius_cc(tree%boxes(id), i)
828 tmp = dt_fac * 2 * uc_pi * tmp * &
829 tree%boxes(id)%cc(i, j, i_src) * product(dr)
830 else
831 tmp = dt_fac * tree%boxes(id)%cc(i, j, i_src) * product(dr)
832 end if
833#else
834 tmp = dt_fac * tree%boxes(id)%cc(ijk, i_src) * product(dr)
835#endif
836
837 n_create = floor(tmp)
838
839 if (prng%rngs(proc_id)%unif_01() < tmp - n_create) &
840 n_create = n_create + 1
841
842 if (n_create > 0) then
843 !$omp critical
844 i_ph = n_used
845 n_used = n_used + n_create
846 !$omp end critical
847 my_num_photons = my_num_photons + n_create
848
849 do n = 1, n_create
850 ! Location of production randomly chosen in cell
851 r(1) = prng%rngs(proc_id)%unif_01()
852 r(2) = prng%rngs(proc_id)%unif_01()
853#if NDIM == 2
854 xyz_src(1:ndim, i_ph+n) = af_rr_cc(tree%boxes(id), &
855 [ijk] - 0.5_dp + r(1:ndim))
856 xyz_src(3, i_ph+n) = 0.0_dp
857#elif NDIM == 3
858 r(3) = prng%rngs(proc_id)%unif_01()
859 xyz_src(:, i_ph+n) = af_rr_cc(tree%boxes(id), &
860 [ijk] - 0.5_dp + r(1:ndim))
861#endif
862 photon_thread(i_ph+n) = proc_id
863 end do
864 end if
865 end do; close_do
866 end do
867 !$omp end do nowait
868 end do
869
870 photons_per_thread(proc_id) = my_num_photons
871 !$omp end parallel
872
873 ! Sort the xyz_src array so that runs are deterministic (the order in which
874 ! the threads write is not deterministic)
875 allocate(ix_offset(n_procs))
876 allocate(xyz_tmp(3, n_used))
877
878 ix_offset(1) = 0
879 do n = 2, n_procs
880 ix_offset(n) = ix_offset(n-1) + photons_per_thread(n-1)
881 end do
882
883 photons_per_thread = 0
884 do n = 1, n_used
885 i = photon_thread(n)
886 photons_per_thread(i) = photons_per_thread(i) + 1
887 ix = ix_offset(i) + photons_per_thread(i)
888 xyz_tmp(:, ix) = xyz_src(:, n)
889 end do
890 xyz_src(:, 1:n_used) = xyz_tmp(:, 1:n_used)
891
892 end subroutine phmc_generate_photons
893
894end module m_photoi_mc
Module with settings and routines to handle dielectrics.
subroutine, public dielectric_reset_photons()
subroutine, public dielectric_photon_absorption(tree, xyz_start, xyz_end, n_dim, n_photons, photon_weight)
Determine whether and where photons hit a dielectric, and change their absorption location to the fir...
Module that stores parameters related to the gas.
Definition m_gas.f90:2
elemental integer function, public gas_index(name)
Find index of a gas component, return -1 if not found.
Definition m_gas.f90:285
real(dp), dimension(:), allocatable, public, protected gas_fractions
Definition m_gas.f90:24
real(dp), public, protected gas_pressure
Definition m_gas.f90:18
Module that provides routines for Monte Carlo photoionization.
subroutine, public phmc_set_src(tree, rng, i_src, i_photo, use_cyl, dt)
Set the source term due to photoionization. At most phmc_num_photons discrete photons are produced.
subroutine, public phmc_get_table_air(phmc_tbl, p_o2, p_h2o, absfunc)
Compute the photonization table for air. If the absorption function is f(r), this table contains r as...
type(phmc_tbl_t), public, protected phmc_tbl
Table for photoionization.
subroutine, public phmc_do_absorption(xyz_in, xyz_out, n_dim, n_photons, tbl, prng)
Given a list of photon production positions (xyz_in), compute where they end up (xyz_out).
subroutine, public phmc_initialize(cfg, is_used, source_type)
Initialize photoionization parameters.
subroutine, public phmc_print_grid_spacing(dr_base)
Print the grid spacing used for the absorption of photons.
logical, public, protected phmc_physical_photons
Whether physical photons are used.
This module contains several pre-defined variables like:
Definition m_streamer.f90:6
integer, public, protected st_box_size
The size of the boxes that we use to construct our mesh.
logical, public, protected st_use_dielectric
Whether a dielectric is used.
real(dp), dimension(ndim), public, protected st_domain_len
Domain length per dimension.
Module that contains physical and numerical constants.
real(dp), parameter uc_pi
real(dp), parameter uc_torr_to_bar
Type to quickly look up absorption lengths from a table.