dist_fn.f90 Source File


Source Code

module dist_fn

  use debug_flags, only: debug => dist_fn_debug
  
   implicit none

   public :: init_gxyz
   public :: init_dist_fn, finish_dist_fn
   public :: checksum

   private

   interface checksum
      module procedure checksum_field
      module procedure checksum_dist
   end interface

   logical :: dist_fn_initialized = .false.
   logical :: gxyz_initialized = .false.
   logical :: kp2init = .false.
   logical :: dkp2drinit = .false.
   logical :: vp2init = .false.

contains

   subroutine init_gxyz(restarted)

      use arrays_dist_fn, only: gvmu, gold, gnew
      use redistribute, only: gather, scatter
      use dist_redistribute, only: kxkyz2vmu
      use parameters_physics, only: radial_variation
      use stella_layouts, only: vmu_lo, iv_idx, imu_idx, is_idx
      use stella_transforms, only: transform_kx2x_xfirst, transform_x2kx_xfirst
      use parameters_kxky_grids, only: nalpha, nakx, naky
      use calculations_kxky, only: multiply_by_rho
      use vpamu_grids, only: mu, vpa, vperp2
      use zgrid, only: nzgrid, ntubes
      use species, only: spec, pfac
      use geometry, only: dBdrho, gfac

      implicit none

      real :: corr
      integer :: ivmu, is, imu, iv, it, iz, ia
      real, dimension(:, :), allocatable :: energy
      complex, dimension(:, :), allocatable :: g0k
      logical, intent(in) :: restarted

      if (gxyz_initialized) return
      gxyz_initialized = .false.

      ! get version of g that has ky,kx,z local
      call gather(kxkyz2vmu, gvmu, gnew)

      ia = 1

      !calculate radial corrections to F0 for use in Krook operator, as well as g1 from initialization
      if (radial_variation) then
         !init_g uses maxwellians, so account for variation in temperature, density, and B

         allocate (energy(nalpha, -nzgrid:nzgrid))
         allocate (g0k(naky, nakx))

         do ivmu = vmu_lo%llim_proc, vmu_lo%ulim_proc
            is = is_idx(vmu_lo, ivmu)
            imu = imu_idx(vmu_lo, ivmu)
            iv = iv_idx(vmu_lo, ivmu)
            energy = (vpa(iv)**2 + vperp2(:, :, imu)) * (spec(is)%temp_psi0 / spec(is)%temp)
            do it = 1, ntubes
               do iz = -nzgrid, nzgrid

                  corr = -(pfac * (spec(is)%fprim + spec(is)%tprim * (energy(ia, iz) - 1.5)) &
                           + 2 * gfac * mu(imu) * dBdrho(iz))

                  if (.not. restarted) then
                     g0k = corr * gnew(:, :, iz, it, ivmu)
                     call multiply_by_rho(g0k)
                     gnew(:, :, iz, it, ivmu) = gnew(:, :, iz, it, ivmu) + g0k
                  end if
               end do
            end do
         end do
         deallocate (energy, g0k)

         if (.not. restarted) call scatter(kxkyz2vmu, gnew, gvmu)
      end if

      gold = gnew

   end subroutine init_gxyz

   subroutine init_dist_fn

      use mp, only: proc0
      use parameters_physics, only: radial_variation
      use stella_layouts, only: init_dist_fn_layouts
      use gyro_averages, only: init_bessel

      implicit none

      if (dist_fn_initialized) return
      dist_fn_initialized = .true.

      debug = debug .and. proc0

      if (debug) write (*, *) 'dist_fn::init_dist_fn::allocate_arrays'
      call allocate_arrays

      !> allocate and initialise kperp2 and dkperp2dr
      if (debug) write (*, *) 'dist_fn::init_dist_fn::init_kperp2'
      call init_kperp2
      if (radial_variation) call init_dkperp2dr

      !> allocate and initialise vperp2
      if (debug) write (*, *) 'dist_fn::init_dist_fn::init_vperp2'
      call init_vperp2

      !> init_bessel sets up arrays needed for gyro-averaging;
      !> for a flux tube simulation, this is j0 and j1;
      !> for a flux annulus simulation, gyro-averaging is non-local in ky
      !> and so more effort is required
      if (debug) write (*, *) 'dist_fn::init_dist_fn::init_bessel'
      call init_bessel

   end subroutine init_dist_fn

   !> init_kperp2 allocates and initialises the kperp2 array
   subroutine init_kperp2

      use arrays_dist_fn, only: kperp2
      use geometry, only: gds2, gds21, gds22
      use geometry, only: geo_surf, q_as_x
      use zgrid, only: nzgrid
      use parameters_kxky_grids, only: naky, nakx, nalpha
      use grids_kxky, only: akx, aky, theta0
      use grids_kxky, only: zonal_mode

      implicit none

      integer :: iky, ikx

      if (kp2init) return
      kp2init = .true.

      !> allocate the kperp2 array to contain |k_perp|^2
      allocate (kperp2(naky, nakx, nalpha, -nzgrid:nzgrid))

      do iky = 1, naky
         if (zonal_mode(iky)) then
            do ikx = 1, nakx
               if (q_as_x) then
                  kperp2(iky, ikx, :, :) = akx(ikx) * akx(ikx) * gds22
               else
                  kperp2(iky, ikx, :, :) = akx(ikx) * akx(ikx) * gds22 / (geo_surf%shat**2)
               end if
            end do
         else
            do ikx = 1, nakx
               kperp2(iky, ikx, :, :) = aky(iky) * aky(iky) &
                                        * (gds2 + 2.0 * theta0(iky, ikx) * gds21 &
                                           + theta0(iky, ikx) * theta0(iky, ikx) * gds22)
            end do
         end if
      end do

      ! NB: should really avoid this by using higher resolution when reading in VMEC geometry and then
      ! NB: course-graining if necessary to map onto lower-resolution stella grid
      ! ensure kperp2 is positive everywhere (only might go negative if using full-flux-surface due to interpolation)
      where (kperp2 < 0.0)
         kperp2 = 0.0
      end where

      call enforce_single_valued_kperp2

   end subroutine init_kperp2

   !> init_dkperp2dr allocates and initialises the dkperp2dr array, needed for radial variation
   subroutine init_dkperp2dr

      use arrays_dist_fn, only: kperp2, dkperp2dr
      use geometry, only: dgds2dr, dgds21dr, dgds22dr
      use geometry, only: geo_surf, q_as_x
      use zgrid, only: nzgrid
      use parameters_kxky_grids, only: naky, nakx, nalpha
      use grids_kxky, only: akx, aky, theta0
      use grids_kxky, only: zonal_mode

      implicit none

      integer :: iky, ikx

      if (dkp2drinit) return
      dkp2drinit = .true.

      allocate (dkperp2dr(naky, nakx, nalpha, -nzgrid:nzgrid))
      do iky = 1, naky
         if (zonal_mode(iky)) then
            do ikx = 1, nakx
               if (q_as_x) then
                  where (kperp2(iky, ikx, :, :) > epsilon(0.0))
                     dkperp2dr(iky, ikx, :, :) = akx(ikx) * akx(ikx) * dgds22dr / kperp2(iky, ikx, :, :)
                  elsewhere
                     dkperp2dr(iky, ikx, :, :) = 0.0
                  end where
               else
                  where (kperp2(iky, ikx, :, :) > epsilon(0.0))
                     dkperp2dr(iky, ikx, :, :) = akx(ikx) * akx(ikx) * dgds22dr / (geo_surf%shat**2 * kperp2(iky, ikx, :, :))
                  elsewhere
                     dkperp2dr(iky, ikx, :, :) = 0.0
                  end where
               end if
            end do
         else
            do ikx = 1, nakx
               dkperp2dr(iky, ikx, :, :) = aky(iky) * aky(iky) &
                                           * (dgds2dr + 2.0 * theta0(iky, ikx) * dgds21dr &
                                              + theta0(iky, ikx) * theta0(iky, ikx) * dgds22dr)
               dkperp2dr(iky, ikx, :, :) = dkperp2dr(iky, ikx, :, :) / kperp2(iky, ikx, :, :)
               if (any(kperp2(iky, ikx, :, :) < epsilon(0.))) dkperp2dr(iky, ikx, :, :) = 0.
            end do
         end if
      end do

   end subroutine init_dkperp2dr

   subroutine enforce_single_valued_kperp2

      use arrays_dist_fn, only: kperp2
      use parameters_kxky_grids, only: naky, nalpha
      use zgrid, only: nzgrid
      use extended_zgrid, only: neigen, nsegments, ikxmod

      implicit none

      integer :: iky, ie, iseg
      real, dimension(:), allocatable :: tmp

      allocate (tmp(nalpha)); tmp = 0.0

      do iky = 1, naky
         do ie = 1, neigen(iky)
            if (nsegments(ie, iky) > 1) then
               do iseg = 2, nsegments(ie, iky)
                  tmp = 0.5 * (kperp2(iky, ikxmod(iseg - 1, ie, iky), :, nzgrid) + kperp2(iky, ikxmod(iseg, ie, iky), :, -nzgrid))
                  kperp2(iky, ikxmod(iseg, ie, iky), :, -nzgrid) = tmp
                  kperp2(iky, ikxmod(iseg - 1, ie, iky), :, nzgrid) = tmp
               end do
            end if
         end do
      end do

      deallocate (tmp)

   end subroutine enforce_single_valued_kperp2

   subroutine allocate_arrays

      use stella_layouts, only: kxkyz_lo, vmu_lo
      use zgrid, only: nzgrid, ntubes
      use parameters_kxky_grids, only: naky, nakx
      use vpamu_grids, only: nvpa, nmu
      use arrays_dist_fn, only: gnew, gold, g_scratch
      use arrays_dist_fn, only: gvmu

      implicit none

      if (.not. allocated(gnew)) &
         allocate (gnew(naky, nakx, -nzgrid:nzgrid, ntubes, vmu_lo%llim_proc:vmu_lo%ulim_alloc))
      gnew = 0.
      if (.not. allocated(gold)) &
         allocate (gold(naky, nakx, -nzgrid:nzgrid, ntubes, vmu_lo%llim_proc:vmu_lo%ulim_alloc))
      gold = 0.
      if (.not. allocated(g_scratch)) &
         allocate (g_scratch(naky, nakx, -nzgrid:nzgrid, ntubes, vmu_lo%llim_proc:vmu_lo%ulim_alloc))
      g_scratch = 0.
      if (.not. allocated(gvmu)) &
         allocate (gvmu(nvpa, nmu, kxkyz_lo%llim_proc:kxkyz_lo%ulim_alloc))
      gvmu = 0.

   end subroutine allocate_arrays

   subroutine init_vperp2

      use geometry, only: bmag
      use zgrid, only: nzgrid
      use vpamu_grids, only: vperp2
      use vpamu_grids, only: nmu, mu
      use parameters_kxky_grids, only: nalpha

      implicit none

      integer :: imu

      if (vp2init) return
      vp2init = .true.

      if (.not. allocated(vperp2)) allocate (vperp2(nalpha, -nzgrid:nzgrid, nmu)); vperp2 = 0.

      do imu = 1, nmu
         vperp2(:, :, imu) = 2.0 * mu(imu) * bmag
      end do

   end subroutine init_vperp2

   subroutine finish_dist_fn

      use gyro_averages, only: finish_bessel

      implicit none

      call finish_bessel
      call finish_kperp2
      call finish_vperp2
      call deallocate_arrays

      dist_fn_initialized = .false.
      gxyz_initialized = .false.

   end subroutine finish_dist_fn

   subroutine deallocate_arrays

      use arrays_dist_fn, only: gnew, gold, g_scratch, gvmu

      implicit none

      if (allocated(gnew)) deallocate (gnew)
      if (allocated(gold)) deallocate (gold)
      if (allocated(g_scratch)) deallocate (g_scratch)
      if (allocated(gvmu)) deallocate (gvmu)

   end subroutine deallocate_arrays

   subroutine finish_kperp2

      use arrays_dist_fn, only: kperp2, dkperp2dr

      implicit none

      if (allocated(kperp2)) deallocate (kperp2)
      if (allocated(dkperp2dr)) deallocate (dkperp2dr)

      kp2init = .false.
      dkp2drinit = .false.

   end subroutine finish_kperp2

   subroutine finish_vperp2

      use vpamu_grids, only: vperp2

      implicit none

      if (allocated(vperp2)) deallocate (vperp2)

      vp2init = .false.

   end subroutine finish_vperp2

   subroutine checksum_field(field, total)

      use zgrid, only: nzgrid, ntubes
      use parameters_kxky_grids, only: naky
      use extended_zgrid, only: neigen, nsegments, ikxmod
      use extended_zgrid, only: iz_low, iz_up

      implicit none

      complex, dimension(:, :, -nzgrid:, :), intent(in) :: field
      real, intent(out) :: total

      integer :: it, iky, ie, iseg
      integer :: ikx

      total = 0.

      do iky = 1, naky
         do it = 1, ntubes
            do ie = 1, neigen(iky)
               iseg = 1
               ikx = ikxmod(iseg, ie, iky)
               total = total + sum(cabs(field(iky, ikx, iz_low(iseg):iz_up(iseg), it)))
               if (nsegments(ie, iky) > 1) then
                  do iseg = 2, nsegments(ie, iky)
                     ikx = ikxmod(iseg, ie, iky)
                     total = total + sum(cabs(field(iky, ikx, iz_low(iseg) + 1:iz_up(iseg), it)))
                  end do
               end if
            end do
         end do
      end do

   end subroutine checksum_field

   subroutine checksum_dist(dist, total, norm)

      use mp, only: sum_allreduce
      use zgrid, only: nzgrid, ntubes
      use stella_layouts, only: vmu_lo, iv_idx, imu_idx, is_idx
      use parameters_kxky_grids, only: naky, nakx
      use vpamu_grids, only: maxwell_vpa, maxwell_mu

      implicit none

      complex, dimension(:, :, -nzgrid:, :, vmu_lo%llim_proc:), intent(in) :: dist
      real, intent(out) :: total
      logical, intent(in), optional :: norm

      integer :: ivmu, iv, imu, is
      integer :: iky, ikx, it
      real :: subtotal

      complex, dimension(:, :, :, :), allocatable :: dist_single

      total = 0.

      allocate (dist_single(naky, nakx, -nzgrid:nzgrid, ntubes))
      do ivmu = vmu_lo%llim_proc, vmu_lo%ulim_proc
         dist_single = dist(:, :, :, :, ivmu)
         if (present(norm)) then
            if (norm) then
               iv = iv_idx(vmu_lo, ivmu)
               imu = imu_idx(vmu_lo, ivmu)
               is = is_idx(vmu_lo, ivmu)
               do it = 1, ntubes
                  do ikx = 1, nakx
                     do iky = 1, naky
                        dist_single(iky, ikx, :, it) = dist_single(iky, ikx, :, it) * maxwell_vpa(iv, is) * maxwell_mu(1, :, imu, is)
                     end do
                  end do
               end do
            else
            end if
         end if
         call checksum(dist_single, subtotal)
         total = total + subtotal
      end do
      deallocate (dist_single)

      call sum_allreduce(total)

   end subroutine checksum_dist

end module dist_fn