stdlib_linalg_state.fypp Source File


Source Code

#:include "common.fypp"
#:set KINDS_TYPES = REAL_KINDS_TYPES + INT_KINDS_TYPES + CMPLX_KINDS_TYPES
module stdlib_linalg_state
     !! Version: experimental
     !!
     !! Provides a state/error handling derived type for advanced error handling of
     !! BLAS/LAPACK based linear algebra procedures. All procedures are pure.
     !! ([Specification](../page/specs/stdlib_linalg.html))
     use stdlib_linalg_constants,only: ilp
     use stdlib_kinds, only: int8, int16, int32, int64, sp, dp, xdp, qp, lk
     use stdlib_io, only: FMT_REAL_SP, FMT_REAL_DP, FMT_REAL_QP, FMT_COMPLEX_SP, FMT_COMPLEX_DP, &
         FMT_COMPLEX_QP, FMT_REAL_XDP, FMT_COMPLEX_XDP
     implicit none(type,external)
     private

     !> Version: experimental
     !>
     !> A fixed-storage state variable for error handling of linear algebra routines
     public :: linalg_state_type

     !> Version: experimental
     !>
     !> Error state handling: if the user requested the error state variable on
     !> output, just return it to the user. Otherwise, halt the program on error.
     public :: linalg_error_handling

     !> Version: experimental
     !>
     !> Interfaces for comparison operators of error states with integer flags
     public :: operator(==),operator(/=)
     public :: operator(<),operator(<=)
     public :: operator(>),operator(>=)

     !> State return types
     integer(ilp),parameter,public :: LINALG_SUCCESS        = 0_ilp
     integer(ilp),parameter,public :: LINALG_VALUE_ERROR    = -1_ilp
     integer(ilp),parameter,public :: LINALG_ERROR          = -2_ilp
     integer(ilp),parameter,public :: LINALG_INTERNAL_ERROR = -3_ilp

     !> Use fixed-size character storage for performance
     integer(ilp),parameter :: MSG_LENGTH = 512_ilp
     integer(ilp),parameter :: NAME_LENGTH = 32_ilp

     !> `linalg_state_type` defines a state return type for a
     !> linear algebra routine. State contains a status flag, a comment, and a
     !> procedure specifier that can be used to mark where the error happened
     type :: linalg_state_type

         !> The current exit state
         integer(ilp) :: state = LINALG_SUCCESS

         !> Message associated to the current state
         character(len=MSG_LENGTH) :: message = repeat(' ',MSG_LENGTH)

         !> Location of the state change
         character(len=NAME_LENGTH) :: where_at = repeat(' ',NAME_LENGTH)

         contains

            !> Cleanup
            procedure :: destroy   => state_destroy

            !> Print error message
            procedure :: print     => state_print
            procedure :: print_msg => state_message

            !> State properties
            procedure :: ok        => state_is_ok
            procedure :: error     => state_is_error

     end type linalg_state_type

     !> Comparison operators
     interface operator(==)
         module procedure state_eq_flag
         module procedure flag_eq_state
     end interface
     interface operator(/=)
         module procedure state_neq_flag
         module procedure flag_neq_state
     end interface
     interface operator(<)
         module procedure state_lt_flag
         module procedure flag_lt_state
     end interface
     interface operator(<=)
         module procedure state_le_flag
         module procedure flag_le_state
     end interface
     interface operator(>)
         module procedure state_gt_flag
         module procedure flag_gt_state
     end interface
     interface operator(>=)
         module procedure state_ge_flag
         module procedure flag_ge_state
     end interface

     interface linalg_state_type
         module procedure new_state
         module procedure new_state_nowhere
     end interface linalg_state_type

     contains

     !> Interface to print linalg state flags
     pure function linalg_message(flag) result(msg)
        integer(ilp),intent(in) :: flag
        character(len=:),allocatable :: msg

        select case (flag)
           case (LINALG_SUCCESS);        msg = 'Success!'
           case (LINALG_VALUE_ERROR);    msg = 'Value Error'
           case (LINALG_ERROR);          msg = 'Algebra Error'
           case (LINALG_INTERNAL_ERROR); msg = 'Internal Error'
           case default;                 msg = 'ERROR/INVALID FLAG'
        end select

     end function linalg_message

     !> Flow control: on output flag present, return it; otherwise, halt on error
     pure subroutine linalg_error_handling(ierr,ierr_out)
         type(linalg_state_type),intent(in) :: ierr
         type(linalg_state_type),optional,intent(out) :: ierr_out

         character(len=:),allocatable :: err_msg

         if (present(ierr_out)) then
             ! Return error flag
             ierr_out = ierr
         elseif (ierr%error()) then
             err_msg = ierr%print()
             error stop err_msg
         end if

     end subroutine linalg_error_handling

     !> Formatted message
     pure function state_message(this) result(msg)
         class(linalg_state_type),intent(in) :: this
         character(len=:),allocatable :: msg

         if (this%state == LINALG_SUCCESS) then
            msg = 'Success!'
         else
            msg = linalg_message(this%state)//': '//trim(this%message)
         end if

     end function state_message

     !> Produce a nice error string
     pure function state_print(this) result(msg)
         class(linalg_state_type),intent(in) :: this
         character(len=:),allocatable :: msg

         if (len_trim(this%where_at) > 0) then
            msg = '['//trim(this%where_at)//'] returned '//state_message(this)
         elseif (this%error()) then
            msg = 'Error encountered: '//state_message(this)
         else
            msg = state_message(this)
         end if

     end function state_print

     !> Cleanup the object
     elemental subroutine state_destroy(this)
        class(linalg_state_type),intent(inout) :: this

        this%state = LINALG_SUCCESS
        this%message = repeat(' ',len(this%message))
        this%where_at = repeat(' ',len(this%where_at))

     end subroutine state_destroy

     !> Check if the current state is successful
     elemental logical(lk) function state_is_ok(this)
        class(linalg_state_type),intent(in) :: this
        state_is_ok = this%state == LINALG_SUCCESS
     end function state_is_ok

     !> Check if the current state is an error state
     elemental logical(lk) function state_is_error(this)
        class(linalg_state_type),intent(in) :: this
        state_is_error = this%state /= LINALG_SUCCESS
     end function state_is_error

     !> Compare an error state with an integer flag
     elemental logical(lk) function state_eq_flag(err,flag)
        type(linalg_state_type),intent(in) :: err
        integer,intent(in) :: flag
        state_eq_flag = err%state == flag
     end function state_eq_flag

     !> Compare an integer flag with the error state
     elemental logical(lk) function flag_eq_state(flag,err)
        integer,intent(in) :: flag
        type(linalg_state_type),intent(in) :: err
        flag_eq_state = err%state == flag
     end function flag_eq_state

     !> Compare the error state with an integer flag
     elemental logical(lk) function state_neq_flag(err,flag)
        type(linalg_state_type),intent(in) :: err
        integer,intent(in) :: flag
        state_neq_flag = .not. state_eq_flag(err,flag)
     end function state_neq_flag

     !> Compare an integer flag with the error state
     elemental logical(lk) function flag_neq_state(flag,err)
        integer,intent(in) :: flag
        type(linalg_state_type),intent(in) :: err
        flag_neq_state = .not. state_eq_flag(err,flag)
     end function flag_neq_state

     !> Compare the error state with an integer flag
     elemental logical(lk) function state_lt_flag(err,flag)
        type(linalg_state_type),intent(in) :: err
        integer,intent(in) :: flag
        state_lt_flag = err%state < flag
     end function state_lt_flag

     !> Compare the error state with an integer flag
     elemental logical(lk) function state_le_flag(err,flag)
        type(linalg_state_type),intent(in) :: err
        integer,intent(in) :: flag
        state_le_flag = err%state <= flag
     end function state_le_flag

     !> Compare an integer flag with the error state
     elemental logical(lk) function flag_lt_state(flag,err)
        integer,intent(in) :: flag
        type(linalg_state_type),intent(in) :: err
        flag_lt_state = err%state < flag
     end function flag_lt_state

     !> Compare an integer flag with the error state
     elemental logical(lk) function flag_le_state(flag,err)
        integer,intent(in) :: flag
        type(linalg_state_type),intent(in) :: err
        flag_le_state = err%state <= flag
     end function flag_le_state

     !> Compare the error state with an integer flag
     elemental logical(lk) function state_gt_flag(err,flag)
        type(linalg_state_type),intent(in) :: err
        integer,intent(in) :: flag
        state_gt_flag = err%state > flag
     end function state_gt_flag

     !> Compare the error state with an integer flag
     elemental logical(lk) function state_ge_flag(err,flag)
        type(linalg_state_type),intent(in) :: err
        integer,intent(in) :: flag
        state_ge_flag = err%state >= flag
     end function state_ge_flag

     !> Compare an integer flag with the error state
     elemental logical(lk) function flag_gt_state(flag,err)
        integer,intent(in) :: flag
        type(linalg_state_type),intent(in) :: err
        flag_gt_state = err%state > flag
     end function flag_gt_state

     !> Compare an integer flag with the error state
     elemental logical(lk) function flag_ge_state(flag,err)
        integer,intent(in) :: flag
        type(linalg_state_type),intent(in) :: err
        flag_ge_state = err%state >= flag
     end function flag_ge_state

    !> Error creation message, with location location
    pure type(linalg_state_type) function new_state(where_at,flag,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10, &
                                                    a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) 

       !> Location
       character(len=*),intent(in) :: where_at

       !> Input error flag
       integer,intent(in) :: flag

       !> Optional rank-agnostic arguments
       class(*),optional,intent(in),dimension(..) :: a1,a2,a3,a4,a5,a6,a7,a8,a9,a10, &
                                                     a11,a12,a13,a14,a15,a16,a17,a18,a19,a20  

       !> Create state with no message
       new_state = new_state_nowhere(flag,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10, &
                                     a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)

       !> Add location
       if (len_trim(where_at) > 0) new_state%where_at = adjustl(where_at)

    end function new_state

    !> Error creation message, from N input variables (numeric or strings)
    pure type(linalg_state_type) function new_state_nowhere(flag,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10, &
                                                            a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) &
                                                            result(new_state)

       !> Input error flag
       integer,intent(in) :: flag

       !> Optional rank-agnostic arguments
       class(*),optional,intent(in),dimension(..) :: a1,a2,a3,a4,a5,a6,a7,a8,a9,a10, &
                                                     a11,a12,a13,a14,a15,a16,a17,a18,a19,a20  

       ! Init object
       call new_state%destroy()

       !> Set error flag
       new_state%state = flag

       !> Set chain
       new_state%message = ""
       call appendr(new_state%message,a1)
       call appendr(new_state%message,a2)
       call appendr(new_state%message,a3)
       call appendr(new_state%message,a4)
       call appendr(new_state%message,a5)
       call appendr(new_state%message,a6)
       call appendr(new_state%message,a7)
       call appendr(new_state%message,a8)
       call appendr(new_state%message,a9)
       call appendr(new_state%message,a10)
       call appendr(new_state%message,a11)
       call appendr(new_state%message,a12)
       call appendr(new_state%message,a13)
       call appendr(new_state%message,a14)
       call appendr(new_state%message,a15)
       call appendr(new_state%message,a16)
       call appendr(new_state%message,a17)
       call appendr(new_state%message,a18)
       call appendr(new_state%message,a19)
       call appendr(new_state%message,a20)

    end function new_state_nowhere

    !> Append a generic value to the error flag (rank-agnostic)
    pure subroutine appendr(msg,a,prefix)
       class(*),optional,intent(in) :: a(..)
       character(len=*),intent(inout) :: msg
       character,optional,intent(in) :: prefix
       
       if (present(a)) then 
          select rank (v=>a)
             rank (0)
                call append (msg,v,prefix)
             rank (1)
                call appendv(msg,v)
             rank default
                msg = trim(msg)//' <ERROR: INVALID RANK>'
          
          end select   
       endif
       
    end subroutine appendr

    ! Append a generic value to the error flag
    pure subroutine append(msg,a,prefix)
       class(*),intent(in) :: a
       character(len=*),intent(inout) :: msg
       character,optional,intent(in) :: prefix

       character(len=MSG_LENGTH) :: buffer,buffer2
       character(len=2) :: sep
       integer :: ls

       ! Do not add separator if this is the first instance
       sep = '  '
       ls = merge(1,0,len_trim(msg) > 0)

       if (present(prefix)) then
           ls = ls + 1
           sep(ls:ls) = prefix
       end if

       select type (aa => a)

        !> String type
        type is (character(len=*))
            msg = trim(msg)//sep(:ls)//aa

        !> Numeric types
