setup_trapped_lambda_grids_new_trap_int Subroutine

private subroutine setup_trapped_lambda_grids_new_trap_int(lambda_grid, weights)

Determine trapped pitch angle weights using "new" polynomial interpolation.

The new method uses Lagrange polynomial interpolation to provide an accurate approximation of the integration over the trapped region. The old method effectively uses the trapezium rule to integrate. In both cases the integration variable is the parallel velocity spanning the range [-v_||wfb, v||_wfb]. Both methods give zero weight to the wfb at the locations where B == Bmax. Generally the old method appears to be accurate to at most single precision, whilst the new method is more accurate, reaching the double precision round off level even with just a few trapped pitch angles. It can be noted that the the old method can reach the same level of accuracy at some theta grid points but not all. Further work may be able to optimise the grid to avoid these locations. There are a number of reasons why we may not want to make new_trap_int default to true, including : 1. It is not compatible with radau_gauss_grid = T (which is the default). 2. One might expect high order Lagrange interpolation to be unstable so may anticipate that the new method will not work well at higher ntheta. Empirically it seems this does not become an issue here. Further, the input nmax can be used to limit the maximum Lagrange order used by splitting the domain. 3. For large ntheta the new method leads to slow initialisation. This is due to the calculation of the error coefficients for use with get_verr. This could perhaps be optmised. Both points 2 and 3 could be avoided to some extent if the trapped pitch angle grid resolution is decoupled from the theta grid resolution. It may be possible to make the new approach compatible with radau_gauss_grid or we may decide radau_gauss_grid should default to false.

Arguments

Type IntentOptional Attributes Name
real, intent(in), dimension(:) :: lambda_grid

The lambda grid points.

real, intent(inout), dimension(-ntgrid:, :) :: weights

The integration weights for the lambda grid. Note this is intent in out as we only set a portion of the grid so may want to keep the rest of the input values.


Contents


