interpolation.f90 Source File


Contents

Source Code


Source Code

module interpolation
   !! Library of Octave/MATLAB like interpolations
   !!
   !! The library supports following interpolation routine:
   !!
   !! - `yi = inerp1(x, y, xi, method, extrap`
   !!

   use, intrinsic :: iso_fortran_env, only: rkind => real64

   implicit none
   private

   interface interp1
      !! Generic interface to `interp1` function
      !!
      !! The `interp` function interpolates given table of values to
      !! given query points. The generic interface is
      !!
      !!     yi = interp1(x, y, xi, method, extrap)
      !!
      !! where
      !!
      !! - `x` is rank 1 array of sample points in ascending order
      !! - `y` is rank 1 or 2 array of values, for rank 2 array with elements
      !!   `y(i,j)` the index `i` corresponds to vector component and `j` is the
      !!   index of sample point, i.e. `size(x) == size(y,2)`
      !! - `xi` is scalar or rank 1 array of query points
      !! - `method` is an optional specification of interpolation method. It can be either
      !!   'linear', or 'pchip' (same as 'cubic'), default is 'linear'
      !!     - 'linear' does piecewise linear interpolation
      !!     - 'pchip' does smooth piecewise cubic interpolation
      !! - `extrap` is an optional parameter specifying the handling of query points
      !!   outside of sample point range. The parameter accepts
      !!     - 'NaN' values beyond the endpoints are set to NaN (**default**)
      !!     - 'extrap' values beyond the endpoints are extrapolated
      !!
      !! The result `yi` is either
      !!
      !! - single interpolated value if rank(y) == 1 and xi is scalar
      !! - rank 1 array of interpolated values if rank(y) == 1 and rank(xi) == 1,
      !!   in that case size(yi) == size(xi)
      !! - rank 1 array of interpolated values if rank(y) == 2 and xi is scalar
      !!   in that case size(yi) == size(y,1)
      !! - rank 2 array of interpolated values if rank(y) == 2 and rank(xi) == 1,
      !!   in that case size(yi,1) == size(y,1) and size(yi,2) == size(xi)
      !!
      module procedure interp1_rank00
      module procedure interp1_rank01
      module procedure interp1_rank10
      module procedure interp1_rank11
   end interface interp1


   type interp1_t
      !! Class implementing scalar interpolations
      !!
      !! The class implements interpolation of scalar quantity
      !! given at discrete points. It is a wraper to `interp`` function.
      !!
      !! @note
      !! The class supports only interpolation of scalars! For vectors
      !! use directly `interp1` function.

      real(rkind), allocatable :: x(:)         !! array of sample points
      real(rkind), allocatable :: y(:)         !! array of values in sample points
      character(len=:), allocatable :: method  !! interpolation method, @see interp1
      character(len=:), allocatable :: extrap  !! extrapolation method, @see interp1
   contains
      procedure :: at => interp1_t_at          !! returns interpolated value at given point
   end type interp1_t

   interface interp1_t
      module procedure create_interp1_t
   end interface interp1_t


   public :: interp1, interp1_t


contains

   !======================================================================
   ! class interp1_t
   !======================================================================
   function create_interp1_t(x, y, method, extrap) result(i1)
      real(rkind), intent(in)  :: x(:)
      real(rkind), intent(in)  :: y(size(x))
      character(len=*), intent(in), optional :: method
      character(len=*), intent(in), optional :: extrap
      type(interp1_t) :: i1

      i1%x = x
      i1%y = y
      if (present(method)) then
         i1%method = method
      else
         i1%method = "linear"
      end if

      if (present(extrap)) then
         i1%extrap = extrap
      else
         i1%extrap = "NaN"
      end if
   end function create_interp1_t


   pure function interp1_t_at(self, xi) result(yi)
      class(interp1_t), intent(in) :: self
      real(rkind), intent(in)      :: xi
      real(rkind)                  :: yi
      yi = interp1(self%x, self%y, xi, self%method, self%extrap)
   end function interp1_t_at


   !======================================================================
   ! function interp1
   !======================================================================

   pure function interp1_rank00(x, y, xi, method, extrap) result(yi)
      real(rkind), intent(in) :: x(:)
      real(rkind), intent(in) :: y(size(x))
      real(rkind), intent(in) :: xi
      character(len=*), intent(in), optional :: method
      character(len=*), intent(in), optional :: extrap
      real(rkind)             :: yi
      real(rkind)             :: yarray(1,1)

      yarray = interp1_dispatch(x, reshape(y,[1,size(x)]), [xi], method, extrap)
      yi = yarray(1,1)
   end function interp1_rank00


   pure function interp1_rank01(x, y, xi, method, extrap) result(yi)
      real(rkind), intent(in) :: x(:)
      real(rkind), intent(in) :: y(size(x))
      real(rkind), intent(in) :: xi(:)
      character(len=*), intent(in), optional :: method
      character(len=*), intent(in), optional :: extrap
      real(rkind)             :: yi(size(xi))
      real(rkind)             :: yarray(1,size(xi))

      yarray = interp1_dispatch(x, reshape(y,[1,size(x)]), xi, method, extrap)
      yi(:) = yarray(1,:)
   end function interp1_rank01


   pure function interp1_rank10(x, y, xi, method, extrap) result(yi)
      real(rkind), intent(in) :: x(:)
      real(rkind), intent(in) :: y(:,:)
      real(rkind), intent(in) :: xi
      character(len=*), intent(in), optional :: method
      character(len=*), intent(in), optional :: extrap
      real(rkind)             :: yi(size(y,1))
      real(rkind)             :: yarray(size(y,1),1)

      yarray = interp1_dispatch(x, y, [xi], method, extrap)
      yi(:) = yarray(:,1)
   end function interp1_rank10


   pure function interp1_rank11(x, y, xi, method, extrap) result(yi)
      use stdlib_optval, only: optval
      real(rkind), intent(in) :: x(:)
      real(rkind), intent(in) :: y(:,:)
      real(rkind), intent(in) :: xi(:)
      character(len=*), intent(in), optional :: method
      character(len=*), intent(in), optional :: extrap
      real(rkind)             :: yi(size(y,1),size(xi))
      real(rkind)             :: yarray(size(y,1),size(xi))

      yarray = interp1_dispatch(x, y, xi, method, extrap)
      yi(:,:) = yarray(:,:)
   end function interp1_rank11


   !======================================================================
   ! IMPLEMENTATION
   !======================================================================

   pure function interp1_dispatch(x, y, xi, method, extrap) result(yi)
      use stdlib_optval, only: optval
      real(rkind), intent(in) :: x(:)
      real(rkind), intent(in) :: y(:,:)
      real(rkind), intent(in) :: xi(:)
      character(len=*), intent(in), optional :: method
      character(len=*), intent(in), optional :: extrap
      real(rkind)             :: yi(size(y,1),size(xi))
      real(rkind)             :: yarray(size(y,1),size(xi))

      select case( optval(method, "linear") )

       case("linear")
         call interpolate_linearly(yarray, x, y, xi, optval(extrap, "NaN"))

       case("pchip", "cubic")
         call interpolate_pchip(yarray, x, y, xi, optval(extrap, "NaN"))

       case default
         error stop "interp1 supports only method='linear', 'pchip', or 'cubic'"

      end select

      yi(:,:) = yarray(:,:)
   end function interp1_dispatch


   pure subroutine interpolate_linearly(yi, x, y, xi, extrap)
      ! Calculates linear interpolation with given extrapolation method.
      ! The method is either 'extrap' or 'NaN'
      use stdlib_error, only: error_stop
      use, intrinsic :: ieee_arithmetic, only: ieee_value, ieee_quiet_nan
      real(rkind), intent(out) :: yi(:,:)
      real(rkind), intent(in)  :: x(:)
      real(rkind), intent(in)  :: y(:,:)
      real(rkind), intent(in)  :: xi(:)
      character(len=*), intent(in) :: extrap

      integer :: i, k
      do k = 1, size(xi)
         if ((x(1) <= xi(k) .and. xi(k) <= x(size(x))) .or. extrap == "extrap") then
            i = find_interval(x, xi(k))
            yi(:,k) = y(:,i) + (xi(k) - x(i))/(x(i+1) - x(i))*(y(:,i+1) - y(:,i))
         elseif (extrap == "NaN") then
            yi(:,k) = ieee_value(1.0, ieee_quiet_nan)
         else
            error stop "interp1 supports only extrap='extrap' or 'NaN'"
         end if
      end do
   end subroutine interpolate_linearly


   pure subroutine interpolate_pchip(yi, x, y, xi, extrap)
      ! Calculates piecewise cubic interpolation with given extrapolation method.
      ! The method is either 'extrap' or 'NaN'
      use, intrinsic :: ieee_arithmetic, only: ieee_value, ieee_quiet_nan
      real(rkind), intent(out) :: yi(:,:)
      real(rkind), intent(in)  :: x(:)
      real(rkind), intent(in)  :: y(:,:)
      real(rkind), intent(in)  :: xi(:)
      character(len=*), intent(in) :: extrap
      real(rkind)  :: h(0:size(x)), fpl(size(y,1)), fpr(size(y,1)), tl, tr
      integer :: i, k, l

      h(0) = 0
      h(1:size(x)-1) = abs(x(2:size(x)) - x(1:size(x)-1))
      h(size(x)) = 0

      do k = 1, size(xi)
         if ((x(1) <= xi(k) .and. xi(k) <= x(size(x))) .or. extrap == "extrap") then
            i = find_interval(x, xi(k))
            fpl(:) = (y(:,i+1) - y(:,i))/h(i)
            fpr(:) = fpl(:)
            if (h(i-1) > 0) then   ! limit fpl
               do l = 1, size(fpl)
                  fpl(l) = harmonic_mean((y(l,i) - y(l,i-1))/h(i-1), fpl(l), h(i-1), h(i))
               end do
            end if
            if (h(i+1) > 0) then   ! limit fpr
               do l = 1, size(fpr)
                  fpr(l) = harmonic_mean(fpr(l), (y(l,i+2) - y(l,i+1))/h(i+1), h(i), h(i+1))
               end do
            end if

            tl = (x(i+1) - xi(k))/h(i)
            tr = (xi(k) - x(i))/h(i)

            yi(:,k) = y(:,i)*(3*tl**2 - 2*tl**3) + y(:,i+1)*(3*tr**2 - 2*tr**3) &
               - fpl(:)*h(i)*(tl**3 - tl**2) + fpr(:)*h(i)*(tr**3 - tr**2)
         elseif (extrap == "NaN") then
            yi(:,k) = ieee_value(1.0, ieee_quiet_nan)
         else
            error stop "interp1 supports only extrap='extrap' or 'NaN'"
         end if
      end do

   contains

      elemental function harmonic_mean(da, db, ha, hb) result(c)
         !! Calculates harmonic mean of two numbers
         !! da and db are the derivatives at the two points, ha and hb are the step sizes
         real(rkind), intent(in) :: da, db
         real(rkind), intent(in) :: ha, hb
         real(rkind)             :: c
         if (da*db <= 0) then
            c = 0
         else
            c = 3*(ha + hb)/( (2*hb + ha)/da + (2*ha + hb)/db)
         end if
      end function harmonic_mean

   end subroutine interpolate_pchip


   !======================================================================
   ! private support functions
   !======================================================================

   pure function find_interval(x, xi) result(i)
      !! Finds starting index of interval in which xi is located
      !! Assumes that x is sorted in ascending order
      real(rkind), intent(in) :: x(:)
      real(rkind), intent(in) :: xi
      integer :: i
      integer :: low, high, mid
      low = 1
      high = size(x)
      do while (high - low > 1)
         mid = (high + low)/2
         if (xi < x(mid)) then
            high = mid
         else
            low = mid
         end if
      end do
      i = low
   end function find_interval


end module interpolation