rotex__arrays.f Source File


Source Code

! ================================================================================================================================ !
module rotex__arrays
  !! Various routines for arrays
  use rotex__types, only: dp

  implicit none

  private

  public :: append
  public :: append_uniq
  public :: uniq
  public :: remove_value
  public :: eye
  public :: adjoint
  public :: size_check
  public :: is_unitary
  public :: is_symmetric
  public :: norm_frob
  public :: realloc
  public :: unitary_defect
  public :: sort_index
  ! public :: vec2diag

  interface append_uniq
    module procedure :: append_uniq_i
    module procedure :: append_uniq_transition
  end interface append_uniq
  interface append
    module procedure :: append_i
    module procedure :: append_r
    module procedure :: append_rvector
    module procedure :: append_asymtop_transition
    module procedure :: append_elec_channel
    module procedure :: append_elec_channels
    module procedure :: append_asymtop_rot_channel
    module procedure :: append_asymtop_rot_channel_l
  end interface append

  interface adjoint
    module procedure :: adjoint_i
    module procedure :: adjoint_r
    module procedure :: adjoint_c
  end interface adjoint

  interface size_check
    module procedure :: size_check_1d
    module procedure :: size_check_2d
  end interface size_check

  interface norm_frob
    module procedure :: norm_frob_i
    module procedure :: norm_frob_r
    module procedure :: norm_frob_c
  end interface norm_frob

  interface realloc
    module procedure :: realloc_1d_int
    module procedure :: realloc_1d_real
    module procedure :: realloc_1d_cmplx
    module procedure :: realloc_1d_elec_channel
    module procedure :: realloc_2d_real
    module procedure :: realloc_2d_cmplx
  end interface realloc

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

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine realloc_1d_int(arr, n)
    implicit none
    integer, intent(inout), allocatable :: arr(:)
    integer,  intent(in)                 :: n
    if(allocated(arr)) then
      if(size(arr, 1) .eq. n) return
      deallocate(arr)
      allocate(arr(n))
      return
    endif
    allocate(arr(n))
  end subroutine realloc_1d_int
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine realloc_1d_real(arr, n)
    implicit none
    real(dp), intent(inout), allocatable :: arr(:)
    integer,  intent(in)                 :: n
    if(allocated(arr)) then
      if(size(arr, 1) .eq. n) return
      deallocate(arr)
      allocate(arr(n))
      return
    endif
    allocate(arr(n))
  end subroutine realloc_1d_real
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine realloc_1d_cmplx(arr, n)
    implicit none
    complex(dp), intent(inout), allocatable :: arr(:)
    integer,  intent(in)                 :: n
    if(allocated(arr)) then
      if(size(arr, 1) .eq. n) return
      deallocate(arr)
      allocate(arr(n))
      return
    endif
    allocate(arr(n))
  end subroutine realloc_1d_cmplx
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine realloc_1d_elec_channel(arr, n)
    use rotex__types, only: elec_channel_type
    implicit none
    type(elec_channel_type), intent(inout), allocatable :: arr(:)
    integer,  intent(in)                 :: n
    if(allocated(arr)) then
      if(size(arr, 1) .eq. n) return
      deallocate(arr)
      allocate(arr(n))
      return
    endif
    allocate(arr(n))
  end subroutine realloc_1d_elec_channel
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine realloc_2d_real(arr, n, m)
    implicit none
    real(dp), intent(inout), allocatable :: arr(:,:)
    integer,  intent(in)                 :: n, m
    if(allocated(arr)) then
      if(all(shape(arr) .eq. [n,m])) return
      deallocate(arr)
      allocate(arr(n,m))
      return
    endif
    allocate(arr(n,m))
  end subroutine realloc_2d_real
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine realloc_2d_cmplx(arr, n, m)
    implicit none
    complex(dp), intent(inout), allocatable :: arr(:,:)
    integer,  intent(in)                 :: n, m
    if(allocated(arr)) then
      if(all(shape(arr) .eq. [n,m])) return
      deallocate(arr)
      allocate(arr(n,m))
      return
    endif
    allocate(arr(n,m))
  end subroutine realloc_2d_cmplx

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module function norm_frob_i(A) result(res)
    !! Returns the Frobenius norm for a matrix A
    implicit none
    integer, intent(in) :: A(:,:)
    real(dp) :: res
    res = sqrt(real(sum(A*A), kind=dp))
  end function norm_frob_i
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module function norm_frob_r(A) result(res)
    !! Returns the Frobenius norm for a matrix A
    implicit none
    real(dp), intent(in) :: A(:,:)
    real(dp) :: res
    res = sqrt(sum(A*A))
  end function norm_frob_r
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module function norm_frob_c(A) result(res)
    !! Returns the Frobenius norm for a matrix A
    implicit none
    complex(dp), intent(in) :: A(:,:)
    real(dp) :: res
    res = sqrt(sum(abs(A)**2))
  end function norm_frob_c

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module function adjoint_i(A) result(res)
    !! Returns the adjoint of an integer-valued matrix
    implicit none
    integer, intent(in) :: A(:,:)
    integer :: res(size(A, 2), size(A, 1))
    res = transpose(A)
  end function adjoint_i
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module function adjoint_r(A) result(res)
    !! Returns the adjoint of a real-valued matrix
    implicit none
    real(dp), intent(in) :: A(:,:)
    real(dp) :: res(size(A, 2), size(A, 1))
    res = transpose(A)
  end function adjoint_r
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module function adjoint_c(A) result(res)
    !! Returns the adjoint of a complex-valued matrix
    implicit none
    complex(dp), intent(in) :: A(:,:)
    complex(dp) :: res(size(A, 2), size(A, 1))
    res = conjg(transpose(A))
  end function adjoint_c

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure function unitary_defect(A) result(rF)
    !! Return the unitary defect with respect to the Frobenius norm
    !! \(rF = ||A^{\dagger}A-I||_F / sqrt{n}\)
    use rotex__system, only: die
    implicit none
    class(*), intent(in) :: A(:,:)
    real(dp) :: rF
    integer :: n, m
    integer :: i, j
    real(dp) :: err2

    n = size(A, 1)
    m = size(A, 2)

    if(n .ne. m) call die("Cannot determine the unitarity of a nonsquare matrix")

    err2 = 0

    select type(A)

    type is (real(dp))

    realmat: block
      real(dp), allocatable :: G(:,:)
      real(dp) :: d
      allocate(G(n,m))
      ! -- Gram matrix
      G = matmul(adjoint(A), A)
      do j=1,n
        do i=1,n
          ! -- subtract identity if i==j
          d = G(i,j) - merge(1, 0, i .eq. j)
          ! -- accumulate norm squared
          err2 = err2 + d*d
        enddo
      enddo
    end block realmat

    type is (complex(dp))

    cmplxmat: block
      complex(dp), allocatable :: G(:,:)
      complex(dp) :: d
      allocate(G(n,m))
      ! -- Gram matrix
      G = matmul(adjoint(A), A)
      do j=1,n
        do i=1,n
          ! -- subtract identity if i==j
          d = G(i,j) - merge(1, 0, i .eq. j)
          ! -- accumulate norm squared
          err2 = err2 + abs(d)**2
        enddo
      enddo
    end block cmplxmat

    class default

      call die("Trying to determine the unitary of a matrix that is neither real nor complex")

    end select

    ! -- relative size-aware defect
    rF = sqrt(err2) / sqrt(real(n, kind = dp))

  end function unitary_defect

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure function is_unitary(A, rtol) result(res)
    !! Determine whether a matrix is unitary w.r.t the Frobenius norm
    use rotex__constants, only: macheps => macheps_dp
    class(*), intent(in) :: A(:,:)
      !! The matrix
    real(dp), intent(in), optional :: rtol
      !! The optional relative tolerance, default 1e-10
    logical :: res
    integer :: n, m
    real(dp) :: rF, tol
    tol = 1e-10_dp ; if(present(rtol)) tol = rtol
    res = .false.
    n = size(A, 1)
    m = size(A, 2)
    if(n .ne. m) return
    rF = unitary_defect(A)
    tol = max(tol, 100*macheps*sqrt(real(n, kind=dp)))
    res = rF .lt. tol
  end function is_unitary

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure function is_symmetric(A, rtol) result(res)
    !! Determine whether a matrix is symmetric
    use rotex__constants, only: macheps => macheps_dp
    class(*), intent(in) :: A(:,:)
      !! The matrix
    real(dp), intent(in), optional :: rtol
      !! The optional relative tolerance, default 1e-10
    logical :: res
    integer :: n, m
    real(dp) :: tol, diff, denom
    tol = 1e-10_dp ; if(present(rtol)) tol = rtol
    res = .false.
    n = size(A, 1)
    m = size(A, 2)
    if(n .ne. m) return
    select type(AA => A)
    type is (integer)
      diff = norm_frob(AA - transpose(AA))
      denom = max(1._dp, norm_frob(AA))
    type is (real(dp))
      diff = norm_frob(AA - transpose(AA))
      denom = max(1._dp, norm_frob(AA))
    type is (complex(dp))
      diff = norm_frob(AA - transpose(AA))
      denom = max(1._dp, norm_frob(AA))
    end select
    res = diff .le. tol*denom + sqrt(macheps)
  end function is_symmetric

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module function eye(n) result(res)
    !! Return an n x n identity matrix
    use rotex__system, only: die
    integer, intent(in) :: n
    integer :: res(n,n)
    integer :: i
    if(n .lt. 1) call die("Attempt to make an identity matrix with dims < 1")
    res = 0
    do concurrent(i=1:n)
      res(i,i) = 1
    enddo
  end function eye

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine remove_value(arr, val)
    !! Remove all instances of the value val from the array arr
    implicit none
    integer, intent(inout), allocatable :: arr(:)
    integer, intent(in) :: val
    integer, allocatable :: tmp(:)
    integer :: i, n
    if(.not. allocated(arr)) return
    n = size(arr, 1)
    do i=1, n
      if(val .ne. arr(i)) call append(tmp, arr(i))
    enddo
    if(.not. allocated(tmp)) then
      deallocate(arr)
      return
    endif
    call move_alloc(tmp, arr)
  end subroutine remove_value

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  subroutine append_uniq_i(arr, new)
    !! Append unique element "new" to array "arr"
    implicit none
    integer, intent(in)                 :: new
    integer, intent(inout), allocatable :: arr(:)
    select case(allocated(arr))
    case(.true.)
      if(any(arr .eq. new)) return
      arr = [arr, new]
    case(.false.)
      arr = [new]
    end select
  end subroutine append_uniq_i

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine append_uniq_transition(old, new)
    !! Append unique element "new" to array "old"
    use rotex__types, only: asymtop_rot_transition_type, operator(.ne.), operator(.isin.)
    implicit none
    type(asymtop_rot_transition_type), intent(in)                 :: new(:)
    type(asymtop_rot_transition_type), intent(inout), allocatable :: old(:)

    logical, allocatable :: mask(:)
    integer :: itrans, nold, num_new_uniq, nnew
    type(asymtop_rot_transition_type), allocatable :: tmp(:)

    select case(allocated(old))

    case(.false.)

      old = new

    case(.true.)

      ! -- count number of uniq elements
      nnew = size(new, 1)
      nold = size(old, 1)
      allocate(mask(nnew), source=.false.)
      if((new(1) .isin. old(:)) .eqv. .false.) mask(1) = .true. ! check first element
      ! get uniq elemnt indices
      do itrans = 2, nnew
        if(new(itrans) .isin. new(:itrans-1)) cycle ! skip dupicate elements in new
        if(new(itrans) .isin. old(:)) cycle
        mask(itrans) = .true.
      enddo
      num_new_uniq = count(mask)

      ! -- return if nothing to append
      if(num_new_uniq .eq. 0) return

      allocate(tmp(nold + num_new_uniq))
      tmp(1:nold)  = old(:)
      tmp(nold+1:) = pack(new, mask)
      call move_alloc(tmp, old)

    end select

  end subroutine append_uniq_transition

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine append_i(arr, val)
    !! Append the value val to the array arr
    implicit none
    integer, intent(inout), allocatable :: arr(:)
    integer, intent(in) :: val
    select case(allocated(arr))
    case(.true.)
      arr = [arr, val]
    case(.false.)
      arr = [val]
    end select
  end subroutine append_i
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine append_r(arr, val)
    !! Append the value val to the array arr
    implicit none
    real(dp), intent(inout), allocatable :: arr(:)
    real(dp), intent(in) :: val
    select case(allocated(arr))
    case(.true.)
      arr = [arr, val]
    case(.false.)
      arr = [val]
    end select
  end subroutine append_r
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine append_rvector(arr, val)
    !! Append the value val to the array arr
    use rotex__types, only: rvector_type
    implicit none
    type(rvector_type), intent(inout), allocatable :: arr(:)
    real(dp), intent(in) :: val(:)
    type(rvector_type) :: elem
    allocate(elem%vec(size(val, 1)))
    elem % vec = val
    select case(allocated(arr))
    case(.true.)
      arr = [arr, elem]
    case(.false.)
      arr = [elem]
    end select
  end subroutine append_rvector
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine append_asymtop_transition(arr, val)
    !! Append the value val to the array arr
    use rotex__types, only: asymtop_rot_transition_type
    implicit none
    type(asymtop_rot_transition_type), intent(inout), allocatable :: arr(:)
    type(asymtop_rot_transition_type), intent(in) :: val
    select case(allocated(arr))
    case(.true.)
      arr = [arr, val]
    case(.false.)
      arr = [val]
    end select
  end subroutine append_asymtop_transition
  ! ------------------------------------------------------------------------------------------------------------------------------- !
  pure module subroutine append_elec_channel(channels, channel)
    !! Append elec_channel to the array elec_channels
    use rotex__types, only: elec_channel_type
    implicit none
    type(elec_channel_type), intent(inout), allocatable :: channels(:)
    type(elec_channel_type), intent(in) :: channel
    select case(allocated(channels))
    case(.true.)  ; channels = [channels, channel]
    case(.false.) ; channels = [channel]
    end select
  end subroutine append_elec_channel
  ! ------------------------------------------------------------------------------------------------------------------------------- !
  pure module subroutine append_elec_channels(channels, channels2)
    !! Append channels2 to the array channels
    use rotex__types, only: elec_channel_type
    implicit none
    type(elec_channel_type), intent(inout), allocatable :: channels(:)
    type(elec_channel_type), intent(in) :: channels2(:)
    select case(allocated(channels))
    case(.true.)  ; channels = [channels, channels2]
    case(.false.) ; channels = [channels2]
    end select
  end subroutine append_elec_channels
  ! ------------------------------------------------------------------------------------------------------------------------------- !
  pure module subroutine append_asymtop_rot_channel(channels, channel)
    !! Append asymtop_rot_channel to the array asymtop_rot_channels
    use rotex__types, only: asymtop_rot_channel_type
    implicit none
    type(asymtop_rot_channel_type), intent(inout), allocatable :: channels(:)
    type(asymtop_rot_channel_type), intent(in) :: channel
    select case(allocated(channels))
    case(.true.)  ; channels = [channels, channel]
    case(.false.) ; channels = [channel]
    end select
  end subroutine append_asymtop_rot_channel
  ! ------------------------------------------------------------------------------------------------------------------------------- !
  pure module subroutine append_asymtop_rot_channel_l(channels, channel)
    !! Append asymtop_rot_channel to the array asymtop_rot_channels
    use rotex__types, only: asymtop_rot_channel_l_type
    implicit none
    type(asymtop_rot_channel_l_type), intent(inout), allocatable :: channels(:)
    type(asymtop_rot_channel_l_type), intent(in) :: channel
    select case(allocated(channels))
    case(.true.)  ; channels = [channels, channel]
    case(.false.) ; channels = [channel]
    end select
  end subroutine append_asymtop_rot_channel_l

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine size_check_1d(arr, larr, name)
    !! Check that the size of the array arr is of length larr
    use rotex__system,     only: die
    use rotex__characters, only: i2c => int2char
    implicit none
    class(*), intent(in) :: arr(:)
    integer, intent(in) :: larr
    character(*), intent(in) :: name
    if(size(arr, 1) .ne. larr) &
      call die("Array " // name // "(:) " // i2c(shape(arr)) // " must have the shape " // i2c([larr]))
  end subroutine size_check_1d
  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine size_check_2d(arr, larr, name)
    !! Check that the size of the array arr is of length larr
    use rotex__system,     only: die
    use rotex__characters, only: i2c => int2char
    implicit none
    class(*), intent(in) :: arr(:,:)
    integer, intent(in) :: larr(:)
    character(*), intent(in) :: name
    if(any(shape(arr) .ne. larr)) &
      call die("Array " // name // "(:,:) " // i2c(shape(arr)) // " must have the shape " // i2c(larr))
  end subroutine size_check_2d

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module function uniq(arr) result(res)
    !! Returns the unique elements of arr
    implicit none
    integer, intent(in)  :: arr(:)
    integer, allocatable :: res(:)
    integer :: n, i, k
    n = size(arr, 1)
    k = 0
    allocate(res(n))
    do i=1, n
      if(any(res(1:k) .eq. arr(i))) cycle
      k = k + 1
      res(k) = arr(i)
    enddo
    if(k .eq. 0) then
      call realloc(res, 0)
      return
    endif
    res = res(1:k)
  end function uniq

  ! ------------------------------------------------------------------------------------------------------------------------------ !
  pure module subroutine sort_index(vals, idx)
    !! Sort the array vals and return the permutation indices
    implicit none
    real(dp),    intent(in)  :: vals(:)
    integer,     intent(out) :: idx(:)
    integer :: i, j, n
    integer :: key
    real(dp) :: targ
    n = size(vals, 1)
    call size_check(idx, n, "IDX")
    do concurrent(i=1:n) ; idx(i) = i ; enddo
    if(n .le. 1) return
    do i = 2, n
      key = idx(i)
      j = i - 1
      targ = vals(key)
      do
        if(j .lt. 1) exit
        if(vals(idx(j)) .le. targ) exit
        idx(j+1) = idx(j)
        j = j - 1
      enddo
      idx(j+1) = key
    enddo
  end subroutine sort_index

! ================================================================================================================================ !
end module rotex__arrays
! ================================================================================================================================ !