Source Code

  subroutine setup_trapped_lambda_grids_new_trap_int(lambda_grid, weights)
    use theta_grid, only: ntgrid, bmag, bmax
    use file_utils, only: open_output_file, close_output_file, error_unit
    use mp, only: proc0
    implicit none
    !> The lambda grid points.
    real, dimension(:), intent(in) :: lambda_grid
    !> The integration weights for the lambda grid. Note this is
    !> intent `in out` as we only set a portion of the grid so may
    !> want to keep the rest of the input values.
    real, dimension(-ntgrid:, :), intent(in out) :: weights
    real, dimension (:), allocatable :: ytmp, yb, wb
    real, dimension (:,:), allocatable :: wberr
    integer :: npts, ntrap
    real :: llim, ulim, wgt_tmp
    logical :: eflag
    integer :: ig, il, ndiv, divmax

    ! Initialise the error flag
    eflag = .false.

    ! max number of trapped particles (occurs at outboard midplane)
    ntrap = nlambda - ng2

    ! wlterr contains weights for less accurate trapped integrals (for error estimation)
    allocate(wlterr(-ntgrid:ntgrid,nlambda,ntrap))

    wlterr = 0.0

    ! Find the integration weights for each theta grid point
    do ig = -ntgrid, ntgrid

       ! First we count how many trapped lambda bounce points we
       ! have to consider at this point. In other words how many
       ! bounce points are not forbidden for the current theta
       ! grid point.
       ! npts is the number of lambda_grid values in the trapped integral (varies with theta)
       npts = 0

       do il = ng2+1, nlambda
          if (1.0 - lambda_grid(il)*bmag(ig) > -bouncefuzz) then
             npts = npts + 1
          end if
       end do

       ! If there are any valid bounce points then we need to
       ! calculate the weights. If npts == 1 then we probably also expect
       ! the integration weights to be 0 so we could probably skip all of
       ! this work for npts = 1 as well.
       if (npts > 0) then

          ! ytmp is an array containing pitch angle grid points (for vpa >= 0)
          ! These are the v||/v points for each lambda between wfb (sets maximum
          ! v||/v here) and the lambda which bounces at this point (v||/v = 0)
          ! yb is an array containing the full set of [-v||/v, v||/v] grid points.
          ! Constructed directly from ytmp by copying and flipping sign+order
          ! wb is an array containing the integration weights calculated for the
          ! given v||/v grid.
          allocate(ytmp(npts), yb(2*npts-1), wb(2*npts-1))
          ! wberr is an array used to hold the weights used in the error estimation
          ! in [[trap_error]] called by [[get_verr]]
          allocate(wberr(2*npts-1,npts))

          ytmp = 0.0; yb = 0.0; wb = 0.0; wberr = 0.0

          ! loop computes transformed variable of integration (v||/v)
          do il = ng2+1, ng2+npts
             ytmp(il-ng2) = sqrt(max(1 - lambda_grid(il)*bmag(ig), 0.0))
          end do

          ! define array (yb) with pitch-angle gridpts
          ! corresponding to both positive and negative vpa
          if (npts > 1) yb(:npts-1) = -ytmp(:npts-1)
          yb(npts:) = ytmp(npts:1:-1)


          ! get grid point weights for trapped particle integral

          ! Note : Here ulim and llim are the upper and lower vpar
          ! values for the valid trapped pitch angles at this theta
          ! location. In other words ulim == v_||_wfb(theta(ig)) =
          ! sqrt(max(1.0-lambda_grid(ng2+1)*bmag(ig),0.0)) = yb(2*npts-1)
          ! We could probably replace ulim and llim with yb(2*npts-1)
          ! and yb(1) respectively.
          ulim = sqrt(max(1.0-bmag(ig)/bmax,0.0))
          llim = -ulim

          ! Call get_weights to find the integration weights for the v||/v grid
          ! Note we don't call this if ulim == 0 as the grid then has no extent.
          ! This situation can arise when bmag(ig) == bmax where we would find
          ! npts = 1, corresponding to just the wfb being a valid lambda. This
          ! means that at this location the wfb has zero integration weight.
          ! Note we currently ignore the error flag and other returned values other
          ! than the weights. We should probably check this to at least warn the
          ! user if something looks badly behaved.
          if (ulim > 0.0) then
             if (new_trap_int_split) then
                ! We split the weights calculation into two symmetric domains from
                ! -v||_wfb to 0 and from 0 to v||_wfb. This has been shown to have
                ! favourable properties when compared to treating the full -v||_wfb
                ! to v||_wfb domain in a single call. In particular significantly more
                ! accurate results when integrating certain functions (see mod vpa test
                ! in the le_grids_integrate unit tests).
                call get_weights (nmax, llim, 0., yb(:npts), wb(:npts), ndiv, divmax, eflag)

                if (eflag .and. proc0) then
                   write(error_unit(), '("Error flag set by first call to get_weights in setup_trapped_lambda_grids")')
                end if

                ! Store the v|| = 0 weight for the lower domain as this will be
                ! clobbered by the subsequent get_weights call so we need to add
                ! this on afterwards
                wgt_tmp = wb(npts)
                call get_weights (nmax, 0., ulim, yb(npts:), wb(npts:), ndiv, divmax, eflag)

                if (eflag .and. proc0) then
                   write(error_unit(), '("Error flag set by second call to get_weights in setup_trapped_lambda_grids")')
                end if

                wb(npts) = wb(npts) + wgt_tmp
             else
                call get_weights (nmax, llim, ulim, yb, wb, ndiv, divmax, eflag)

                if (eflag .and. proc0) then
                   write(error_unit(), '("Error flag set by first call to get_weights in setup_trapped_lambda_grids")')
                end if
             end if
          end if

          ! Calculate the weights for use in the trapped
          ! integration error estimation code.  This can be quite
          ! expensive so we would like to to only call this if the
          ! error estimation code is active (i.e. if we're going to
          ! call [[get_verr]]).
          if (npts > 1) call get_trapped_lambda_grid_error_estimate_weights(yb, llim, ulim, npts, wberr)

          ! Convert from v||/v weights to lambda
          ! weights. Essentially just sum up the postive and
          ! negtive v|| points corresponding to the same lambda
          ! point. Note we skip the v|| == 0 point currently to ensure we
          ! don't double count.
          if (npts > 1) then
             do il = ng2+1, ng2+npts-1
                ! take into account possible asymmetry of weights about xi = 0
                ! due to unequal # of grid points per integration interval
                ! Whilst the v||/v grid should always be symmetric
                ! the algorithm in [[get_weights]] could
                ! potentially lead to asymmetry in the resulting
                ! weights. This may be an indication that things
                ! aren't behaving well.
                weights(ig,il) = wb(il-ng2) + wb(2*npts-il+ng2)
                wlterr(ig,il,:npts) = wberr(il-ng2,:) + wberr(2*npts-il+ng2,:)
             end do
          end if

          ! avoid double counting of gridpoint at vpa=0
          weights(ig,ng2+npts) = wb(npts)
          wlterr(ig,ng2+npts,:npts) = wberr(npts,:)

          deallocate(ytmp, yb, wb, wberr)
       end if
    end do
  end subroutine setup_trapped_lambda_grids_new_trap_int