#:for k1, t1 in KINDS_TYPES
        type is (${t1}$)
        #:if 'complex' in t1
            write (buffer, FMT_REAL_${k1}$) aa%re
            write (buffer2,FMT_REAL_${k1}$) aa%im
            msg = trim(msg)//sep(:ls)//'('//trim(adjustl(buffer))//','//trim(adjustl(buffer2))//')'
        #:else
            #:if 'real' in t1
            write (buffer,FMT_REAL_${k1}$) aa
            #:else
            write (buffer,'(i0)') aa
            #:endif
            msg = trim(msg)//sep(:ls)//trim(adjustl(buffer))
        #:endif

#:endfor
        class default
            msg = trim(msg)//' <ERROR: INVALID TYPE>'

       end select

    end subroutine append

    !> Append a generic vector to the error flag
    pure subroutine appendv(msg,a)
       class(*),intent(in) :: a(:)
       character(len=*),intent(inout) :: msg

       integer :: j,ls
       character(len=MSG_LENGTH) :: buffer,buffer2,buffer_format
       character(len=2) :: sep

       if (size(a) <= 0) return

       ! Default: separate elements with one space
       sep = '  '
       ls = 1

       ! Open bracket
       msg = trim(msg)//' ['

       ! Do not call append(msg(aa(j))), it will crash gfortran
       select type (aa => a)

        !> Strings (cannot use string_type due to `sequence`)
        type is (character(len=*))
            msg = trim(msg)//adjustl(aa(1))
            do j = 2,size(a)
               msg = trim(msg)//sep(:ls)//adjustl(aa(j))
            end do

        !> Numeric types
