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 allocate(ph_loc(n_used))
554
555 if (phmc_const_dx) then
556 ! Get a typical length scale for the absorption of photons
557 pi_lengthscale = lt_get_col(phmc_tbl%tbl, 1, phmc_absorp_fac)
558
559 ! Determine at which level we estimate the photoionization source term. This
560 ! depends on the typical length scale for absorption.
561 pho_lvl = get_lvl_length(maxval(tree%dr_base), pi_lengthscale)
562
563 !$omp parallel do
564 do n = 1, n_used
565 ph_loc(n) = af_get_loc(tree, xyz_abs(1:ndim, n), pho_lvl)
566 end do
567 !$omp end parallel do
568 else
569 !$omp parallel private(n, dist, lvl, proc_id)
570 proc_id = 1+omp_get_thread_num()
571 !$omp do
572 do n = 1, n_used
573 dist = phmc_absorp_fac * norm2(xyz_abs(1:ndim, n) - xyz_src(1:ndim, n))
574 if (dist < phmc_min_dx) dist = phmc_min_dx
575 lvl = get_rlvl_length(maxval(tree%dr_base), dist, prng%rngs(proc_id))
576 ph_loc(n) = af_get_loc(tree, xyz_abs(1:ndim, n), lvl)
577 end do
578 !$omp end do
579 !$omp end parallel
580 end if
581
582 ! Clear variable i_photo, in which we will store the photoionization source term
583 call af_tree_clear_cc(tree, i_photo)
584
585 do n = 1, n_used
586 id = ph_loc(n)%id
587 if (id > af_no_box) then
588#if NDIM == 1
589 i = ph_loc(n)%ix(1)
590 dr = tree%boxes(id)%dr
591
592 tree%boxes(id)%cc(ijk, i_photo) = &
593 tree%boxes(id)%cc(ijk, i_photo) + &
594 phmc_tbl%full_integral/(dt_fac * product(dr))
595#elif NDIM == 2
596 i = ph_loc(n)%ix(1)
597 j = ph_loc(n)%ix(2)
598 dr = tree%boxes(id)%dr
599
600 if (use_cyl) then
601 tmp = dt_fac * 2 * pi
602 r(1:2) = af_r_cc(tree%boxes(id), [i, j])
603 tree%boxes(id)%cc(i, j, i_photo) = &
604 tree%boxes(id)%cc(i, j, i_photo) + &
605 phmc_tbl%full_integral/(tmp * product(dr) * r(1))
606 else
607 tree%boxes(id)%cc(ijk, i_photo) = &
608 tree%boxes(id)%cc(ijk, i_photo) + &
609 phmc_tbl%full_integral/(dt_fac * product(dr))
610 end if
611#elif NDIM == 3
612 i = ph_loc(n)%ix(1)
613 j = ph_loc(n)%ix(2)
614 k = ph_loc(n)%ix(3)
615 dr = tree%boxes(id)%dr
616
617 tree%boxes(id)%cc(ijk, i_photo) = &
618 tree%boxes(id)%cc(ijk, i_photo) + &
619 phmc_tbl%full_integral/(dt_fac * product(dr))
620#endif
621 end if
622 end do
623
624 ! Set ghost cells on highest level with photon source
625 if (phmc_const_dx) then
626 min_lvl = pho_lvl
627 else
628 min_lvl = 1
629 end if
630
631 !$omp parallel private(lvl, i, id)
632 ! Prolong to finer grids
633 do lvl = min_lvl, tree%highest_lvl-1
634 !$omp do
635 do i = 1, size(tree%lvls(lvl)%parents)
636 id = tree%lvls(lvl)%parents(i)
637 call af_gc_box(tree, id, [i_photo])
638 end do
639 !$omp end do
640
641 !$omp do
642 do i = 1, size(tree%lvls(lvl)%parents)
643 id = tree%lvls(lvl)%parents(i)
644 call af_prolong_linear_from(tree%boxes, id, i_photo, add=.true.)
645 end do
646 !$omp end do
647 end do
648 !$omp end parallel
649
650 call prng%update_seed(rng)
651 end subroutine phmc_set_src
652
653! !> Whole photoemission procedure
654! subroutine phe_mc_set_src(tree, rng, i_src, i_photo, use_cyl, dt)
655! use m_random
656! use m_lookup_table
657! use m_dielectric2
658! use omp_lib
659
660! type(af_t), intent(inout) :: tree !< Tree
661! type(RNG_t), intent(inout) :: rng !< Random number generator
662! !> Input variable that contains photon production per cell
663! integer, intent(in) :: i_src
664! !> Input variable that contains photoionization source rate
665! integer, intent(in) :: i_photo
666! !> Use cylindrical coordinates (only in 2D)
667! logical, intent(in) :: use_cyl
668! !> Time step, if present use "physical" photons
669! real(dp), intent(in), optional :: dt
670
671! integer :: nc
672! integer :: n, n_used
673! integer :: proc_id, n_procs
674! integer :: pho_lvl, max_num_photons
675! real(dp) :: tmp, dt_fac, r(3)
676! real(dp) :: sum_production_rate, pi_lengthscale
677! real(dp), allocatable :: xyz_src(:, :)
678! real(dp), allocatable :: xyz_abs(:, :)
679! #if NDIM == 2
680! real(dp), parameter :: pi = acos(-1.0_dp)
681! #endif
682! type(PRNG_t) :: prng
683! type(af_loc_t), allocatable :: ph_loc(:)
684! real(dp), parameter :: small_value = 1e-100_dp
685
686! if (NDIM == 3 .and. use_cyl) error stop "phmc_set_src: use_cyl is .true."
687
688! if (ST_use_dielectric) then
689! call dielectric2_reset_photons()
690! end if
691
692! nc = tree%n_cell
693
694! ! Initialize parallel random numbers
695! n_procs = omp_get_max_threads()
696! call prng%init_parallel(n_procs, rng)
697
698! ! Compute the sum of the photon production rate
699! call af_tree_sum_cc(tree, i_src, sum_production_rate)
700
701! ! dt_fac is used to convert a discrete number of photons to a number of
702! ! photons per unit time. Depending on the arguments dt and phmc_num_photons,
703! ! 'physical' photons with a weight of 1.0, or super-photons with a weight
704! ! larger (or smaller) than one can be used.
705! if (present(dt)) then
706! ! Create "physical" photons when less than phmc_num_photons are produced,
707! ! otherwise create approximately phmc_num_photons
708! dt_fac = min(dt, phmc_num_photons / (sum_production_rate + small_value))
709! else
710! ! Create approximately phmc_num_photons by setting dt_fac like this
711! dt_fac = phmc_num_photons / (sum_production_rate + small_value)
712! end if
713
714! ! Over-estimate number of photons because of stochastic production
715! max_num_photons = nint(1.2_dp * dt_fac * sum_production_rate + 1000)
716
717! call phmc_generate_photons(tree, dt_fac, i_src, xyz_src, &
718! max_num_photons, n_used, prng)
719
720! allocate(xyz_abs(3, n_used))
721
722! if (use_cyl) then ! 2D only
723! ! Get location of absorption. On input, xyz is set to (r, z, 0). On
724! ! output, the coordinates thus correspond to (x, z, y)
725! call phe_mc_get_endloc(xyz_src, xyz_abs, 3, n_used, prng)
726
727! !$omp do
728! do n = 1, n_used
729! ! Convert back to (r,z) coordinates
730! xyz_abs(1, n) = sqrt(xyz_abs(1, n)**2 + xyz_abs(3, n)**2)
731! xyz_abs(2, n) = xyz_abs(2, n)
732! xyz_src(2, n) = xyz_src(3, n)
733! end do
734! !$omp end do
735
736! if (ST_use_dielectric) then
737! ! Handle photons that collide with dielectrics separately
738! call dielectric2_photon_absorption(tree, xyz_src, xyz_abs, &
739! 2, n_used, 1.0_dp/dt_fac)
740! end if
741! else
742! ! Get location of absorbption
743! call phe_mc_get_endloc(xyz_src, xyz_abs, NDIM, n_used, prng)
744
745! if (ST_use_dielectric) then
746! ! Handle photons that collide with dielectrics separately
747! call dielectric2_photon_absorption(tree, xyz_src, xyz_abs, &
748! NDIM, n_used, 1.0_dp/dt_fac)
749! end if
750! end if
751
752! call prng%update_seed(rng)
753! end subroutine phe_mc_set_src
754
755
756 subroutine phmc_generate_photons(tree, dt_fac, i_src, xyz_src, n_max, n_used, prng)
757 use omp_lib
758 use m_random
759 type(af_t), intent(in) :: tree
760 !> Time step to convert photon production rate to number of photons
761 real(dp), intent(in) :: dt_fac
762 !> Index of cell-centered variable containing photon production rate
763 integer, intent(in) :: i_src
764 !> Origin of the produced photons
765 real(dp), allocatable, intent(inout) :: xyz_src(:, :)
766 !> Maximum number of photons that will be produced
767 integer, intent(in) :: n_max
768 !> Number of photons that have been created
769 integer, intent(out) :: n_used
770 !> Parallel random number generator
771 type(prng_t), intent(inout) :: prng
772
773 integer :: proc_id, n_procs
774 integer :: lvl, ix, id, ijk, n, nc, i_ph
775 integer :: n_create, my_num_photons
776 real(dp) :: tmp, dr(ndim), r(3)
777 integer, allocatable :: photons_per_thread(:), photon_thread(:)
778 integer, allocatable :: ix_offset(:)
779 real(dp), allocatable :: xyz_tmp(:, :)
780
781 nc = tree%n_cell
782 n_procs = omp_get_max_threads()
783
784 allocate(xyz_src(3, n_max))
785 allocate(photons_per_thread(n_procs))
786 allocate(photon_thread(n_max))
787
788 ! Loop over all leaves and create photons using random numbers
789 n_used = 0
790
791 !$omp parallel private(lvl, ix, id, IJK, n, r, dr, i_ph, proc_id, &
792 !$omp tmp, n_create, my_num_photons)
793 proc_id = 1+omp_get_thread_num()
794 my_num_photons = 0
795
796 do lvl = 1, tree%highest_lvl
797 dr = af_lvl_dr(tree, lvl)
798 !$omp do
799 do ix = 1, size(tree%lvls(lvl)%leaves)
800 id = tree%lvls(lvl)%leaves(ix)
801
802 do kji_do(1,nc)
803
804#if NDIM == 2
805 if (tree%boxes(id)%coord_t == af_cyl) then
806 tmp = af_cyl_radius_cc(tree%boxes(id), i)
807 tmp = dt_fac * 2 * uc_pi * tmp * &
808 tree%boxes(id)%cc(i, j, i_src) * product(dr)
809 else
810 tmp = dt_fac * tree%boxes(id)%cc(i, j, i_src) * product(dr)
811 end if
812#else
813 tmp = dt_fac * tree%boxes(id)%cc(ijk, i_src) * product(dr)
814#endif
815
816 n_create = floor(tmp)
817
818 if (prng%rngs(proc_id)%unif_01() < tmp - n_create) &
819 n_create = n_create + 1
820
821 if (n_create > 0) then
822 !$omp critical
823 i_ph = n_used
824 n_used = n_used + n_create
825 !$omp end critical
826 my_num_photons = my_num_photons + n_create
827
828 do n = 1, n_create
829 ! Location of production randomly chosen in cell
830 r(1) = prng%rngs(proc_id)%unif_01()
831 r(2) = prng%rngs(proc_id)%unif_01()
832#if NDIM == 2
833 xyz_src(1:ndim, i_ph+n) = af_rr_cc(tree%boxes(id), &
834 [ijk] - 0.5_dp + r(1:ndim))
835 xyz_src(3, i_ph+n) = 0.0_dp
836#elif NDIM == 3
837 r(3) = prng%rngs(proc_id)%unif_01()
838 xyz_src(:, i_ph+n) = af_rr_cc(tree%boxes(id), &
839 [ijk] - 0.5_dp + r(1:ndim))
840#endif
841 photon_thread(i_ph+n) = proc_id
842 end do
843 end if
844 end do; close_do
845 end do
846 !$omp end do nowait
847 end do
848
849 photons_per_thread(proc_id) = my_num_photons
850 !$omp end parallel
851
852 ! Sort the xyz_src array so that runs are deterministic (the order in which
853 ! the threads write is not deterministic)
854 allocate(ix_offset(n_procs))
855 allocate(xyz_tmp(3, n_used))
856
857 ix_offset(1) = 0
858 do n = 2, n_procs
859 ix_offset(n) = ix_offset(n-1) + photons_per_thread(n-1)
860 end do
861
862 photons_per_thread = 0
863 do n = 1, n_used
864 i = photon_thread(n)
865 photons_per_thread(i) = photons_per_thread(i) + 1
866 ix = ix_offset(i) + photons_per_thread(i)
867 xyz_tmp(:, ix) = xyz_src(:, n)
868 end do
869 xyz_src(:, 1:n_used) = xyz_tmp(:, 1:n_used)
870
871 end subroutine phmc_generate_photons
872
873end 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.