summation.f90 Source File


Contents

Source Code


Source Code

!> Provides utility routines for compenstated summation. The purpose
!> of these approaches is to try to minimise the accumulation of
!> floating point errors.
module summation
  implicit none
  private

  public :: kahan_sum, neumaier_sum, klein_sum, pairwise_sum

  interface kahan_sum
     module procedure kahan_sum_real_1d
  end interface kahan_sum

  interface neumaier_sum
     module procedure neumaier_sum_real_1d
  end interface neumaier_sum

  interface klein_sum
     module procedure klein_sum_real_1d
  end interface klein_sum

  interface pairwise_sum
     module procedure pairwise_sum_real_1d
  end interface pairwise_sum

contains

  !> Simple Kahan summation designed to limit the error arising from
  !> summing a large collection of numbers by taking care to keep
  !> track of the required compensation.
  pure real function kahan_sum_real_1d(array) result(the_result)
    implicit none
    real, dimension(:), intent(in) :: array
    integer :: i
    real :: compensation, compensated_element, current_total
    the_result = 0.0
    compensation = 0.0
    do i = 1, size(array)
       compensated_element = array(i) - compensation
       current_total = the_result + compensated_element
       compensation = (current_total - the_result) - compensated_element
       the_result = current_total
    end do
  end function kahan_sum_real_1d

  !> Very similar to Kahan summation but can perform better when the
  !> array contains a large range of inputs (i.e. where there may be
  !> elements that are larger the the cummulattive sum at that point)
  pure real function neumaier_sum_real_1d(array) result(the_result)
    implicit none
    real, dimension(:), intent(in) :: array
    integer :: i
    real :: compensation, current_total
    the_result = 0.0
    compensation = 0.0
    do i = 1, size(array)
       current_total = the_result + array(i)
       ! Here we choose how to calculate the compensation based on
       ! which is large of the running total and the current value
       if ( abs(the_result) >= abs(array(i)) ) then
          compensation = compensation + (the_result - current_total) + array(i)
       else
          compensation = compensation + (array(i) - current_total) + the_result
       end if
       the_result = current_total
    end do
    the_result = the_result + compensation
  end function neumaier_sum_real_1d

  !> Similar approach to Neumaier but introduces two levels of compensation
  pure real function klein_sum_real_1d(array) result(the_result)
    implicit none
    real, dimension(:), intent(in) :: array
    integer :: i
    real :: local_compensation_1, local_compensation_2, compensation_1, compensation_2
    real :: current_total
    the_result = 0.0
    compensation_1 = 0.0
    compensation_2 = 0.0

    do i = 1, size(array)
       ! This part is very similar to neumaier
       current_total = the_result + array(i)
       if ( abs(the_result) >= abs(array(i)) ) then
          local_compensation_1 = (the_result - current_total) + array(i)
       else
          local_compensation_1 = (array(i) - current_total) + the_result
       end if
       the_result = current_total

       ! Here we repeat the above approach but for accumulating the compensation
       current_total = compensation_1 + local_compensation_1
       if ( abs(compensation_1) >= abs(local_compensation_1) ) then
          local_compensation_2 = (compensation_1 - current_total) + local_compensation_1
       else
          local_compensation_2 = (local_compensation_1 - current_total) + compensation_1
       end if

       compensation_1 = current_total
       compensation_2 = compensation_2 + local_compensation_2
    end do
    the_result = the_result + compensation_1 + compensation_2
  end function klein_sum_real_1d

  !> Unlike Kahan summation and similar methods this approach simply
  !> divides the set of numbers in half and sums each half
  !> separately. This is applied recursively until the local problem size
  !> drops below a certain size, at which point regular summation is applied
  !>
  !> This should be more efficient than Kahan style approaches. Here
  !> there should be roughly the same number of additions as a simple
  !> sum, whilst Kahan style routines will have somewhat more (Kahan
  !> is roughly four times as much). We do have to worry a little
  !> about the cost of recursion here. Provided small_case_size is not
  !> too small then hopefully the recursion depth is not that large
  !> and hence we can neglect the cost of recursion.
  recursive real function pairwise_sum_real_1d(array) result(the_result)
    implicit none
    real, dimension(:), intent(in) :: array
    !> The small_case_size parameter controls the problem size for which the
    !> approach switches between recursive calls and a direct summation
    integer, parameter :: small_case_size = 5
    integer :: task_size, mid_point

    task_size = size(array)
    if (task_size <= small_case_size) then
       ! We could use one of the other methods provided here rather
       ! than the language provided `sum` but probably not required
       ! as small_case_size should be small.
       the_result = sum(array)
    else
       mid_point = task_size / 2
       the_result = pairwise_sum(array(:mid_point)) + pairwise_sum(array(mid_point+1:))
    end if
  end function pairwise_sum_real_1d

end module summation