#:for k1, t1 in KINDS_TYPES
        type is (${t1}$)
        #:if 'complex' in t1
            write (buffer,FMT_REAL_${k1}$)  aa(1)%re
            write (buffer2,FMT_REAL_${k1}$) aa(1)%im
            msg = trim(msg)//'('//trim(adjustl(buffer))//','//trim(adjustl(buffer2))//')'
            do j = 2,size(a)
                write (buffer,FMT_REAL_${k1}$)  aa(j)%re
                write (buffer2,FMT_REAL_${k1}$) aa(j)%im
                msg = trim(msg)//sep(:ls)//'('//trim(adjustl(buffer))//','//trim(adjustl(buffer2))//')'
            end do
        #:else
            #:if 'real' in t1
            buffer_format = FMT_REAL_${k1}$
            #:else
            buffer_format = '(i0)'
            #:endif

            write (buffer,buffer_format) aa(1)
            msg = trim(msg)//adjustl(buffer)
            do j = 2,size(a)
               write (buffer,buffer_format) aa(j)
               msg = trim(msg)//sep(:ls)//adjustl(buffer)
            end do
            msg = trim(msg)//sep(:ls)//trim(adjustl(buffer))
        #:endif
#:endfor
        class default
            msg = trim(msg)//' <ERROR: INVALID TYPE>'

       end select

       ! Close bracket
       msg = trim(msg)//']'

    end subroutine appendv

end module stdlib_linalg_state