Afivo  0.3
simple_streamer_2d.f90
1 
4 program simple_streamer_2d
5 
6  use m_a2_types
7  use m_a2_core
9  use m_a2_utils
10  use m_a2_restrict
11  use m_a2_multigrid
12  use m_a2_output
13  use m_write_silo
14  use m_a2_prolong
15 
16  implicit none
17 
18  integer :: i, n
19  character(len=100) :: fname
20  type(a2_t) :: tree ! This contains the full grid information
21  type(mg2_t) :: mg ! Multigrid option struct
22  type(ref_info_t) :: refine_info
23 
24  ! Indices of cell-centered variables
25  integer, parameter :: n_var_cell = 7 ! Number of variables
26  integer, parameter :: i_elec = 1 ! Electron density
27  integer, parameter :: i_pion = 2 ! Positive ion density
28  integer, parameter :: i_elec_old = 3 ! For time-stepping scheme
29  integer, parameter :: i_pion_old = 4 ! For time-stepping scheme
30  integer, parameter :: i_phi = 5 ! Electrical potential
31  integer, parameter :: i_fld = 6 ! Electric field norm
32  integer, parameter :: i_rhs = 7 ! Source term Poisson
33 
34  ! Indices of face-centered variables **
35  integer, parameter :: n_var_face = 2 ! Number of variables
36  integer, parameter :: f_elec = 1 ! Electron flux
37  integer, parameter :: f_fld = 2 ! Electric field vector
38 
39  ! Names of the cell-centered variables
40  character(len=10) :: st_cc_names(n_var_cell) = &
41  [character(len=10) :: "elec", "pion", "elec_old", &
42  "pion_old", "phi", "fld", "rhs"]
43 
44  ! Simulation parameters
45  real(dp), parameter :: end_time = 8e-9_dp
46  real(dp), parameter :: dt_output = 20e-11_dp
47  real(dp), parameter :: dt_max = 20e-11_dp
48  integer, parameter :: ref_per_steps = 2
49  integer, parameter :: box_size = 8
50 
51  ! Physical parameters
52  real(dp), parameter :: applied_field = -0.8e7_dp
53  real(dp), parameter :: mobility = 0.03_dp
54  real(dp), parameter :: diffusion_c = 0.2_dp
55 
56  ! Computational domain
57  real(dp), parameter :: domain_length = 10e-3_dp
58  real(dp), parameter :: refine_max_dx = 1e-3_dp
59  real(dp), parameter :: refine_min_dx = 1e-9_dp
60 
61  ! Settings for the initial conditions
62  real(dp), parameter :: init_density = 1e15_dp
63  real(dp), parameter :: init_y_min = 8.0e-3_dp
64  real(dp), parameter :: init_y_max = 9.0e-3_dp
65 
66  ! Simulation variables
67  real(dp) :: dt
68  real(dp) :: time
69  integer :: output_count
70 
71  ! To test charge conservation
72  real(dp) :: sum_elec, sum_pion
73 
74  ! Initialize the tree (which contains all the mesh information)
75  call init_tree(tree)
76 
77  ! Set the multigrid options. First define the variables to use
78  mg%i_phi = i_phi
79  mg%i_tmp = i_fld
80  mg%i_rhs = i_rhs
81 
82  ! Routines to use for ...
83  mg%sides_bc => sides_bc_pot ! Filling ghost cell on physical boundaries
84 
85  ! This routine always needs to be called when using multigrid
86  call mg2_init_mg(mg)
87 
88  call a2_set_cc_methods(tree, i_elec, a2_bc_dirichlet_zero, &
89  a2_gc_interp_lim, a2_prolong_limit)
90  call a2_set_cc_methods(tree, i_pion, a2_bc_dirichlet_zero, &
91  a2_gc_interp_lim, a2_prolong_limit)
92  call a2_set_cc_methods(tree, i_phi, mg%sides_bc, mg%sides_rb)
93 
94  output_count = 0 ! Number of output files written
95  time = 0 ! Simulation time (all times are in s)
96 
97  ! Set up the initial conditions
98  do
99  ! For each box in tree, set the initial conditions
100  call a2_loop_box(tree, set_initial_condition)
101 
102  ! Compute electric field on the tree.
103  ! First perform multigrid to get electric potential,
104  ! then take numerical gradient to geld field.
105  call compute_fld(tree, .false.)
106 
107  ! Adjust the refinement of a tree using refine_routine (see below) for grid
108  ! refinement.
109  ! Routine a2_adjust_refinement sets the bit af_bit_new_children for each box
110  ! that is refined. On input, the tree should be balanced. On output,
111  ! the tree is still balanced, and its refinement is updated (with at most
112  call a2_adjust_refinement(tree, & ! tree
113  refinement_routine, & ! Refinement function
114  refine_info) ! Information about refinement
115 
116  ! If no new boxes have been added or removed, exit the loop
117  if (refine_info%n_add == 0 .and. refine_info%n_rm == 0) exit
118  end do
119 
120  call a2_print_info(tree)
121 
122  do
123  ! Get a new time step, which is at most dt_max
124  call a2_reduction(tree, & ! Tree to do the reduction on
125  max_dt, & ! function
126  get_min, & ! function
127  dt_max, & ! Initial value for the reduction
128  dt) ! Result of the reduction
129 
130  if (dt < 1e-14) then
131  print *, "dt getting too small, instability?", dt
132  time = end_time + 1.0_dp
133  end if
134 
135  ! Every dt_output, write output
136  if (output_count * dt_output <= time) then
137  output_count = output_count + 1
138  write(fname, "(A,I6.6)") "simple_streamer_2d_", output_count
139 
140  ! Write the cell centered data of a tree to a Silo file. Only the
141  ! leaves of the tree are used
142  call a2_write_silo(tree, fname, output_count, time, dir="output")
143 
144  call a2_tree_sum_cc(tree, i_elec, sum_elec)
145  call a2_tree_sum_cc(tree, i_pion, sum_pion)
146  print *, "sum(pion-elec)/sum(pion)", (sum_pion - sum_elec)/sum_pion
147  end if
148 
149  if (time > end_time) exit
150 
151  ! We perform n_steps between mesh-refinements
152  do n = 1, ref_per_steps
153  time = time + dt
154 
155  ! Copy previous solution
156  call a2_tree_copy_cc(tree, i_elec, i_elec_old)
157  call a2_tree_copy_cc(tree, i_pion, i_pion_old)
158 
159  ! Two forward Euler steps over dt
160  do i = 1, 2
161  ! First calculate fluxes
162  call a2_loop_boxes(tree, fluxes_koren, leaves_only=.true.)
163  call a2_consistent_fluxes(tree, [f_elec])
164 
165  ! Update the solution
166  call a2_loop_box_arg(tree, update_solution, [dt], &
167  leaves_only=.true.)
168 
169  ! Compute new field on first iteration
170  if (i == 1) call compute_fld(tree, .true.)
171  end do
172 
173  ! Take average of phi_old and phi (explicit trapezoidal rule)
174  call a2_loop_box(tree, average_dens)
175 
176  ! Compute field with new density
177  call compute_fld(tree, .true.)
178  end do
179 
180  ! Fill ghost cells for i_pion and i_elec
181  call a2_gc_tree(tree, i_elec)
182  call a2_gc_tree(tree, i_pion)
183 
184  ! Adjust the refinement of a tree using refine_routine (see below) for grid
185  ! refinement.
186  ! Routine a2_adjust_refinement sets the bit af_bit_new_children for each box
187  ! that is refined. On input, the tree should be balanced. On output,
188  ! the tree is still balanced, and its refinement is updated (with at most
189  ! one level per call).
190  call a2_adjust_refinement(tree, & ! tree
191  refinement_routine, & ! Refinement function
192  refine_info, & ! Information about refinement
193  4) ! Buffer width (in cells)
194 
195  if (refine_info%n_add > 0 .or. refine_info%n_rm > 0) then
196  ! Compute the field on the new mesh
197  call compute_fld(tree, .true.)
198  end if
199  end do
200 
201 contains
202 
204  subroutine init_tree(tree)
205  type(a2_t), intent(inout) :: tree
206 
207  ! Variables used below to initialize tree
208  real(dp) :: dr
209  integer :: id
210  integer :: ix_list(2, 1) ! Spatial indices of initial boxes
211  integer :: nb_list(4, 1) ! Neighbors of initial boxes
212  integer :: n_boxes_init = 1000
213 
214  dr = domain_length / box_size
215 
216  ! Initialize tree
217  call a2_init(tree, & ! The tree to initialize
218  box_size, & ! Boxes have box_size^dim cells
219  n_var_cell, & ! Number of cell-centered variables
220  n_var_face, & ! Number of face-centered variables
221  dr, & ! spacing of a cell at lvl 1
222  coarsen_to=2, & ! Create additional coarse grids down to this size.
223  n_boxes = n_boxes_init, & ! Allocate initial storage for n_boxes.
224  cc_names=st_cc_names) ! Names of cell-centered variables
225 
226  ! Set up geometry
227  id = 1 ! One box ...
228  ix_list(:, id) = [1,1] ! With index 1,1 ...
229 
230  nb_list(a2_neighb_lowy, id) = -1 ! physical boundary
231  nb_list(a2_neighb_highy, id) = -1 ! idem
232  nb_list(a2_neighb_lowx, id) = id ! periodic boundary
233  nb_list(a2_neighb_highx, id) = id ! idem
234 
235  ! Create the base mesh
236  call a2_set_base(tree, 1, ix_list, nb_list)
237 
238  end subroutine init_tree
239 
241  subroutine refinement_routine(box, cell_flags)
242  type(box2_t), intent(in) :: box
243  integer, intent(out) :: cell_flags(box%n_cell, box%n_cell)
244  integer :: i, j, nc
245  real(dp) :: dx, dens, fld, adx, xy(2)
246 
247  nc = box%n_cell
248  dx = box%dr
249 
250  do j = 1, nc
251  do i = 1, nc
252  xy = a2_r_cc(box, [i,j])
253  dens = box%cc(i, j, i_elec)
254  fld = box%cc(i, j, i_fld)
255  adx = get_alpha(fld) * dx
256 
257  if (dens > 1e0_dp .and. adx > 0.8_dp) then
258  cell_flags(i, j) = af_do_ref
259  else if (dx < 1.25e-5_dp .and. adx < 0.1_dp) then
260  cell_flags(i, j) = af_rm_ref
261  else
262  cell_flags(i, j) = af_keep_ref
263  end if
264  end do
265  end do
266 
267  ! Make sure we don't have or get a too fine or too coarse grid
268  if (dx > refine_max_dx) then
269  cell_flags = af_do_ref
270  else if (dx < 2 * refine_min_dx) then
271  where(cell_flags == af_do_ref) cell_flags = af_keep_ref
272  else if (dx > 0.5_dp * refine_max_dx) then
273  where(cell_flags == af_rm_ref) cell_flags = af_keep_ref
274  end if
275 
276  end subroutine refinement_routine
277 
279  subroutine set_initial_condition(box)
280  type(box2_t), intent(inout) :: box
281  integer :: i, j, nc
282  real(dp) :: xy(2), normal_rands(2)
283 
284  nc = box%n_cell
285 
286  do j = 0, nc+1
287  do i = 0, nc+1
288  xy = a2_r_cc(box, [i,j])
289 
290  if (xy(2) > init_y_min .and. xy(2) < init_y_max) then
291  ! Approximate Poisson distribution with normal distribution
292  normal_rands = two_normals(box%dr**3 * init_density, &
293  sqrt(box%dr**3 * init_density))
294  ! Prevent negative numbers
295  box%cc(i, j, i_elec) = abs(normal_rands(1)) / box%dr**3
296  else
297  box%cc(i, j, i_elec) = 0
298  end if
299  end do
300  end do
301 
302  box%cc(:, :, i_pion) = box%cc(:, :, i_elec)
303  box%cc(:, :, i_phi) = 0 ! Inital potential set to zero
304 
305  end subroutine set_initial_condition
306 
309  function two_normals(mean, sigma) result(rands)
310  real(dp), intent(in) :: mean, sigma
311  real(dp) :: rands(2), sum_sq
312 
313  do
314  call random_number(rands)
315  rands = 2 * rands - 1
316  sum_sq = sum(rands**2)
317  if (sum_sq < 1.0_dp .and. sum_sq > 0.0_dp) exit
318  end do
319  rands = rands * sqrt(-2 * log(sum_sq) / sum_sq)
320  rands = mean + rands * sigma
321  end function two_normals
322 
324  real(dp) function get_min(a, b)
325  real(dp), intent(in) :: a, b
326  get_min = min(a, b)
327  end function get_min
328 
330  real(dp) function max_dt(box)
331  type(box2_t), intent(in) :: box
332  real(dp), parameter :: uc_eps0 = 8.8541878176d-12
333  real(dp), parameter :: uc_elem_charge = 1.6022d-19
334  integer :: i, j, nc
335  real(dp) :: fld(2)
336  real(dp) :: dt_cfl, dt_dif, dt_drt
337 
338  nc = box%n_cell
339  dt_cfl = dt_max
340 
341  do j = 1, nc
342  do i = 1, nc
343  fld(1) = 0.5_dp * (box%fc(i, j, 1, f_fld) + &
344  box%fc(i+1, j, 1, f_fld))
345  fld(2) = 0.5_dp * (box%fc(i, j, 2, f_fld) + &
346  box%fc(i, j+1, 2, f_fld))
347 
348  ! The 0.5 is here because of the explicit trapezoidal rule
349  dt_cfl = min(dt_cfl, 0.5_dp / sum(abs(fld * mobility) / box%dr))
350  end do
351  end do
352 
353  ! Dielectric relaxation time
354  dt_drt = uc_eps0 / (uc_elem_charge * mobility * &
355  maxval(box%cc(1:nc, 1:nc, i_elec)) + epsilon(1.0_dp))
356 
357  ! Diffusion condition
358  dt_dif = 0.25_dp * box%dr**2 / max(diffusion_c, epsilon(1.0_dp))
359 
360  max_dt = min(dt_cfl, dt_drt, dt_dif)
361  end function max_dt
362 
363 
369  elemental function get_alpha(fld) result(alpha)
370  real(dp), intent(in) :: fld
371  real(dp) :: alpha
372  real(dp), parameter :: c0 = 1.04e1_dp
373  real(dp), parameter :: c1 = 0.601_dp
374  real(dp), parameter :: c2 = 1.86e7_dp
375 
376  alpha = exp(c0) * (abs(fld) * 1e-5_dp)**c1 * exp(-c2 / abs(fld))
377  end function get_alpha
378 
379  ! Compute electric field on the tree. First perform multigrid to get electric
380  ! potential, then take numerical gradient to geld field.
381  subroutine compute_fld(tree, have_guess)
382  type(a2_t), intent(inout) :: tree
383  logical, intent(in) :: have_guess
384  real(dp), parameter :: fac = 1.6021766208e-19_dp / 8.8541878176e-12_dp
385  integer :: lvl, i, id, nc
386 
387  nc = tree%n_cell
388 
389  ! Set the source term (rhs)
390  !$omp parallel private(lvl, i, id)
391  do lvl = 1, tree%highest_lvl
392  !$omp do
393  do i = 1, size(tree%lvls(lvl)%leaves)
394  id = tree%lvls(lvl)%leaves(i)
395  tree%boxes(id)%cc(:, :, i_rhs) = fac * (&
396  tree%boxes(id)%cc(:, :, i_elec) - &
397  tree%boxes(id)%cc(:, :, i_pion))
398  end do
399  !$omp end do nowait
400  end do
401  !$omp end parallel
402 
403  ! Perform an FMG cycle (logicals: store residual, first call)
404  call mg2_fas_fmg(tree, mg, .false., have_guess)
405 
406  ! Compute field from potential
407  call a2_loop_box(tree, fld_from_pot)
408 
409  ! Set the field norm also in ghost cells
410  call a2_gc_tree(tree, i_fld, a2_gc_interp, a2_bc_neumann_zero)
411  end subroutine compute_fld
412 
413  ! Compute electric field from electrical potential
414  subroutine fld_from_pot(box)
415  type(box2_t), intent(inout) :: box
416  integer :: nc
417  real(dp) :: inv_dr
418 
419  nc = box%n_cell
420  inv_dr = 1 / box%dr
421 
422  box%fc(1:nc+1, 1:nc, 1, f_fld) = inv_dr * &
423  (box%cc(0:nc, 1:nc, i_phi) - box%cc(1:nc+1, 1:nc, i_phi))
424  box%fc(1:nc, 1:nc+1, 2, f_fld) = inv_dr * &
425  (box%cc(1:nc, 0:nc, i_phi) - box%cc(1:nc, 1:nc+1, i_phi))
426 
427  box%cc(1:nc, 1:nc, i_fld) = sqrt(&
428  0.25_dp * (box%fc(1:nc, 1:nc, 1, f_fld) + &
429  box%fc(2:nc+1, 1:nc, 1, f_fld))**2 + &
430  0.25_dp * (box%fc(1:nc, 1:nc, 2, f_fld) + &
431  box%fc(1:nc, 2:nc+1, 2, f_fld))**2)
432  end subroutine fld_from_pot
433 
434  ! Compute the electron fluxes due to drift and diffusion
435  subroutine fluxes_koren(boxes, id)
436  use m_flux_schemes
437  type(box2_t), intent(inout) :: boxes(:)
438  integer, intent(in) :: id
439  real(dp) :: inv_dr
440  real(dp) :: cc(-1:boxes(id)%n_cell+2, -1:boxes(id)%n_cell+2)
441  real(dp), allocatable :: v(:, :, :), dc(:, :, :)
442  integer :: nc
443 
444  nc = boxes(id)%n_cell
445  inv_dr = 1/boxes(id)%dr
446 
447  allocate(v(1:nc+1, 1:nc+1, 2))
448  allocate(dc(1:nc+1, 1:nc+1, 2))
449 
450  call a2_gc_box(boxes, id, i_elec, a2_gc_interp_lim, &
451  a2_bc_dirichlet_zero)
452  call a2_gc2_box(boxes, id, i_elec, a2_gc2_prolong_linear, &
453  a2_bc2_dirichlet_zero, cc, nc)
454 
455  v = -mobility * boxes(id)%fc(:, :, :, f_fld)
456  dc = diffusion_c
457 
458  call flux_koren_2d(cc, v, nc, 2)
459  call flux_diff_2d(cc, dc, inv_dr, nc, 2)
460 
461  boxes(id)%fc(:, :, :, f_elec) = v + dc
462  end subroutine fluxes_koren
463 
464  ! Take average of new and old electron/ion density for explicit trapezoidal rule
465  subroutine average_dens(box)
466  type(box2_t), intent(inout) :: box
467  box%cc(:, :, i_elec) = 0.5_dp * (box%cc(:, :, i_elec) + box%cc(:, :, i_elec_old))
468  box%cc(:, :, i_pion) = 0.5_dp * (box%cc(:, :, i_pion) + box%cc(:, :, i_pion_old))
469  end subroutine average_dens
470 
471  ! Advance solution over dt based on the fluxes / source term, using forward Euler
472  subroutine update_solution(box, dt)
473  type(box2_t), intent(inout) :: box
474  real(dp), intent(in) :: dt(:)
475  real(dp) :: inv_dr, src, sflux, fld
476  real(dp) :: alpha
477  integer :: i, j, nc
478 
479  nc = box%n_cell
480  inv_dr = 1/box%dr
481  do j = 1, nc
482  do i = 1, nc
483  fld = box%cc(i,j, i_fld)
484  alpha = get_alpha(fld)
485  src = box%cc(i, j, i_elec) * mobility * abs(fld) * alpha
486 
487  sflux = (sum(box%fc(i, j, :, f_elec)) - &
488  box%fc(i+1, j, 1, f_elec) - &
489  box%fc(i, j+1, 2, f_elec)) * inv_dr
490 
491  box%cc(i, j, i_elec) = box%cc(i, j, i_elec) + (src + sflux) * dt(1)
492  box%cc(i, j, i_pion) = box%cc(i, j, i_pion) + src * dt(1)
493  end do
494  end do
495 
496  end subroutine update_solution
497 
499  subroutine sides_bc_pot(box, nb, iv, bc_type)
500  type(box2_t), intent(inout) :: box
501  integer, intent(in) :: nb ! Direction for the boundary condition
502  integer, intent(in) :: iv ! Index of variable
503  integer, intent(out) :: bc_type ! Type of boundary condition
504  integer :: nc
505 
506  nc = box%n_cell
507 
508  select case (nb)
509  case (a2_neighb_lowy)
510  bc_type = af_bc_neumann
511  box%cc(1:nc, 0, iv) = applied_field
512  case (a2_neighb_highy)
513  bc_type = af_bc_dirichlet
514  box%cc(1:nc, nc+1, iv) = 0
515  case default
516  stop "sides_bc_pot: unspecified boundary"
517  end select
518  end subroutine sides_bc_pot
519 
520 end program simple_streamer_2d
This module contains routines for writing output files with Afivo. The Silo format should probably be...
Definition: m_a2_output.f90:4
This module contains wrapper functions to simplify writing Silo files.
Definition: m_write_silo.f90:3
Type to store multigrid options in.
Module containing a couple simple flux schemes for scalar advection/diffusion problems.
This module contains all kinds of different &#39;helper&#39; routines for Afivo. If the number of routines fo...
Definition: m_a2_utils.f90:5
This module contains routines related to the filling of ghost cells. Note that corner ghost cells are...
Type which stores all the boxes and levels, as well as some information about the number of boxes...
Definition: m_a2_types.f90:93
This module contains the basic types and constants that are used in the 2-dimensional version of Afiv...
Definition: m_a2_types.f90:5
This module contains the core routines of Afivo, namely those that deal with initializing and changin...
Definition: m_a2_core.f90:4
This module contains routines for restriction: going from fine to coarse variables.
This module contains the routines related to prolongation: going from coarse to fine variables...
Definition: m_a2_prolong.f90:4
This module contains the geometric multigrid routines that come with Afivo.
The basic building block of afivo: a box with cell-centered and face centered data, and information about its position, neighbors, children etc.
Definition: m_a2_types.f90:71