rotex__functions.f Source File


Source Code

! ================================================================================================================================ !
module rotex__functions
  !! Contains various small functions used in the code

  use rotex__kinds,  only: dp

  private

  public :: delta
  public :: factorial
  public :: log_factorial
  public :: are_approx_eq
  public :: expm1
  ! public :: logp1
  public :: isnatural
  public :: isinteger
  public :: arg
  public :: inv
  public :: iseven
  public :: isodd
  public :: istriangle
  public :: logrange
  public :: logb
  public :: neg

  interface are_approx_eq
    !! Compare two a and b and see if the magnitude of their difference is smaller than a tolerance,
    !! taking machine epsilon*max(|a|,|b|) for their precision as the default value
    module procedure :: are_approx_eqz
    module procedure :: are_approx_eqr
  end interface are_approx_eq

  interface factorial
    !! !n
    module procedure :: factorial_int
    module procedure :: factorial_real
  end interface factorial

  interface log_factorial
    !! ln(!n)
    module procedure :: log_factorial_i
    module procedure :: log_factorial_r
  end interface log_factorial

  interface expm1
    module procedure :: expm1r
    module procedure :: expm1z
  end interface expm1

  ! interface logp1
  !   module procedure :: logp1r
  !   module procedure :: logp1z
  ! end interface logp1

  interface logb
    module procedure :: logb_ii
    module procedure :: logb_rr
    module procedure :: logb_cc
  end interface logb

  interface logrange
    module procedure :: logrange_ib
    module procedure :: logrange_rb
  end interface logrange

  interface
    !! Interface to the C functions expm1 for real x
    function c_expm1(x) bind(C, name="expm1") result(res)
      use, intrinsic :: iso_c_binding, only: c_double
      implicit none
      real(c_double), value :: x
      real(c_double) :: res
    end function c_expm1
  end interface

  interface
    !! Interface to the C functions logp1 for real x
    function c_logp1(x) bind(C, name="logp1") result(res)
      use, intrinsic :: iso_c_binding, only: c_double
      implicit none
      real(c_double), value :: x
      real(c_double) :: res
    end function c_logp1
  end interface

  interface isinteger
    !! Check if a real/complex number is an integer
    module procedure :: isintegerr
    module procedure :: isintegerz
  end interface isinteger

  interface isnatural
    !! Check if a real/complex number is a natrual number
    module procedure :: isnaturalr
    module procedure :: isnaturalz
  end interface isnatural

  interface arg
    !! Return the phase of a complex number in (-π,π]
    module procedure argi
    module procedure argr
    module procedure argc
  end interface arg

  interface inv
    !! Compute 1/z
    module procedure invi
    module procedure invr
    module procedure invz
  end interface inv

