suprls Subroutine

private subroutine suprls(me, i, rowi, n, bi, a, nn, soln, err, ier)

To determine the least squares solution of a large overdetermined linear system.

Given the m by n matrix r (m >= n) and the m-vector b, this routine calculates the n-vector x such that the euclidean norm of the residue (r*x-b) is minimized. the subroutine accepts rows of the matrix one by one so that the entire matrix need not be stored at one time. this allows large problems to be solved without peripheral storage. the length of the rows is limited by the amount of scratch storage which can be set aside for use by the routine. there is no restriction on the number of rows.

Usage

suprls is called once for each row of the matrix. A final call returns the solution vector and the euclidean norm of the residual. This following sequence would process the m by n matrix r and the right hand side m-vector b

  do i = 1,m
    do j = 1,n
      ! here set rowi(j) to the (i,j) element of r
    end do
    ! here set bi to the ith component of b.
    call suprls(i,rowi,n,bi,a,nn,soln,err,ier)
  end do
  call suprls (0,rowi,n,bi,a,nn,soln,err,ier)

Algorithm

given the m by n matrix r (m>=n) and the m-vector b, we wish to find an n-vector x such that

      e = l2-norm of  r*x-b

is minimized. since the euclidean norm is invariant under orthogonal transformation, r and b may be premultiplied by any orthogonal matrix without changing the norm of the residual (r*x-b). r is reduced to upper triangular form by premultiplying r and b by a sequence of householder and rotation matrices. when the reduction is complete, the norm of the residual takes the form

    e =  l2 norm(t*x-b(n))+l2 norm(b(m-n))

where t is an n by n upper triangular matrix, b(n) is a vector of the first n components of b, b(m-n) is a vector of the remaining (m-n) components of b. e is minimized by taking x to be the solution of the system t*x=b(n). this triangular system is therefore solved to give the required least squares solution. the norm of the residual is then the l2-norm of b(m-n).

at each phase of the reduction, as many rows as space permits are entered into the scratch area. householder transformations are then used to zero out subdiagonal elements. space is saved by eliminating storage for the zero subdiagonal terms. if there is room for only one new row, rotation rather than householder matrices are used for greater speed. when all m rows have been entered, reduction is completed and the triangular system solved.

Reference

  • Hanson, R.J., and Lawson, C.L., "Extensions and applications of the householder algorithm for solving linear least squares problems". Math. of comp. vol.23, pp. 787-812. (1969)

Accuracy

This will depend upon the size and condition of the matrix. Near machine accuracy may be expected for well conditioned systems of moderate size. If ill conditioning is suspect, a version using pivoting may be necessary.

History

  • Original FORTRAN 66 version written in May 1972 by A.K. Cline of NCAR'S Scientific Computing Division.

Type Bound

splpak_type

Arguments

Type IntentOptional Attributes Name
class(splpak_type), intent(inout) :: me
integer, intent(in) :: i

the index of the row being entered. (i is 1 for the first call, increases by 1 for each call, and is m when the final row is entered). After the final row has been entered, suprls is called with i = 0 to complete the reduction and solution.

real(kind=wp), intent(in) :: rowi(n)

a vector which on the ith call contains the n components of the ith row of the matrix. the dimension of rowi in calling program must be at least n.

integer, intent(in) :: n

the length of the rows of the matrix (i.e., the number of columns). n <= m, where m is the number of rows.

real(kind=wp), intent(in) :: bi

on the ith call, bi contains the ith element of the right hand side vector b.

real(kind=wp), intent(inout) :: a(nn)

a working array which must not be changed between the successive calls to suprls.

integer, intent(in) :: nn

length of scratch array a. nn must be at least n*(n+5)/2+1. For speed, nn should be as large as possible up to a maximum of (n+1)*m.

real(kind=wp), intent(out) :: soln(n)

the n-components of the solution vector are returned in this array after the final call to suprls.

real(kind=wp), intent(out) :: err

the euclidean norm of the residual is returned in err after the final call to suprls.

