Afivo  0.3
drift_diffusion_3d.f90

A drift-diffusion example. Diffusion is implemented with centered differences, and for the drift the Koren flux limiter is used. Time stepping is done with the explicit trapezoidal rule.

1 
7 program drift_diffusion_3d
8  use m_a3_all
9 
10  implicit none
11 
12  integer, parameter :: box_size = 8
13  integer, parameter :: i_phi = 1
14  integer, parameter :: i_phi_old = 2
15  integer, parameter :: i_err = 3
16  integer, parameter :: sol_type = 1
17  real(dp), parameter :: domain_len = 2 * acos(-1.0_dp)
18  real(dp), parameter :: dr = domain_len / box_size
19 
20  type(a3_t) :: tree
21  type(ref_info_t) :: refine_info
22  integer :: ix_list(3, 1)
23  integer :: nb_list(a3_num_neighbors, 1)
24  integer :: refine_steps, time_steps, output_cnt
25  integer :: i, id, n, n_steps
26  real(dp) :: dt, time, end_time, p_err, n_err, sum_phi
27  real(dp) :: dt_adapt, dt_output
28  real(dp) :: diff_coeff, velocity(3), dr_min(3)
29  character(len=100) :: fname
30  integer :: count_rate, t_start, t_end
31 
32  print *, "Running drift_diffusion_3d"
33  print *, "Number of threads", af_get_max_threads()
34 
35  ! Initialize tree
36  call a3_init(tree, & ! Tree to initialize
37  box_size, & ! A box contains box_size**DIM cells
38  n_var_cell=3, & ! Number of cell-centered variables
39  n_var_face=2, & ! Number of face-centered variables
40  dr=dr, & ! Distance between cells on base level
41  cc_names=["phi", "old", "err"]) ! Variable names
42 
43  ! Set up geometry
44  id = 1
45  ix_list(:, id) = 1 ! Set index of box
46  nb_list(:, id) = id ! Box is periodic, so its own neighbor
47 
48  ! Create the base mesh, using the box indices and their neighbor information
49  call a3_set_base(tree, 1, ix_list, nb_list)
50 
51  output_cnt = 0
52  time = 0
53  dt_adapt = 0.01_dp
54  dt_output = 0.05_dp
55  end_time = 1.5_dp
56  diff_coeff = 0.0_dp
57  velocity(:) = 0.0_dp
58  velocity(1) = 1.0_dp
59  velocity(2) = -1.0_dp
60 
61  ! Set up the initial conditions
62  call system_clock(t_start,count_rate)
63  refine_steps=0
64 
65  do
66  refine_steps=refine_steps+1
67  ! We should only set the finest level, but this also works
68  call a3_loop_box(tree, set_initial_condition)
69 
70  ! Fill ghost cells for variables i_phi on the sides of all boxes, using
71  ! a3_gc_interp_lim on refinement boundaries: Interpolation between fine
72  ! points and coarse neighbors to fill ghost cells near refinement
73  ! boundaries. The ghost values are less than twice the coarse values. and
74  ! a3_bc_neumann_zero physical boundaries: fill ghost cells near physical
75  ! boundaries using Neumann zero
76  call a3_gc_tree(tree, i_phi, a3_gc_interp_lim, a3_bc_neumann_zero)
77 
78  ! Adjust the refinement of a tree using refine_routine (see below) for grid
79  ! refinement.
80  ! Routine a3_adjust_refinement sets the bit af_bit_new_children for each box
81  ! that is refined. On input, the tree should be balanced. On output,
82  ! the tree is still balanced, and its refinement is updated (with at most
83  ! one level per call).
84  call a3_adjust_refinement(tree, & ! tree
85  refine_routine, & ! Refinement function
86  refine_info, & ! Information about refinement
87  2) ! Buffer width (in cells)
88 
89  ! If no new boxes have been added, exit the loop
90  if (refine_info%n_add == 0) exit
91  end do
92  call system_clock(t_end, count_rate)
93 
94  write(*, "(A,i0,A,Es10.3,A)") " Wall-clock time for ", &
95  refine_steps, " refinement steps: ", &
96  (t_end-t_start) / real(count_rate, dp), " seconds"
97 
98  call a3_print_info(tree)
99 
100  ! Restrict the initial conditions Restrict the children of a box to the box
101  ! (e.g., in 3D, average the values at the four children to get the value for
102  ! the parent)
103  call a3_restrict_tree(tree, i_phi)
104 
105  ! Fill ghost cells for variables i_phi on the sides of all boxes, using
106  ! a3_gc_interp_lim on refinement boundaries and a3_bc_neumann_zero on
107  ! physical boundaries
108  call a3_gc_tree(tree, i_phi, a3_gc_interp_lim, a3_bc_neumann_zero)
109 
110  call system_clock(t_start, count_rate)
111  time_steps = 0
112 
113  ! Starting simulation
114  do
115  time_steps = time_steps + 1
116  dr_min = a3_min_dr(tree)
117  dt = 0.5_dp / (2 * diff_coeff * sum(1/dr_min**2) + &
118  sum(abs(velocity/dr_min)) + epsilon(1.0_dp))
119 
120  n_steps = ceiling(dt_adapt/dt)
121  dt = dt_adapt / n_steps
122 
123  if (output_cnt * dt_output <= time) then
124  output_cnt = output_cnt + 1
125  write(fname, "(A,I0)") "drift_diffusion_3d_", output_cnt
126 
127  ! Call procedure set_error (see below) for each box in tree, with argument time
128  call a3_loop_box_arg(tree, set_error, [time])
129 
130  ! Write the cell centered data of tree to a vtk unstructured file fname.
131  ! Only the leaves of the tree are used
132  ! call a3_write_vtk(tree, trim(fname), output_cnt, time, &
133  ! ixs_fc=[1], dir="output")
134 
135  ! Find maximum and minimum values of cc(..., i_err) and cc(..., i_phi).
136  ! By default, only loop over leaves, and ghost cells are not used.
137  call a3_tree_max_cc(tree, i_err, p_err)
138  call a3_tree_min_cc(tree, i_err, n_err)
139  call a3_tree_sum_cc(tree, i_phi, sum_phi)
140  write(*,"(2(A,1x,Es12.4,2x))") &
141  " max error:", max(p_err, abs(n_err)), &
142  "sum phi: ", sum_phi
143  end if
144 
145  if (time > end_time) exit
146 
147  do n = 1, n_steps
148  ! Copy previous solution
149  call a3_tree_copy_cc(tree, i_phi, i_phi_old)
150 
151  ! Two forward Euler steps over dt
152  do i = 1, 2
153  ! Call procedure fluxes_koren for each id in tree, giving the list of boxes
154  call a3_loop_boxes(tree, fluxes_koren)
155 
156  ! Restrict fluxes from children to parents on refinement boundaries.
157  call a3_consistent_fluxes(tree, [i_phi])
158 
159  ! Call procedure update_solution (see below) for each box in tree, with argument dt
160  call a3_loop_box_arg(tree, update_solution, [dt])
161 
162  ! Restrict variables i_phi to all parent boxes
163  call a3_restrict_tree(tree, i_phi)
164  end do
165 
166  ! Take average of phi_old and phi
167  call a3_loop_box(tree, average_phi)
168  time = time + dt
169  end do
170 
171  ! Fill ghost cells for variable i_phi
172  call a3_gc_tree(tree, i_phi, a3_gc_interp_lim, a3_bc_neumann_zero)
173 
174  ! Adjust the refinement of a tree using refine_routine (see below) for grid
175  ! refinement. On input, the tree should be balanced. On output, the tree is
176  ! still balanced, and its refinement is updated (with at most one level per
177  ! call).
178  call a3_adjust_refinement(tree, refine_routine, refine_info, 2)
179 
180  ! Prolongation of i_phi values to new children (see below)
181  call prolong_to_new_children(tree, refine_info)
182  end do
183 
184  call system_clock(t_end,count_rate)
185  write(*, "(A,I0,A,Es10.3,A)") &
186  " Wall-clock time after ",time_steps, &
187  " time steps: ", (t_end-t_start) / real(count_rate, dp), &
188  " seconds"
189 
190  ! This call is not really necessary here, but cleaning up the data in a tree
191  ! is important if your program continues with other tasks.
192  call a3_destroy(tree)
193 
194 contains
195 
197  subroutine refine_routine(box, cell_flags)
198  type(box3_t), intent(in) :: box
199  integer, intent(out) :: cell_flags(box%n_cell, box%n_cell, box%n_cell)
200  real(dp) :: diff
201  integer :: i, j, k, nc
202 
203  nc = box%n_cell
204 
205  do k = 1, nc; do j = 1, nc; do i = 1, nc
206  diff = abs(box%cc(i+1, j, k, i_phi) + &
207  box%cc(i-1, j, k, i_phi) + &
208  box%cc(i, j+1, k, i_phi) + &
209  box%cc(i, j-1, k, i_phi) + &
210  box%cc(i, j, k+1, i_phi) + &
211  box%cc(i, j, k-1, i_phi) - &
212  6 * box%cc(i, j, k, i_phi)) * box%dr
213 
214  if (box%lvl < 2 .or. diff > 0.5e-3_dp .and. box%lvl < 5) then
215  cell_flags(i, j, k) = af_do_ref
216  else if (diff < 0.1_dp * 0.1e-3_dp) then
217  cell_flags(i, j, k) = af_rm_ref
218  else
219  cell_flags(i, j, k) = af_keep_ref
220  end if
221  end do; end do; end do
222  end subroutine refine_routine
223 
225  subroutine set_initial_condition(box)
226  type(box3_t), intent(inout) :: box
227  integer :: i, j, k, nc
228  real(dp) :: rr(3)
229 
230  nc = box%n_cell
231  do k = 0, nc+1; do j = 0, nc+1; do i = 0, nc+1
232  rr = a3_r_cc(box, [i, j, k])
233  box%cc(i, j, k, i_phi) = solution(rr, 0.0_dp)
234  end do; end do; end do
235  end subroutine set_initial_condition
236 
238  subroutine set_error(box, time)
239  type(box3_t), intent(inout) :: box
240  real(dp), intent(in) :: time(:)
241  integer :: i, j, k, nc
242  real(dp) :: rr(3)
243 
244  nc = box%n_cell
245  do k = 1, nc; do j = 1, nc; do i = 1, nc
246  rr = a3_r_cc(box, [i, j, k])
247  box%cc(i, j, k, i_err) = &
248  box%cc(i, j, k, i_phi) - solution(rr, time(1))
249  end do; end do; end do
250  end subroutine set_error
251 
253  function solution(rr, t) result(sol)
254  real(dp), intent(in) :: rr(3), t
255  real(dp) :: sol, rr_t(3)
256 
257  rr_t = rr - velocity * t
258 
259  select case (sol_type)
260  case (1)
261  sol = sin(rr_t(1))**4 * cos(rr_t(2))**4
262  case (2)
263  rr_t = modulo(rr_t, domain_len) / domain_len
264  if (norm2(rr_t - 0.5_dp) < 0.1_dp) then
265  sol = 1.0_dp
266  else
267  sol = 0.0_dp
268  end if
269  end select
270  end function solution
271 
274  subroutine fluxes_koren(boxes, id)
275  use m_flux_schemes
276  type(box3_t), intent(inout) :: boxes(:)
277  integer, intent(in) :: id
278  real(dp) :: gradp, gradc, gradn
279  real(dp) :: inv_dr
280  integer :: dim, dix(3), i, j, k, nc
281  real(dp), allocatable :: cc(:, :, :)
282  real(dp), allocatable :: v(:, :, :, :)
283 
284  nc = boxes(id)%n_cell
285  inv_dr = 1/boxes(id)%dr
286  allocate(cc(-1:nc+2, -1:nc+2, -1:nc+2))
287  allocate(v(1:nc+1, 1:nc+1, 1:nc+1, 3))
288 
289  call a3_gc_box(boxes, id, i_phi, a3_gc_interp_lim, a3_bc_neumann_zero)
290 
291  ! Get a second layer of ghost cell data (the 'normal' routines give just one
292  ! layer of ghost cells). Use a3_gc2_prolong_linear on refinement boundaries and
293  ! a3_bc2_neumann_zero on physical boundaries.
294  call a3_gc2_box(boxes, & ! List of all the boxes
295  id, & ! Id of box for which we set ghost cells
296  i_phi, & ! Variable for which ghost cells are set
297  a3_gc2_prolong_linear, & ! Procedure called at refinement boundaries
298  a3_bc2_neumann_zero, & ! Procedure called at physical boundaries
299  cc, & ! The enlarged box with ghost cells
300  nc) ! box%n_cell
301 
302  v(:, :, :, 1) = velocity(1)
303  v(:, :, :, 2) = velocity(2)
304  v(:, :, :, 3) = velocity(3)
305 
306  call flux_koren_3d(cc, v, nc, 2)
307  boxes(id)%fc(:, :, :, :, i_phi) = v
308 
309 ! ! Diffusive part with 2-nd order explicit method
310 ! boxes(id)%fc(i, j, k, dim, i_phi) = &
311 ! boxes(id)%fc(i, j, k, dim, i_phi) - &
312 ! diff_coeff * gradc * inv_dr
313 
314  end subroutine fluxes_koren
315 
317  subroutine update_solution(box, dt)
318  type(box3_t), intent(inout) :: box
319  real(dp), intent(in) :: dt(:)
320  real(dp) :: inv_dr
321  integer :: i, j, k, nc
322 
323  nc = box%n_cell
324  inv_dr = 1/box%dr
325 
326  forall (i = 1:nc, j = 1:nc, k = 1:nc)
327  box%cc(i, j, k, i_phi) = box%cc(i, j, k, i_phi) + dt(1) * inv_dr * ( &
328  box%fc(i, j, k, 1, i_phi) - box%fc(i+1, j, k, 1, i_phi) + &
329  box%fc(i, j, k, 2, i_phi) - box%fc(i, j+1, k, 2, i_phi) + &
330  box%fc(i, j, k, 3, i_phi) - box%fc(i, j, k+1, 3, i_phi))
331  end forall
332  end subroutine update_solution
333 
335  subroutine average_phi(box)
336  type(box3_t), intent(inout) :: box
337 
338  box%cc(:, :, :, i_phi) = 0.5_dp * (box%cc(:, :, :, i_phi) + &
339  box%cc(:, :, :, i_phi_old))
340  end subroutine average_phi
341 
342  ! Linear prolongation of i_phi values to new children
343  subroutine prolong_to_new_children(tree, refine_info)
344  type(a3_t), intent(inout) :: tree
345  type(ref_info_t), intent(in) :: refine_info
346  integer :: lvl, i, id, p_id
347 
348  do lvl = 1, tree%highest_lvl
349  !$omp parallel do private(id, p_id)
350  do i = 1, size(refine_info%lvls(lvl)%add)
351  id = refine_info%lvls(lvl)%add(i)
352  p_id = tree%boxes(id)%parent
353 
354  ! Linear prolongation will not strictly conserve phi
355  call a3_prolong_linear(tree%boxes(p_id), tree%boxes(id), i_phi)
356  end do
357  !$omp end parallel do
358 
359  ! Fill ghost cells for variables i_phi on the sides of a box, using
360  ! a3_gc_interp_lim on refinement boundaries and a3_bc_neumann_zero
361  ! on physical boundaries
362  call a3_gc_ids(tree%boxes, refine_info%lvls(lvl)%add, i_phi, &
363  a3_gc_interp_lim, a3_bc_neumann_zero)
364  end do
365  end subroutine prolong_to_new_children
366 
367 end program drift_diffusion_3d