! ================================================================================================================================ !
contains
! ================================================================================================================================ !

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elementaL module function are_approx_eqz(a, b, tol) result(res)
    !! Comparre two a and b and see if magnitude of their difference is smaller than a tolerance,
    !! taking machine epsilon for their precision as the default value
    use rotex__constants, only: macheps => macheps_dp

    implicit none

    complex(dp), intent(in) :: a, b
    real(dp), intent(in), optional :: tol
    logical :: res

    real(dp) :: tol_local

    tol_local = macheps*max(abs(a), abs(b)) ; if(present(tol)) tol_local = tol

    res = .false.
    if(abs(a - b) .gt. tol_local) return
    res = .true.

  end function are_approx_eqz
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elementaL module function are_approx_eqr(a, b, tol) result(res)
    !! Comparre two a and b and see if magnitude of their difference is smaller than a tolerance,
    !! taking machine epsilon for their precision as the default value
    use rotex__constants, only: macheps => macheps_dp

    implicit none

    real(dp), intent(in) :: a, b
    real(dp), intent(in), optional :: tol
    logical :: res

    real(dp) :: tol_local

    tol_local = macheps*max(abs(a), abs(b)) ; if(present(tol)) tol_local = tol

    res = .false.
    if(abs(a - b) .gt. tol_local) return
    res = .true.

  end function are_approx_eqr

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function delta(m, n) result(res)
    !! Return the Kronecker delta function \(\delta_{m,n}\)
    use rotex__constants, only: zero, one
    implicit none
    integer, intent(in) :: m, n
    real(dp) :: res
    res = zero ; if(m .eq. n) res = one
  end function delta

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function factorial_int(n) result(res)
    !! !n
    implicit none
    integer, intent(in) :: n
    real(dp) :: res
    res = gamma(real(n + 1, kind = dp))
  end function factorial_int

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function factorial_real(n) result(res)
    !! !n
    use rotex__constants, only: one
    implicit none
    real(dp), intent(in) :: n
    real(dp) :: res
    res = gamma(n + one)
  end function factorial_real

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  module function expm1r(x) result(res)
    !! Returns \(e^x - 1\). TODO, replace with a fast and accurate version
    !! coded natively in fortran !

    implicit none

    real(dp), intent(in), value :: x
    real(dp) :: res
    res = c_expm1(x)
  end function expm1r
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  module function expm1z(z) result(res)
    !! Returns \(e^x - 1\). TODO, replace with a fast and accurate version
    !! coded natively in fortran !
    implicit none
    complex(dp), intent(in) :: z ! = a + ib
    complex(dp) :: res
    real(dp) :: sin_bover2
    sin_bover2 = sin(z%im / 2)
    res % re = c_expm1(z%re)*cos(z%im) - 2*sin_bover2*sin_bover2
    res % im = exp(z%re)*sin(z%im)
  end function expm1z

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  module function logp1r(x) result(res)
    !! Returns \(\log(x+1)\) for real z. TODO, replace with a fast and accurate version
    !! coded natively in fortran !
    implicit none
    real(dp), intent(in) :: x
    real(dp) :: res
    res = c_logp1(x)
  end function logp1r
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function logp1z(z) result(res)
    !! Returns \(\log(z+1)\) for complex z. TODO, replace with a fast and accurate version ?
    !! coded natively in fortran !
    use rotex__constants, only: one
    implicit none
    complex(dp), intent(in) :: z
    complex(dp) :: res
    complex(dp) :: zp1
    zp1 = z + one
    res = log(zp1) - (zp1 - one - z)/zp1
  end function logp1z

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function isintegerr(x) result(res)
    !! Check if x is an integer
    implicit none
    real(dp), intent(in) :: x
    logical :: res
    res = .false.
    if(x .ne. nint(x)) return
    res = .true.
  end function isintegerr
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function isintegerz(z) result(res)
    !! Check if z is an integer
    use rotex__constants, only: zero
    implicit none
    complex(dp), intent(in) :: z
    logical :: res
    res = .false.
    if(z%im .ne. zero) return
    if(z%re .ne. nint(z%re)) return
    res = .true.
  end function isintegerz

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function isnaturalr(x) result(res)
    !! Check if x is a natural number (1, 2, 3, ...)
    use rotex__constants, only: zero
    implicit none
    real(dp), intent(in) :: x
    logical :: res
    integer :: nintx
    res = .false.
    nintx = nint(x)
    if(x .ne. nintx) return
    if(nintx .le. 0) return
    res = .true.
  end function isnaturalr
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function isnaturalz(z) result(res)
    !! Check if z is a natural number (1, 2, 3, ...)
    use rotex__constants, only: zero
    implicit none
    complex(dp), intent(in) :: z
    logical :: res
    integer :: nintz
    res = .false.
    nintz = nint(z%re)
    if(z%im .ne. zero) return
    if(z%re .ne. nintz) return
    if(nintz .le. 0) return
    res = .true.
  end function isnaturalz

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function argi(z) result(res)
    !! Return the phase of an integer (as a complex number) in (-π,π]
    use rotex__constants, only: pi
    implicit none
    integer, intent(in) :: z
    real(dp) :: res
    res = 0.0_dp
    if(z .ge. 0) return
    res = pi
  end function argi
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function argr(z) result(res)
    !! Return the phase of a real number (as a complex number) in (-π,π]
    use rotex__constants, only: pi
    implicit none
    real(dp), intent(in) :: z
    real(dp) :: res
    res = 0.0_dp
    if(z .ge. 0._dp) return
    res = pi
  end function argr
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function argc(z) result(res)
    !! Return the phase of a complex number in (-π,π]
    use rotex__constants, only: pi, zero
    implicit none
    complex(dp), intent(in) :: z
    real(dp) :: res
    res = zero
    if(res .eq. (zero, zero)) return
    res = atan2(z%im, z%re)
  end function argc

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function invi(i) result(res)
    !! Return the real 1/i for integer i
    implicit none
    integer, intent(in) :: i
    real(dp) :: res
    res = 1/real(i, kind = dp)
  end function invi
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function invr(x) result(res)
    !! Return 1/x
    use rotex__constants, only: zero
    implicit none
    real(dp), intent(in) :: x
    real(dp) :: res
    res = 1/x
  end function invr
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function invz(z) result(res)
    !! Return 1/z for complex z. If the imaginary part of z is 0, flip its sign
    use ieee_arithmetic, only: ieee_is_negative, ieee_copy_sign
    implicit none
    complex(dp), intent(in) :: z
    complex(dp) :: res
    res = 1/z
    if(z%im .ne. 0) return
    if(ieee_is_negative(z%im) .eqv. .true.) return
    res%im = -res%im
  end function invz

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function iseven(n) result(res)
    integer, intent(in) :: n
    logical :: res
    res = .false.
    if(iand(n, 1) .eq. 1) return
    res = .true.
  end function iseven
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function isodd(n) result(res)
    integer, intent(in) :: n
    logical :: res
    res = .not. iseven(n)
  end function isodd

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function istriangle(a, b, c) result(res)
    !! returns whether the arguments satisfy the triangle inequality,
    !! assuming that they're positive quantities

    implicit none

    integer, intent(in) :: a
    integer, intent(in) :: b
    integer, intent(in) :: c
    logical :: res

    res = .false.

    if(c .gt. a+b) return
    if(a .gt. b+c) return
    if(b .gt. a+c) return

    res = .true.

  end function istriangle

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function log_factorial_i(n) result(res)
    !! returns ln(n!) for integer n
    use rotex__system, only: die
    implicit none
    integer, intent(in) :: n
    real(dp) :: res
    select case(n)
    case(0)  ; res = 0.0_dp
    case(1)  ; res = 1.0_dp
    case(2:) ; res = log_gamma(n + 1.0_dp)
    case default
      call die("ln(n!) not defined for negative integer n")
    end select
  end function log_factorial_i
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental module function log_factorial_r(n) result(res)
    !! returns ln(n!) for real n
    use rotex__system, only: die
    implicit none
    real(dp), intent(in) :: n
    real(dp) :: res
    if(n .lt. 0) call die("ln(n!) not defined for negative real n")
    res = log_gamma(n+1)
  end function log_factorial_r

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure elemental function logb_ii(b, x) result(res)
    !! Returns the logarithm of x in the base b
    implicit none
    integer, intent(in) :: b
    integer, intent(in) :: x
    real(dp) :: res
    res = log(real(x, kind = dp))/log(real(b, kind = dp))
  end function logb_ii
  pure elemental function logb_rr(b, x) result(res)
    !! Returns the logarithm of x in the base b
    implicit none
    real(dp), intent(in) :: b
    real(dp), intent(in) :: x
    real(dp) :: res
    res = log(x)/log(b)
  end function logb_rr
  pure elemental function logb_cc(b, x) result(res)
    !! Returns the logarithm of x in the base b
    implicit none
    complex(dp), intent(in) :: b
    complex(dp), intent(in) :: x
    complex(dp) :: res
    res = log(x)/log(b)
  end function logb_cc

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure function logrange_ib(a, b, n, base, inclast) result(res)
    !! Returns an array of n log-spaced values from a to b. By default, the base is 10
    !! if omitted but can be changed by the user
    implicit none
    integer(dp), intent(in) :: a, b
    integer,  intent(in) :: n
    integer,  intent(in), optional :: base
      !! Logarithm base
    logical, intent(in), optional :: inclast
      !! Whether to include the last value b
    real(dp) :: res(n)
    logical :: inclast_
    integer :: base_
    inclast_ = .true. ; if(present(inclast)) inclast_ = inclast
    base_ = 10 ; if(present(base)) base_ = base
    res = logrange_rb(real(a, kind = dp), real(b, kind = dp), n, real(base_, kind = dp), inclast_)
  end function logrange_ib
  pure function logrange_rb(a, b, n, base, inclast) result(res)
    !! Returns an array of n log-spaced values from a to b. By default, the base is 10
    !! if omitted but can be changed by the user
    implicit none
    real(dp), intent(in) :: a, b
    integer,  intent(in) :: n
    real(dp), intent(in), optional :: base
      !! Logarithm base
    logical, intent(in), optional :: inclast
      !! Whether to include the last value b
    real(dp) :: res(n)
    logical :: inclast_
    integer  :: i, m
    real(dp) :: base_
    real(dp) :: start, finish
    real(dp) :: dx
    real(dp) :: logx(n)
    base_    = 10._dp ; if(present(base))    base_    = base
    inclast_ = .true. ; if(present(inclast)) inclast_ = inclast
    if(inclast_) then
      m = n
    else
      m = n+1
    endif
    start     = logb(base_, a)
    finish    = logb(base_, b)
    dx        = (finish - start) / (m-1)
    logx      = [( start + i*dx, i=0, m-1 )]
    res       = base_**logx(1:n)
  end function logrange_rb

  ! -------------------------------------------------------------------------------------------------------------------------------- !
  pure elemental module function neg(i) result(res)
    !! Returns the integer \((-1)^{i)\)}
    implicit none
    integer, intent(in) :: i
    integer :: res
    res = 1
    if(iand(i, 1) .eq. 0) return
    res = -1
  end function neg

! ================================================================================================================================ !
end module rotex__functions
! ================================================================================================================================ !