integer, intent(out) :: ier

error parameter. fatal errors:

  • 32 -- insufficient scratch storage provided, must have nn >= n*(n+5)/2+1.
  • 33 -- array has too few rows. must have m >= n.
  • 34 -- system is singular.
  • 35 -- values of i not in sequence.

Calls

proc~~suprls~~CallsGraph proc~suprls splpak_module::splpak_type%suprls proc~cfaerr splpak_module::cfaerr proc~suprls->proc~cfaerr

Called by

proc~~suprls~~CalledByGraph proc~suprls splpak_module::splpak_type%suprls proc~splcw splpak_module::splpak_type%splcw proc~splcw->proc~suprls proc~splcc splpak_module::splpak_type%splcc proc~splcc->proc~splcw

Source Code

subroutine suprls(me,i,rowi,n,bi,a,nn,soln,err,ier)

    class(splpak_type),intent(inout) :: me
    integer,intent(in) :: i !! the index of the row being entered.  (`i` is 1
                            !! for the first call, increases by 1 for each
                            !! call, and is `m` when the final row is
                            !! entered).  After the final row has been
                            !! entered, [[suprls]] is called with `i = 0` to
                            !! complete the reduction and solution.
    integer,intent(in) :: nn !! length of scratch array `a`. `nn` must be at
                             !! least `n*(n+5)/2+1`. For speed, `nn` should be
                             !! as large as possible up to a maximum of
                             !! `(n+1)*m`.
    integer,intent(in) :: n !! the length of the rows of the matrix (i.e.,
                            !! the number of columns). `n <= m`, where `m` is
                            !! the number of rows.
    real(wp),intent(in) :: rowi(n) !! a vector which on the `i`th call contains the `n`
                                   !! components of the `i`th row of the matrix.  the
                                   !! dimension of `rowi` in calling program must be
                                   !! at least `n`.
    real(wp),intent(in) :: bi !! on the `i`th call, `bi` contains the `i`th element
                              !! of the right hand side vector `b`.
    real(wp),intent(inout) :: a(nn) !! a working array which must not be changed
                                    !! between the successive calls to [[suprls]].
    real(wp),intent(out) :: soln(n) !! the `n`-components of the solution vector are
                                    !! returned in this array after the final call
                                    !! to [[suprls]].
    real(wp),intent(out) :: err !! the euclidean norm of the residual is
                                !! returned in `err` after the final call to
                                !! [[suprls]].
    integer,intent(out) :: ier !! error parameter.
                               !! fatal errors:
                               !!
                               !!  * 32 -- insufficient scratch storage provided,
                               !!    must have `nn >= n*(n+5)/2+1`.
                               !!  * 33 -- array has too few rows.  must have
                               !!    `m >= n`.
                               !!  * 34 -- system is singular.
                               !!  * 35 -- values of `i` not in sequence.

    real(wp) :: s,temp,temp1,cn,sn
    integer :: j,ilj,ilnp,nreq,k,&
               idiag,i1,i2,ii,jp1,lmkm1,j1,jdel,idj,iijd,&
               i1jd,k11,k1m1,i11,np1mk,lmk,imov,iii,iiim,iim1,&
               ilk,npk,ilii,npii
    logical :: complete_reduction !! Routine entered with `I<=0` means complete
                                  !! the reduction and store the solution in `SOLN`.

    real(wp),parameter :: tol = 1.0e-18_wp !! small number tolerance

    ier = 0
    complete_reduction = i <= 0

    if (.not. complete_reduction) then

        if (i<=1) then

            !  Set up quantities on first call.
            me%iold = 0
            me%np1 = n + 1

            !  Compute how many rows can be input now.
            me%l = nn/me%np1
            me%ilast = 0
            me%il1 = 0
            me%k = 0
            me%k1 = 0
            me%errsum = 0.0_wp
            nreq = ((n+5)*n+2)/2

            !  Error exit if insufficient scratch storage provided.
            if (nn<nreq) then
                ier = 32
                write(*,*) 'nn   = ', nn
                write(*,*) 'nreq = ', nreq
                call cfaerr(ier, &
                            ' suprls - insufficient scratch storage provided. '//&
                            'at least ((N+5)*N+2)/2 locations needed')
                return
            end if

        end if

        !  Error exit if (I-IOLD)/=1.
        if ((i-me%iold)/=1) then
            ier = 35
            write(*,*) 'i    =',i
            write(*,*) 'me%iold =',me%iold
            call cfaerr(ier,' suprls - values of I not in sequence')
            return
        end if

        !  Store the row in the scratch storage.
        me%iold = i
        do j = 1,n
            ilj = me%ilast + j
            a(ilj) = rowi(j)
        end do
        ilnp = me%ilast + me%np1
        a(ilnp) = bi
        me%ilast = me%ilast + me%np1
        me%isav = i
        if (i<me%l) return

    end if

    main : do

        if (.not. complete_reduction) then

            if (me%k/=0) then
                me%k1 = min(me%k,n)
                idiag = -me%np1
                if (me%l-me%k==1) then
                    !  Apply rotations to zero out the single new row.
                    do j = 1,me%k1
                        idiag = idiag + (me%np1-j+2)
                        i1 = me%il1 + j
                        if (abs(a(i1))<=tol) then
                            s = sqrt(a(idiag)*a(idiag))
                        else if (abs(a(idiag))<tol) then
                            s = sqrt(a(i1)*a(i1))
                        else
                            s = sqrt(a(idiag)*a(idiag)+a(i1)*a(i1))
                        end if
                        if (s==0.0_wp) cycle
                        temp = a(idiag)
                        a(idiag) = s
                        s = 1.0_wp/s
                        cn = temp*s
                        sn = a(i1)*s
                        jp1 = j + 1
                        do j1 = jp1,me%np1
                            jdel = j1 - j
                            idj = idiag + jdel
                            temp = a(idj)
                            i1jd = i1 + jdel
                            a(idj) = cn*temp + sn*a(i1jd)
                            a(i1jd) = -sn*temp + cn*a(i1jd)
                        end do
                    end do
                else
                    !  Apply householder transformations to zero out new rows.
                    do j = 1,me%k1
                        idiag = idiag + (me%np1-j+2)
                        i1 = me%il1 + j
                        i2 = i1 + me%np1* (me%l-me%k-1)
                        s = a(idiag)*a(idiag)
                        do ii = i1,i2,me%np1
                            s = s + a(ii)*a(ii)
                        end do
                        if (s==0.0_wp) cycle
                        temp = a(idiag)
                        a(idiag) = sqrt(s)
                        if (temp>0.0_wp) a(idiag) = -a(idiag)
                        temp = temp - a(idiag)
                        temp1 = 1.0_wp / (temp*a(idiag))
                        jp1 = j + 1
                        do j1 = jp1,me%np1
                            jdel = j1 - j
                            idj = idiag + jdel
                            s = temp*a(idj)
                            do ii = i1,i2,me%np1
                                iijd = ii + jdel
                                s = s + a(ii)*a(iijd)
                            end do
                            s = s*temp1
                            a(idj) = a(idj) + s*temp
                            do ii = i1,i2,me%np1
                                iijd = ii + jdel
                                a(iijd) = a(iijd) + s*a(ii)
                            end do
                        end do
                    end do
                end if

                if (me%k>=n) then
                    lmkm1 = me%l - me%k

                    !  Accumulate residual sum of squares.
                    do ii = 1,lmkm1
                        ilnp = me%il1 + ii*me%np1
                        me%errsum = me%errsum + a(ilnp)*a(ilnp)
                    end do
                    if (i<=0) exit main
                    me%k = me%l
                    me%ilast = me%il1

                    !  Determine how many new rows may be input on next iteration.
                    me%l = me%k + (nn-me%ilast)/me%np1
                    return
                end if
            end if

            k11 = me%k1 + 1
            me%k1 = min(me%l,n)
            if (me%l-me%k/=1) then
                k1m1 = me%k1 - 1
                if (me%l>n) k1m1 = n
                i1 = me%il1 + k11 - me%np1 - 1

                !  Perform householder transformations to reduce rows to upper
                !  triangular form.
                do j = k11,k1m1
                    i1 = i1 + (me%np1+1)
                    i2 = i1 + (me%l-j)*me%np1
                    s = 0.0_wp
                    do ii = i1,i2,me%np1
                        s = s + a(ii)*a(ii)
                    end do
                    if (s==0.0_wp) cycle
                    temp = a(i1)
                    a(i1) = sqrt(s)
                    if (temp>0.0_wp) a(i1) = -a(i1)
                    temp = temp - a(i1)
                    temp1 = 1.0_wp/ (temp*a(i1))
                    jp1 = j + 1
                    i11 = i1 + me%np1
                    do j1 = jp1,me%np1
                        jdel = j1 - j
                        i1jd = i1 + jdel
                        s = temp*a(i1jd)
                        do ii = i11,i2,me%np1
                            iijd = ii + jdel
                            s = s + a(ii)*a(iijd)
                        end do
                        s = s*temp1
                        i1jd = i1 + jdel
                        a(i1jd) = a(i1jd) + s*temp
                        do ii = i11,i2,me%np1
                            iijd = ii + jdel
                            a(iijd) = a(iijd) + s*a(ii)
                        end do
                    end do
                end do
                if (me%l>n) then
                    np1mk = me%np1 - me%k
                    lmk = me%l - me%k
                    ! Accumulate residual sum of squares.
                    do ii = np1mk,lmk
                        ilnp = me%il1 + ii*me%np1
                        me%errsum = me%errsum + a(ilnp)*a(ilnp)
                    end do
                end if
            end if
            imov = 0
            i1 = me%il1 + k11 - me%np1 - 1

            !  Squeeze the unnecessary elements out of scratch storage to
            !  allow space for more rows.
            do ii = k11,me%k1
                imov = imov + (ii-1)
                i1 = i1 + me%np1 + 1
                i2 = i1 + me%np1 - ii
                do iii = i1,i2
                    iiim = iii - imov
                    a(iiim) = a(iii)
                end do
            end do
            me%ilast = i2 - imov
            me%il1 = me%ilast
            if (i<=0) exit main
            me%k = me%l

            !  Determine how many new rows may be input on next iteration.
            me%l = me%k + (nn-me%ilast)/me%np1
            return

        end if

        ! Complete reduction and store solution in SOLN.
        complete_reduction = .false. ! reset this flag
        me%l = me%isav

        ! Error exit if L less than N.
        if (me%l<n) then
            ier = 33
            call cfaerr(ier,' suprls - array has too few rows.')
            return
        end if

        ! K/=ISAV means further reduction needed.
        if (me%k==me%isav) exit main

    end do main

    me%ilast = (me%np1* (me%np1+1))/2 - 1
    if (a(me%ilast-1)==0.0_wp) then
        ! Error return if system is singular.
        ier = 34
        call cfaerr(ier,' suprls - system is singular.')
        return
    end if

    ! Solve triangular system into ROWI.
    soln(n) = a(me%ilast)/a(me%ilast-1)
    do ii = 2,n
        iim1 = ii - 1
        me%ilast = me%ilast - ii
        s = a(me%ilast)
        do k = 1,iim1
            ilk = me%ilast - k
            npk = me%np1 - k
            s = s - a(ilk)*soln(npk)
        end do
        me%k = k ! JW : is this necessary ???
        ilii = me%ilast - ii
        if (a(ilii)==0.0_wp) then
            ! Error return if system is singular.
            ier = 34
            call cfaerr(ier,' suprls - system is singular.')
            return
        end if
        npii = me%np1 - ii
        soln(npii) = s/a(ilii)
    end do

    ! Store residual norm.
    err = sqrt(me%errsum)

end subroutine suprls