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)
          
     !> Wrappers to LAPACK *LANGE matrix norm flags
     character, parameter :: MAT_NORM_MAT = 'M' ! maxval(sum(abs(a)))   ! over whole matrix: unused
     character, parameter :: MAT_NORM_ONE = '1' ! maxval(sum(abs(a),1)) ! over columns
     character, parameter :: MAT_NORM_INF = 'I' ! maxval(sum(abs(a),2)) ! over rows
     character, parameter :: MAT_NORM_FRO = 'E' ! sqrt(sum(a**2))       ! "Euclidean" or "Frobenius"     
     !> Other wrappers to matrix norms
     character, parameter :: MAT_NORM_SVD = '2' ! maxval(svdvals(a))    ! Maximum singular value
     
     interface parse_norm_type
        module procedure parse_norm_type_integer
        module procedure parse_norm_type_character
     end interface parse_norm_type
     
     interface mat_task_request
        module procedure mat_task_request_integer
        module procedure mat_task_request_character
     end interface mat_task_request
     
     
     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

     !> From a user norm request, generate a *LANGE task command
     pure subroutine mat_task_request_integer(order,mat_task,err)
        !> Parsed matrix norm type
        integer(ilp), optional, intent(in) :: order
        !> LANGE task
        character, intent(out) :: mat_task
        !> Error flag
        type(linalg_state_type), intent(inout) :: err
        
        if (present(order)) then 
        
            select case (order)
               case (NORM_INF)
                  mat_task = MAT_NORM_INF 
               case (NORM_TWO)
                  mat_task = MAT_NORM_SVD 
               case (NORM_ONE)
                  mat_task = MAT_NORM_ONE
               case default 
                  err = linalg_state_type(this,LINALG_VALUE_ERROR,'Integer order ',order,' is not a valid matrix norm input.')
            end select
            
        else
            
            ! No user input: Frobenius norm
            mat_task = MAT_NORM_FRO
            
        endif
     end subroutine mat_task_request_integer

     pure subroutine mat_task_request_character(order,mat_task,err)
        !> User input value
        character(len=*), intent(in) :: order
        !> Return value: norm type
        character, intent(out) :: mat_task
        !> State return flag
        type(linalg_state_type), intent(out) :: err
        
        integer(ilp) :: int_order,read_err
        
        select case (order)
           case ('inf','Inf','INF')
              mat_task = MAT_NORM_INF
           case ('Euclidean','euclidean','EUCLIDEAN','Frobenius','frobenius','FROBENIUS','Fro','fro','frob')
              mat_task = MAT_NORM_FRO
           case default
            
              ! Check if this input can be read as an integer
              read(order,*,iostat=read_err) int_order
              if (read_err/=0 .or. all(int_order/=[1,2])) then 
                 ! Cannot read as an integer                 
                 err = linalg_state_type(this,LINALG_ERROR,'Matrix norm input',order,' is not recognized.')                 
              endif
              
              select case (int_order)
                 case (1);     mat_task = MAT_NORM_ONE
                 case (2);     mat_task = MAT_NORM_SVD
                 case default; mat_task = MAT_NORM_ONE   
              end select
              
        end select    
        
     end subroutine mat_task_request_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
    
    !====================================================================
    ! Matrix norms
    !====================================================================    
    
    ! Internal implementation 
    module function matrix_norm_${ii}$_${ri}$(a, order, err) result(nrm)
        !> Input matrix a(m,n)
        ${rt}$, intent(in), target :: a(:,:)
        !> Norm of the matrix.        
        real(${rk}$) :: nrm
        !> Order of the matrix norm being computed.
        ${it}$, #{if 'integer' in it}#optional, #{endif}#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) :: m,n
        character :: mat_task
        real(${rk}$), target :: work1(1)
        real(${rk}$), pointer :: work(:)        
        
        m = size(a,dim=1,kind=ilp)
        n = size(a,dim=2,kind=ilp)
        
        ! Initialize norm to zero
        nrm = 0.0_${rk}$
        
        if (m<=0 .or. n<=0) then
            err_ = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid matrix shape: a=',[m,n])
            call linalg_error_handling(err_,err)
            return
        end if

        ! Check norm request: user + *LANGE support
        call mat_task_request(order,mat_task,err_)
        if (err_%error()) then 
            call linalg_error_handling(err_,err)
            return
        endif       
        
        if (mat_task==MAT_NORM_INF) then 
            allocate(work(m))
        else
            work => work1
        end if
                
        if (mat_task==MAT_NORM_SVD) then 
           nrm = maxval(svdvals(a,err_),1) 
           call linalg_error_handling(err_,err)
        else
           ! LAPACK interface 
           nrm = lange(mat_task,m,n,a,m,work)
        end if
        
        if (mat_task==MAT_NORM_INF) deallocate(work)
        
    end function matrix_norm_${ii}$_${ri}$
    
    #:for rank in range(3, MAXRANK + 1)
    
    ! Pure function interface with DIM specifier. On error, the code will stop
    module function matrix_norm_${rank}$D_to_${rank-2}$D_${ii}$_${ri}$(a, order, dim, err) result(nrm)
        !> Input matrix a(m,n)
        ${rt}$, intent(in), contiguous, target :: a${ranksuffix(rank)}$
        !> Norm of the matrix.        
        real(${rk}$), allocatable :: nrm${ranksuffix(rank-2)}$
        !> Order of the matrix norm being computed.
        ${it}$, #{if 'integer' in it}#optional, #{endif}#intent(in) :: order
        !> [optional] dimensions of the sub-matrices the norms should be evaluated at (default = [1,2])
        integer(ilp), optional, intent(in) :: dim(2)
        !> [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) :: j,m,n,lda,dims(2),norm_request,svd_errors
        integer(ilp), dimension(${rank}$) :: s,spack,perm,iperm
        integer(ilp), dimension(${rank}$), parameter :: dim_range = [(m,m=1_ilp,${rank}$_ilp)]
        integer(ilp) :: ${loop_variables('j',rank-2,2)}$
        logical :: contiguous_data
        character :: mat_task
        real(${rk}$), target :: work1(1)
        real(${rk}$), pointer :: work(:)        
        ${rt}$, pointer :: apack${ranksuffix(rank)}$
        
        ! Get dimensions
        if (present(dim)) then 
           dims = dim 
        else
           dims = [1,2]
        endif
        
        nullify(apack)
        svd_errors = 0

        if (dims(1)==dims(2) .or. .not.all(dims>0 .and. dims<=${rank}$)) then 
            err_ = linalg_state_type(this,LINALG_VALUE_ERROR,'Rank-',${rank}$,' matrix norm has invalid dim=',dims)
            allocate(nrm${emptyranksuffix(rank-2)}$)
            call linalg_error_handling(err_,err)
            return
        endif
        
        ! Check norm request: user + *LANGE support
        call mat_task_request(order,mat_task,err_)
        if (err_%error()) then 
            allocate(nrm${emptyranksuffix(rank-2)}$)
            call linalg_error_handling(err_,err)
            return
        endif             
        
        ! Input matrix properties
        s = shape(a,kind=ilp)
        
        ! Check if input column data is contiguous
        contiguous_data = all(dims==[1,2])
        
        ! Matrix norm size
        m = s(dims(1))
        n = s(dims(2))

        if (m<=0 .or. n<=0) then
            err_ = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid matrix shape: a=',[m,n])
            allocate(nrm${emptyranksuffix(rank-2)}$)
            call linalg_error_handling(err_,err)
            return
        end if

        ! Get packed data with norm dimensions as 1:2
        if (contiguous_data) then 
            
            ! Reshape without moving data
            spack =  s
            apack => a
            
        else
            
            ! Dimension permutations to map dims(1),dims(2) => 1:2
            perm = [dims,pack(dim_range, dim_range/=dims(1) .and. dim_range/=dims(2))]            
            iperm(perm) = dim_range            
            spack = s(perm)            
            allocate(apack,source=reshape(a, shape=spack, order=iperm))
            
        endif
            
        if (mat_task==MAT_NORM_INF) then 
            allocate(work(m))
        elseif (mat_task==MAT_NORM_SVD) then 
            allocate(work(min(m,n)))
        else
            work => work1
        endif
        
        ! Allocate norm        
        allocate(nrm${shape_from_array_size('apack',rank-2, 2)}$)
        
        lda = size(apack,dim=1,kind=ilp)
        
        ! LAPACK interface
${loop_variables_start('j', 'apack', rank-2, 2)}$
        if (mat_task==MAT_NORM_SVD) then
            work(:) = svdvals(apack(:,:,${loop_variables('j',rank-2,2)}$),err_)
            nrm(${loop_variables('j',rank-2,2)}$) = maxval(work,1)
            if (err_%error()) svd_errors = svd_errors+1
        else
            nrm(${loop_variables('j',rank-2,2)}$) = &
            lange(mat_task,m,n,apack(:,:,${loop_variables('j',rank-2,2)}$),lda,work)            
        end if
${loop_variables_end(rank-2)}$
        
        if (any(mat_task==[MAT_NORM_INF,MAT_NORM_SVD])) deallocate(work)
        if (.not.contiguous_data) deallocate(apack)
        
        if (svd_errors>0) then 
            err_ = linalg_state_type(this,LINALG_VALUE_ERROR,svd_errors,'failed SVDs')
            call linalg_error_handling(err_,err)
        endif             
                
    end function matrix_norm_${rank}$D_to_${rank-2}$D_${ii}$_${ri}$    
    
    #:endfor
    
    #:endfor
    #:endfor

end submodule stdlib_linalg_norms