Report Typos and Errors    
Semi-Lagrangian Library
Modular library for kinetic and gyrokinetic simulations of plasmas in fusion energy devices.
sll_m_qn_solver_2d_polar.F90
Go to the documentation of this file.
1 !**************************************************************
2 ! Copyright INRIA
3 ! Authors :
4 ! CALVI project team
5 !
6 ! This code SeLaLib (for Semi-Lagrangian-Library)
7 ! is a parallel library for simulating the plasma turbulence
8 ! in a tokamak.
9 !
10 ! This software is governed by the CeCILL-B license
11 ! under French law and abiding by the rules of distribution
12 ! of free software. You can use, modify and redistribute
13 ! the software under the terms of the CeCILL-B license as
14 ! circulated by CEA, CNRS and INRIA at the following URL
15 ! "http://www.cecill.info".
16 !**************************************************************
17 
207 
209 !+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
210 #include "sll_assert.h"
211 #include "sll_errors.h"
212 #include "sll_working_precision.h"
213 
214  use sll_m_constants, only: &
216 
218  sll_p_dirichlet, &
219  sll_p_neumann, &
220  sll_p_neumann_mode_0, &
221  sll_p_polar_origin
222 
223  use sll_m_fft, only: &
224  sll_t_fft, &
234 
235  use sll_m_tridiagonal, only: &
238 
239  use sll_m_utilities, only: &
241 
242  implicit none
243 
244  public :: &
249 
250  private
251 !+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
252 
255 
256  real(f64) :: rmin
257  real(f64) :: rmax
258  integer(i32) :: nr
259  integer(i32) :: nt
260  integer(i32) :: bc(2)
261  real(f64) :: epsilon_0
262  real(f64), allocatable :: g(:)
263 
264  type(sll_t_fft) :: fw
265  type(sll_t_fft) :: bw
266  complex(f64), allocatable :: z(:, :)
267  complex(f64), pointer :: temp_c(:)
268  real(f64), pointer :: temp_r(:)
269  real(f64), allocatable :: mat(:, :)
270  real(f64), allocatable :: cts(:)
271  integer(i32), allocatable :: ipiv(:)
272 
273  real(f64) :: bc_coeffs_rmin(2:3) ! needed for Neumann at r=r_min
274  real(f64) :: bc_coeffs_rmax(-2:-1) ! needed for Neumann at r=r_max
275  integer :: skip0 ! needed for full circle
276 
277  end type sll_t_qn_solver_2d_polar
278 
279 !+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
280 contains
281 !+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
282 
283  !=============================================================================
285  subroutine sll_s_qn_solver_2d_polar_init(solver, &
286  rmin, &
287  rmax, &
288  nr, &
289  ntheta, &
290  bc_rmin, &
291  bc_rmax, &
292  rho_m0, &
293  b_magn, &
294  lambda, &
295  use_zonal_flow, &
296  epsilon_0, &
297  rgrid)
298 
299  type(sll_t_qn_solver_2d_polar), intent(out) :: solver
300  real(f64), intent(in) :: rmin
301  real(f64), intent(in) :: rmax
302  integer(i32), intent(in) :: nr
303  integer(i32), intent(in) :: ntheta
304  integer(i32), intent(in) :: bc_rmin
305  integer(i32), intent(in) :: bc_rmax
306  real(f64), intent(in) :: rho_m0(:)
307  real(f64), intent(in) :: b_magn(:)
308  real(f64), optional, intent(in) :: lambda(:)
309  logical, optional, intent(in) :: use_zonal_flow
310  real(f64), optional, intent(in) :: epsilon_0
311  real(f64), target, optional, intent(in) :: rgrid(:)
312 
313  character(len=*), parameter :: this_sub_name = 'sll_s_qn_solver_2d_polar_init'
314 
315  real(f64) :: hp, hm
316  real(f64) :: inv_r
317  real(f64) :: d0_coeffs(3)
318  real(f64) :: d1_coeffs(3)
319  real(f64) :: d2_coeffs(3)
320  real(f64) :: ddr_ln_g
321  real(f64) :: c
322  real(f64), allocatable :: r_nodes(:)
323 
324  integer(i32) :: i, k
325  integer(i32) :: bck(2)
326  integer(i32) :: last
327 
328  ! Consistency checks
329  sll_assert_always(rmin >= 0.0_f64)
330  sll_assert_always(rmin < rmax)
331  sll_assert_always(nr >= 1)
332  sll_assert_always(ntheta >= 1)
333 
334  if (bc_rmin == sll_p_polar_origin .and. rmin /= 0.0_f64) then
335  sll_error(this_sub_name, "BC option 'sll_p_polar_origin' requires r_min = 0")
336  end if
337 
338  ! Set boundary condition at r_min
339  select case (bc_rmin)
340  case (sll_p_dirichlet, sll_p_neumann, sll_p_neumann_mode_0, sll_p_polar_origin)
341  solver%bc(1) = bc_rmin
342  case default
343  sll_error(this_sub_name, 'Unrecognized boundary condition at r_min')
344  end select
345 
346  ! Set boundary condition at r_max
347  select case (bc_rmax)
348  case (sll_p_dirichlet, sll_p_neumann, sll_p_neumann_mode_0)
349  solver%bc(2) = bc_rmax
350  case default
351  sll_error(this_sub_name, 'Unrecognized boundary condition at r_max')
352  end select
353 
354  ! Override vacuum permittivity in SI units
355  if (present(epsilon_0)) then
356  solver%epsilon_0 = epsilon_0
357  else
358  solver%epsilon_0 = sll_p_epsilon_0
359  end if
360 
361  ! Store information in solver
362  solver%rmin = rmin
363  solver%rmax = rmax
364  solver%nr = nr
365  solver%nt = ntheta
366 
367  ! Important: if full circle is simulated, center point is not solved for!
368  ! Therefore, solution is calculated on nr+1-skip0 points.
369  solver%skip0 = merge(1, 0, bc_rmin == sll_p_polar_origin)
370 
371  ! r grid (possibly non-uniform)
372  allocate (r_nodes(nr + 1))
373 
374  if (present(rgrid)) then !--> Create computational grid from user data
375 
376  sll_assert_always(all(rgrid > 0.0_f64))
377  if (bc_rmin == sll_p_polar_origin) then
378  sll_assert_always(size(rgrid) == nr)
379  sll_assert_always(rgrid(nr) == rmax)
380  r_nodes(1) = -rgrid(1)
381  r_nodes(2:) = rgrid(:)
382  else
383  sll_assert_always(size(rgrid) == nr + 1)
384  sll_assert_always(rgrid(1) == rmin)
385  sll_assert_always(rgrid(nr + 1) == rmax)
386  r_nodes(:) = rgrid(:)
387  end if
388 
389  else !-------------------------> Create uniform grid
390 
391  if (bc_rmin == sll_p_polar_origin) then
392  associate(rmin => rmax/real(2*nr + 1, f64))
393  r_nodes(1) = -rmin
394  call sll_s_new_array_linspace(r_nodes(2:), rmin, rmax, endpoint=.true.)
395  end associate
396  else
397  call sll_s_new_array_linspace(r_nodes, rmin, rmax, endpoint=.true.)
398  end if
399 
400  end if
401 
402  ! Allocate arrays for solution of linear systems along r
403  associate(sh => solver%skip0)
404  allocate (solver%g(nr + 1))
405  allocate (solver%z(nr + 1 - sh, 0:ntheta/2))
406  allocate (solver%mat((nr - 1)*3, 0:ntheta/2)) ! for each k, matrix depends on r
407  allocate (solver%cts((nr - 1)*7))
408  allocate (solver%ipiv(nr - 1))
409  end associate
410 
411  ! Allocate in ALIGNED fashion 1D work arrays for FFT
412  solver%temp_r => sll_f_fft_allocate_aligned_real(ntheta)
413  solver%temp_c => sll_f_fft_allocate_aligned_complex(ntheta/2 + 1)
414 
415  ! Initialize plans for forward and backward FFTs
416  call sll_s_fft_init_r2c_1d(solver%fw, &
417  ntheta, &
418  solver%temp_r(:), &
419  solver%temp_c(:), &
420  aligned=.true., &
421  normalized=.true.)
422 
423  call sll_s_fft_init_c2r_1d(solver%bw, &
424  ntheta, &
425  solver%temp_c(:), &
426  solver%temp_r(:), &
427  aligned=.true., &
428  normalized=.false.)
429 
430  ! Store non-dimensional coefficient g(r) = \rho(r) / (B(r)^2 \epsilon_0)
431  associate(sh => solver%skip0)
432  solver%g(1 + sh:) = rho_m0(:)/(b_magn(:)**2*solver%epsilon_0)
433  if (sh == 1) then
434  solver%g(1) = solver%g(2) ! ghost point
435  end if
436  end associate
437 
438  ! Store matrix coefficients into solver%mat
439  ! Cycle over k
440  do k = 0, ntheta/2
441 
442  !--------------------------------------------
443  ! Compute boundary conditions type for mode k
444  !--------------------------------------------
445  bck(:) = solver%bc(:)
446  do i = 1, 2
447  if (bck(i) == sll_p_neumann_mode_0) then
448  if (k == 0) then
449  bck(i) = sll_p_neumann
450  else
451  bck(i) = sll_p_dirichlet
452  end if
453  end if
454  end do
455 
456  !--------------------------------------------
457  ! Compute matrix coefficients for a given k_j
458  !--------------------------------------------
459  do i = 2, nr
460 
461  ! Finite difference coefficients for 0th (trivial), 1st and 2nd derivatives
462  hp = r_nodes(i + 1) - r_nodes(i)
463  hm = r_nodes(i) - r_nodes(i - 1)
464  inv_r = 1.0_f64/r_nodes(i)
465  d0_coeffs(:) = [0.0_f64, 1.0_f64, 0.0_f64]
466  d1_coeffs(:) = [-hp/hm, (hp**2 - hm**2)/(hp*hm), hm/hp]/(hp + hm)
467  d2_coeffs(:) = [2*hp/(hp + hm), -2.0_f64, 2*hm/(hp + hm)]/(hp*hm)
468 
469  ! Compute d/dr(ln(g)) as (1/g)*(dg/dr) using finite differences
470  ddr_ln_g = dot_product(d1_coeffs(:), solver%g(i - 1:i + 1))/solver%g(i)
471 
472  ! Determine contribution from zonal flow
473  if (present(lambda)) then
474  associate(sh => solver%skip0)
475  c = 1.0_f64/(lambda(i - sh)**2*solver%g(i))
476  end associate
477  if (present(use_zonal_flow)) then
478  if (use_zonal_flow .and. k == 0) then
479  c = 0.0_f64
480  end if
481  end if
482  else
483  c = 0.0_f64
484  end if
485 
486  ! Fill in elements of i-th matrix row
487  solver%mat(3*(i - 1) - 2:3*(i - 1), k) = &
488  -d2_coeffs(:) &
489  - d1_coeffs(:)*(inv_r + ddr_ln_g) &
490  + d0_coeffs(:)*((k*inv_r)**2 + c)
491 
492  end do
493 
494  !--------------------------------------------
495  ! Set boundary condition at rmin
496  !--------------------------------------------
497  if (bck(1) == sll_p_dirichlet) then ! Dirichlet
498  solver%mat(1, k) = 0.0_f64
499 
500  else if (bck(1) == sll_p_neumann) then ! Neumann
501 
502  ! Coefficients of homogeneous boundary condition
503  hp = r_nodes(3) - r_nodes(2)
504  hm = r_nodes(2) - r_nodes(1)
505  d1_coeffs(:) = [-2 - hp/hm, 2 + hp/hm + hm/hp, -hm/hp]
506  solver%bc_coeffs_rmin(2:3) = -d1_coeffs(2:3)/d1_coeffs(1)
507 
508  ! Gaussian elimination: remove phi(1) variable using boundary condition
509  solver%mat(3, k) = solver%mat(3, k) + solver%bc_coeffs_rmin(3)*solver%mat(1, k)
510  solver%mat(2, k) = solver%mat(2, k) + solver%bc_coeffs_rmin(2)*solver%mat(1, k)
511  solver%mat(1, k) = 0.0_f64
512 
513  else if (bck(1) == sll_p_polar_origin) then ! center of circular domain
514 
515  ! Gaussian elimination: phi(1) = (-1)^k phi(2)
516  solver%mat(2, k) = solver%mat(2, k) + (-1)**k*solver%mat(1, k)
517  solver%mat(1, k) = 0.0_f64
518 
519  end if
520 
521  !--------------------------------------------
522  ! Set boundary condition at rmax
523  !--------------------------------------------
524  last = 3*(nr - 1)
525  if (bck(2) == sll_p_dirichlet) then ! Dirichlet
526  solver%mat(last, k) = 0.0_f64
527 
528  else if (bck(2) == sll_p_neumann) then ! Neumann
529 
530  ! Coefficients of homogeneous boundary condition
531  hp = r_nodes(nr + 1) - r_nodes(nr)
532  hm = r_nodes(nr) - r_nodes(nr - 1)
533  d1_coeffs(:) = [hp/hm, -2 - hp/hm - hm/hp, 2 + hm/hp]
534  solver%bc_coeffs_rmax(-2:-1) = -d1_coeffs(1:2)/d1_coeffs(3)
535 
536  ! Gaussian elimination: remove phi(last) variable using boundary condition
537  solver%mat(last - 2, k) = solver%mat(last - 2, k) + solver%bc_coeffs_rmax(-2)*solver%mat(last, k)
538  solver%mat(last - 1, k) = solver%mat(last - 1, k) + solver%bc_coeffs_rmax(-1)*solver%mat(last, k)
539  solver%mat(last, k) = 0.0_f64
540 
541  end if
542 
543  end do
544 
545  end subroutine sll_s_qn_solver_2d_polar_init
546 
547  !=============================================================================
549  subroutine sll_s_qn_solver_2d_polar_solve(solver, rho, phi)
550  type(sll_t_qn_solver_2d_polar), intent(inout) :: solver
551  real(f64), intent(in) :: rho(:, :)
552  real(f64), intent(out) :: phi(:, :)
553 
554  integer(i32) :: nr, ntheta, bck(2)
555  integer(i32) :: i, k
556  integer(i32) :: nrpts
557  integer(i32) :: sh
558 
559  nr = solver%nr
560  ntheta = solver%nt
561 
562  ! Shift in radial grid indexing
563  sh = solver%skip0 ! =1 if bc_rmin==sll_p_polar_origin, =0 otherwise
564 
565  ! Number of points in radial grid
566  nrpts = nr + 1 - sh
567 
568  ! Consistency check: 'rho' and 'phi' have shape defined at initialization
569  sll_assert_always(all(shape(rho) == [nrpts, ntheta]))
570  sll_assert_always(all(shape(phi) == [nrpts, ntheta]))
571 
572  ! For each r_i, compute FFT of rho(r_i,theta) to obtain \hat{rho}(r_i,k)
573  do i = 1, nrpts
574  solver%temp_r(:) = rho(i, :)
575  call sll_s_fft_exec_r2c_1d(solver%fw, solver%temp_r(:), solver%temp_c(:))
576  solver%z(i, :) = solver%temp_c(:)
577  end do
578 
579  ! Cycle over k
580  do k = 0, ntheta/2
581 
582  ! rhok(r) is k-th Fourier mode of rho(r,theta)
583  ! phik(r) is k-th Fourier mode of phi(r,theta)
584  ! rhok is 1D contiguous slice (column) of solver%z
585  ! we will overwrite rhok with phik
586  associate(rhok => solver%z(:, k), phik => solver%z(:, k))
587 
588  rhok(:) = rhok(:)/(solver%g(1 + sh:)*solver%epsilon_0)
589 
590  ! Solve tridiagonal system to obtain \hat{phi}_{k_j}(r) at internal points
591  call sll_s_setup_cyclic_tridiag(solver%mat(:, k), nr - 1, solver%cts, solver%ipiv)
592  call sll_o_solve_cyclic_tridiag(solver%cts, solver%ipiv, &
593  rhok(2 - sh:nrpts - 1), nr - 1, phik(2 - sh:nrpts - 1))
594 
595  ! Compute boundary conditions type for mode k
596  bck(:) = solver%bc(:)
597  do i = 1, 2
598  if (bck(i) == sll_p_neumann_mode_0) then
599  if (k == 0) then
600  bck(i) = sll_p_neumann
601  else
602  bck(i) = sll_p_dirichlet
603  end if
604  end if
605  end do
606 
607  ! Boundary condition at rmin
608  if (bck(1) == sll_p_dirichlet) then ! Dirichlet
609  phik(1) = (0.0_f64, 0.0_f64)
610  else if (bck(1) == sll_p_neumann) then ! Neumann
611  associate(c => solver%bc_coeffs_rmin)
612  phik(1) = c(2)*phik(2) + c(3)*phik(3)
613  end associate
614  end if
615 
616  ! Boundary condition at rmax
617  if (bck(2) == sll_p_dirichlet) then ! Dirichlet
618  phik(nrpts) = (0.0_f64, 0.0_f64)
619  else if (bck(2) == sll_p_neumann) then ! Neumann
620  associate(c => solver%bc_coeffs_rmax)
621  phik(nrpts) = c(-2)*phik(nrpts - 2) + c(-1)*phik(nrpts - 1)
622  end associate
623  end if
624 
625  end associate
626 
627  end do
628 
629  ! For each r_i, compute inverse FFT of \hat{phi}(r_i,k) to obtain phi(r_i,theta)
630  do i = 1, nrpts
631  solver%temp_c(:) = solver%z(i, :)
632  call sll_s_fft_exec_c2r_1d(solver%bw, solver%temp_c(:), solver%temp_r(:))
633  phi(i, :) = solver%temp_r(:)
634  end do
635 
636  end subroutine sll_s_qn_solver_2d_polar_solve
637 
638  !=============================================================================
640  subroutine sll_s_qn_solver_2d_polar_free(solver)
641  type(sll_t_qn_solver_2d_polar), intent(inout) :: solver
642 
643  call sll_s_fft_free(solver%fw)
644  call sll_s_fft_free(solver%bw)
645 
646  call sll_s_fft_deallocate_aligned_real(solver%temp_r)
647  call sll_s_fft_deallocate_aligned_complex(solver%temp_c)
648 
649  deallocate (solver%z)
650  deallocate (solver%mat)
651  deallocate (solver%cts)
652  deallocate (solver%ipiv)
653 
654  end subroutine sll_s_qn_solver_2d_polar_free
655 
656 end module sll_m_qn_solver_2d_polar
Solve tridiagonal system (double or complex)
Fortran module where set some physical and mathematical constants.
real(kind=f64), parameter, public sll_p_epsilon_0
FFT interface for FFTW.
subroutine, public sll_s_fft_exec_r2c_1d(plan, array_in, array_out)
Compute fast Fourier transform in real to complex mode.
subroutine, public sll_s_fft_free(plan)
Delete a plan.
subroutine, public sll_s_fft_init_r2c_1d(plan, nx, array_in, array_out, normalized, aligned, optimization)
Create new 1d real to complex plan for forward FFT.
subroutine, public sll_s_fft_deallocate_aligned_complex(data)
Function to deallocate an aligned complex array.
subroutine, public sll_s_fft_exec_c2r_1d(plan, array_in, array_out)
Compute fast Fourier transform in complex to real mode.
subroutine, public sll_s_fft_init_c2r_1d(plan, nx, array_in, array_out, normalized, aligned, optimization)
Create new 1d complex to real plan for backward FFT.
real(c_double) function, dimension(:), pointer, public sll_f_fft_allocate_aligned_real(n)
Function to allocate an aligned real array.
subroutine, public sll_s_fft_deallocate_aligned_real(data)
Function to deallocate an aligned real array.
complex(c_double_complex) function, dimension(:), pointer, public sll_f_fft_allocate_aligned_complex(n)
Function to allocate an aligned complex array.
Serial quasi-neutrality solver on 2D polar mesh; uses FFT in theta and 2nd-order FD in r.
subroutine, public sll_s_qn_solver_2d_polar_solve(solver, rho, phi)
Solve the quasi-neutrality equation and get the electrostatic potential.
subroutine, public sll_s_qn_solver_2d_polar_free(solver)
Delete contents (local storage) of quasi-neutrality solver.
subroutine, public sll_s_qn_solver_2d_polar_init(solver, rmin, rmax, nr, ntheta, bc_rmin, bc_rmax, rho_m0, b_magn, lambda, use_zonal_flow, epsilon_0, rgrid)
Initialize the solver.
Tridiagonal system solver.
subroutine, public sll_s_setup_cyclic_tridiag(a, n, cts, ipiv)
Give the factorization of the matrix in argument.
Some common numerical utilities.
pure subroutine, public sll_s_new_array_linspace(array, vmin, vmax, endpoint, step)
Equivalent to numpy.linspace @contact Yaman Güçlü, IPP Garching.
Type for FFT plan in SeLaLib.
Class for 2D Poisson solver in polar coordinates.
    Report Typos and Errors