stdlib_linalg_norms.fypp Source File


Source Code

#:include "common.fypp"
#:set ALL_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES

#! Allow for integer or character norm input: i.e., norm(a,2) or norm(a, '2')
#:set INPUT_TYPE    = ["character(len=*)","integer(ilp)"]
#:set INPUT_SHORT   = ["char","int"]
#:set INPUT_OPTIONS = list(zip(INPUT_TYPE,INPUT_SHORT))
! Vector norms
submodule(stdlib_linalg) stdlib_linalg_norms
     use stdlib_linalg_constants
     use stdlib_linalg_blas
     use stdlib_linalg_lapack
     use stdlib_linalg_state, only: linalg_state_type, linalg_error_handling, LINALG_ERROR, &
         LINALG_INTERNAL_ERROR, LINALG_VALUE_ERROR     
     use iso_c_binding, only: c_intptr_t,c_char,c_loc
     implicit none(type,external)
     
     character(*), parameter :: this = 'norm'
     
     !> List of internal norm flags
     integer(ilp), parameter :: NORM_ONE       = 1_ilp 
     integer(ilp), parameter :: NORM_TWO       = 2_ilp
     integer(ilp), parameter :: NORM_POW_FIRST = 3_ilp       
     integer(ilp), parameter :: NORM_INF       = +huge(0_ilp) ! infinity norm 
     integer(ilp), parameter :: NORM_POW_LAST  = NORM_INF - 1_ilp
     integer(ilp), parameter :: NORM_MINUSINF  = -huge(0_ilp)
     
     interface parse_norm_type
        module procedure parse_norm_type_integer
        module procedure parse_norm_type_character
     end interface parse_norm_type
     
     
     interface stride_1d
        #:for rk,rt,ri in ALL_KINDS_TYPES
        module procedure stride_1d_${ri}$
        #:endfor
     end interface stride_1d
     
     contains
     
     !> Parse norm type from an integer user input
     pure subroutine parse_norm_type_integer(order,norm_type,err)
        !> User input value
        integer(ilp), intent(in) :: order
        !> Return value: norm type
        integer(ilp), intent(out) :: norm_type
        !> State return flag
        type(linalg_state_type), intent(out) :: err
        
        select case (order)
           case (1_ilp)
               norm_type = NORM_ONE
           case (2_ilp)
               norm_type = NORM_TWO
           case (3_ilp:NORM_POW_LAST)
               norm_type = order
           case (NORM_INF:)
               norm_type = NORM_INF
           case (:NORM_MINUSINF)
               norm_type = NORM_MINUSINF
           
           case default
               norm_type = NORM_ONE
               err = linalg_state_type(this,LINALG_ERROR,'Input norm type ',order,' is not recognized.')
        end select    
        
     end subroutine parse_norm_type_integer

     pure subroutine parse_norm_type_character(order,norm_type,err)
        !> User input value
        character(len=*), intent(in) :: order
        !> Return value: norm type
        integer(ilp), intent(out) :: norm_type
        !> State return flag
        type(linalg_state_type), intent(out) :: err
        
        integer(ilp) :: int_order,read_err
        
        select case (order)
           case ('inf','Inf','INF')
              norm_type = NORM_INF
           case ('-inf','-Inf','-INF')
              norm_type = NORM_MINUSINF
           case ('Euclidean','euclidean','EUCLIDEAN')
              norm_type = NORM_TWO
           case default
            
              ! Check if this input can be read as an integer
              read(order,*,iostat=read_err) int_order
              if (read_err/=0) then 
                 ! Cannot read as an integer
                 norm_type = NORM_ONE
                 err = linalg_state_type(this,LINALG_ERROR,'Input norm type ',order,' is not recognized.')                 
              else
                 call parse_norm_type_integer(int_order,norm_type,err)
              endif  

        end select    
        
     end subroutine parse_norm_type_character

    #:for rk,rt,ri in ALL_KINDS_TYPES
    
    ! Compute stride of a 1d array
    pure integer(ilp) function stride_1d_${ri}$(a) result(stride)
        !> Input 1-d array 
        ${rt}$, intent(in), target :: a(:)
        
        integer(c_intptr_t) :: a1,a2
        
        if (size(a,kind=ilp)<=1_ilp) then 
           stride = 1_ilp
        else
           a1 = transfer(c_loc(a(1)),a1)
           a2 = transfer(c_loc(a(2)),a2)
           stride = bit_size(0_c_char)*int(a2-a1, ilp)/storage_size(a, kind=ilp)
        endif
        
    end function stride_1d_${ri}$
    
    ! Private internal 1D implementation. This has to be used only internally, 
    ! when all inputs are already checked for correctness.
    pure subroutine internal_norm_1D_${ri}$(sze, a, nrm, norm_request)
        !> Input matrix length
        integer(ilp), intent(in) :: sze
        !> Input contiguous 1-d matrix a(*)
        ${rt}$, intent(in) :: a(sze)
        !> Norm of the matrix.
        real(${rk}$), intent(out) :: nrm
        !> Internal matrix request 
        integer(ilp), intent(in) :: norm_request
        
        integer(ilp) :: i
        real(${rk}$) :: rorder
        intrinsic :: abs, sum, sqrt, maxval, minval, conjg
        
        ! Initialize norm to zero
        nrm = 0.0_${rk}$
        
        select case(norm_request)
            case(NORM_ONE)
                nrm = asum(sze,a,incx=1_ilp)
            case(NORM_TWO)            
                nrm = nrm2(sze,a,incx=1_ilp)
            case(NORM_INF)
                #:if rt.startswith('complex')
                ! The default BLAS stdlib_i${ri}$amax uses |Re(.)|+|Im(.)|. Do not use it.
                i = stdlib_i${ri}$max1(sze,a,incx=1_ilp)
                #:else
                i = stdlib_i${ri}$amax(sze,a,incx=1_ilp)
                #:endif                     
                nrm = abs(a(i))
            case(NORM_MINUSINF)
                nrm = minval( abs(a) )
            case (NORM_POW_FIRST:NORM_POW_LAST)
                rorder = 1.0_${rk}$ / norm_request
                nrm = sum( abs(a) ** norm_request ) ** rorder
        end select
        
    end subroutine internal_norm_1D_${ri}$     
    
    #:for it,ii in INPUT_OPTIONS

    !==============================================
    ! Norms : any rank to scalar
    !==============================================

    #:for rank in range(1, MAXRANK + 1)

    ! Pure function interface, with order specification. On error, the code will stop
    pure module function stdlib_linalg_norm_${rank}$D_order_${ii}$_${ri}$(a, order) result(nrm)
        !> Input ${rank}$-d matrix a${ranksuffix(rank)}$
        ${rt}$, intent(in) :: a${ranksuffix(rank)}$
        !> Order of the matrix norm being computed.
        ${it}$, intent(in) :: order
        !> Norm of the matrix.
        real(${rk}$) :: nrm
                                    
        call norm_${rank}$D_${ii}$_${ri}$(a, nrm=nrm, order=order)
        
    end function stdlib_linalg_norm_${rank}$D_order_${ii}$_${ri}$
    
    ! Function interface with output error
    module function stdlib_linalg_norm_${rank}$D_order_err_${ii}$_${ri}$(a, order, err) result(nrm)
        !> Input ${rank}$-d matrix a${ranksuffix(rank)}$
        ${rt}$, intent(in) :: a${ranksuffix(rank)}$
        !> Order of the matrix norm being computed.
        ${it}$, intent(in) :: order
        !> Output state return flag. 
        type(linalg_state_type), intent(out) :: err                         
        !> Norm of the matrix.
        real(${rk}$) :: nrm                 
                
        call norm_${rank}$D_${ii}$_${ri}$(a, nrm=nrm, order=order, err=err)
        
    end function stdlib_linalg_norm_${rank}$D_order_err_${ii}$_${ri}$

    ! Internal implementation: ${rank}$-d    
    pure module subroutine norm_${rank}$D_${ii}$_${ri}$(a, nrm, order, err)
        !> Input ${rank}$-d matrix a${ranksuffix(rank)}$
        ${rt}$, intent(in), target :: a${ranksuffix(rank)}$
        !> Norm of the matrix.
        real(${rk}$), intent(out) :: nrm
        !> Order of the matrix norm being computed.
        ${it}$, intent(in) :: order
        !> [optional] state return flag. On error if not requested, the code will stop
        type(linalg_state_type), intent(out), optional :: err         
        
        type(linalg_state_type) :: err_
        integer(ilp) :: sze,norm_request
        real(${rk}$) :: rorder
        intrinsic :: abs, sum, sqrt, maxval, minval, conjg
        
        sze = size(a,kind=ilp)
        
        ! Initialize norm to zero
        nrm = 0.0_${rk}$
        
        ! Check matrix size
        if (sze<=0) then
            err_ = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid matrix shape: a=',shape(a,kind=ilp))
            call linalg_error_handling(err_,err)
            return
        end if
        
        ! Check norm request
        call parse_norm_type(order,norm_request,err_)
        if (err_%error()) then 
            call linalg_error_handling(err_,err)
            return
        endif         
        
        ! Get norm
        call internal_norm_1D_${ri}$(sze, a, nrm, norm_request)
        call linalg_error_handling(err_,err)
        
    end subroutine norm_${rank}$D_${ii}$_${ri}$

    #:endfor

    !====================================================================
    ! Norms : any rank to rank-1, with DIM specifier and ${ii}$ input
    !====================================================================

    #:for rank in range(2, MAXRANK + 1)
    
    ! Pure function interface with DIM specifier. On error, the code will stop
    pure module function stdlib_linalg_norm_${rank}$D_to_${rank-1}$D_${ii}$_${ri}$(a, order, dim) result(nrm)
        !> Input matrix a[..]
        ${rt}$, intent(in), target :: a${ranksuffix(rank)}$
        !> Order of the matrix norm being computed.
        ${it}$, intent(in) :: order
        !> Dimension to collapse by computing the norm w.r.t other dimensions
        integer(ilp), intent(in) :: dim
        !> Norm of the matrix.
        real(${rk}$) :: nrm${reduced_shape('a', rank, 'dim')}$     
        
        call norm_${rank}$D_to_${rank-1}$D_${ii}$_${ri}$(a, nrm, order, dim)
            
    end function stdlib_linalg_norm_${rank}$D_to_${rank-1}$D_${ii}$_${ri}$

    ! Function interface with DIM specifier and output error state.
    module function stdlib_linalg_norm_${rank}$D_to_${rank-1}$D_err_${ii}$_${ri}$(a, order, dim, err) result(nrm)
        !> Input matrix a[..]
        ${rt}$, intent(in), target :: a${ranksuffix(rank)}$
        !> Order of the matrix norm being computed.
        ${it}$, intent(in) :: order
        !> Dimension to collapse by computing the norm w.r.t other dimensions
        integer(ilp), intent(in) :: dim
        !> Output state return flag. 
        type(linalg_state_type), intent(out) :: err                                 
        !> Norm of the matrix.
        real(${rk}$) :: nrm${reduced_shape('a', rank, 'dim')}$     
        
        call norm_${rank}$D_to_${rank-1}$D_${ii}$_${ri}$(a, nrm, order, dim, err)
            
    end function stdlib_linalg_norm_${rank}$D_to_${rank-1}$D_err_${ii}$_${ri}$
    
    ! Internal implementation 
    pure module subroutine norm_${rank}$D_to_${rank-1}$D_${ii}$_${ri}$(a, nrm, order, dim, err)
        !> Input matrix a[..]
        ${rt}$, intent(in) :: a${ranksuffix(rank)}$
        !> Dimension to collapse by computing the norm w.r.t other dimensions
        !  (dim must be defined before it is used for `nrm`)
        integer(ilp), intent(in) :: dim        
        !> Norm of the matrix.        
        real(${rk}$), intent(out) :: nrm${reduced_shape('a', rank, 'dim')}$     
        !> Order of the matrix norm being computed.
        ${it}$, intent(in) :: order
        !> [optional] state return flag. On error if not requested, the code will stop
        type(linalg_state_type), intent(out), optional :: err           
        
        type(linalg_state_type) :: err_
        integer(ilp) :: sze,lda,norm_request,${loop_variables('j',rank-1,1)}$
        logical :: contiguous_data
        real(${rk}$) :: rorder
        integer(ilp), dimension(${rank}$) :: spe,spack,perm,iperm
        integer(ilp), dimension(${rank}$), parameter :: dim_range = [(lda,lda=1_ilp,${rank}$_ilp)]
        ${rt}$, allocatable :: apack${ranksuffix(rank)}$
        intrinsic :: abs, sum, sqrt, norm2, maxval, minval, conjg
        
        ! Input matrix properties
        sze = size (a,kind=ilp)
        spe = shape(a,kind=ilp)
        
        ! Initialize norm to zero
        nrm = 0.0_${rk}$
        
        if (sze<=0) then
            err_ = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid matrix shape: a=',shape(a,kind=ilp))
            call linalg_error_handling(err_,err)
            return
        end if

        ! Check dimension choice
        if (dim<1 .or. dim>${rank}$) then
            err_ = linalg_state_type(this,LINALG_VALUE_ERROR,'dimension ',dim, &
                                'is out of rank for shape(a)=',shape(a,kind=ilp))
            call linalg_error_handling(err_,err)
            return
        end if
        
        ! Check norm request
        call parse_norm_type(order,norm_request,err_)
        if (err_%error()) then 
            call linalg_error_handling(err_,err)
            return
        endif     
        
        ! The norm's leading dimension
        lda = spe(dim)    
        
        ! Check if input column data is contiguous
        contiguous_data = dim==1
        
        ! Get packed data with the norm dimension as the first dimension
        if (.not.contiguous_data) then 
            
            ! Permute array to map dim to 1
            perm = [dim,pack(dim_range,dim_range/=dim)]
            iperm(perm) = dim_range            
            spack = spe(perm)            
            apack = reshape(a, shape=spack, order=iperm)                 
            
${loop_variables_start('j', 'apack', rank-1, 1," "*12)}$
                call internal_norm_1D_${ri}$(lda, apack(:, ${loop_variables('j',rank-1,1)}$), &
                    nrm(${loop_variables('j',rank-1,1)}$), norm_request)
${loop_variables_end(rank-1," "*12)}$            
            
        else
            
${loop_variables_start('j', 'a', rank-1, 1," "*12)}$
                call internal_norm_1D_${ri}$(lda, a(:, ${loop_variables('j',rank-1,1)}$), &
                    nrm(${loop_variables('j',rank-1,1)}$), norm_request)
${loop_variables_end(rank-1," "*12)}$               
            
        endif        
        
    end subroutine norm_${rank}$D_to_${rank-1}$D_${ii}$_${ri}$

    #:endfor
    #:endfor
    #:endfor

end submodule stdlib_linalg_norms