forlab_linalg_svd.f90 Source File


Contents

Source Code


Source Code

submodule(forlab_linalg) forlab_linalg_svd

    implicit none

contains

    module procedure svd_sp
    integer :: m, n, i, its, i1, j, k, kk, k1, l, ll, l1, mn
    integer, dimension(:), allocatable :: idx
    real(sp) :: c, f, g, h, s, scale, tst1, tst2, x, y, z
    real(sp), dimension(:), allocatable :: rv1
    real(sp), dimension(:, :), allocatable :: opt_u, opt_v
    logical :: outu, outv, opt_d, outierr
    real(sp), parameter::zero = 0.0_sp, one = 1.0_sp
    outu = .false.
    outv = .false.
    opt_d = .true.
    outierr = .false.
    m = size(a, 1)
    n = size(a, 2)
    if (.not. allocated(w)) allocate (w(n))
    allocate (rv1(n), opt_u(m, n), opt_v(n, n))
    opt_u = a
    if (present(d)) opt_d = d
    if (present(u)) outu = .true.
    if (present(v)) outv = .true.
    if (present(ierr)) outierr = .true.
    ! Householder reduction to bidiagonal form
    !==========================================
    g = zero
    scale = zero
    x = zero
    do i = 1, n
        l = i + 1
        rv1(i) = scale*g
        g = zero
        s = zero
        scale = zero
        if (i <= m) then
            scale = sum(abs(opt_u(i:m, i)))
            if (scale /= zero) then
                opt_u(i:m, i) = opt_u(i:m, i)/scale
                s = sum(opt_u(i:m, i)**2)
                f = opt_u(i, i)
                g = -sign(sqrt(s), f)
                h = f*g - s
                opt_u(i, i) = f - g
                if (i /= n) then
                    do j = l, n
                        s = dot_product(opt_u(i:m, i), opt_u(i:m, j))
                        opt_u(i:m, j) = opt_u(i:m, j) + s*opt_u(i:m, i)/h
                    end do
                end if
                opt_u(i:m, i) = scale*opt_u(i:m, i)
            end if
        end if
        w(i) = scale*g
        g = zero
        s = zero
        scale = zero
        if ((i <= m) .and. (i /= n)) then
            scale = sum(abs(opt_u(i, l:n)))
            if (scale /= zero) then
                opt_u(i, l:n) = opt_u(i, l:n)/scale
                s = sum(opt_u(i, l:n)**2)
                f = opt_u(i, l)
                g = -sign(sqrt(s), f)
                h = f*g - s
                opt_u(i, l) = f - g
                rv1(l:n) = opt_u(i, l:n)/h
                if (i /= m) then
                    do j = l, m
                        s = dot_product(opt_u(j, l:n), opt_u(i, l:n))
                        opt_u(j, l:n) = opt_u(j, l:n) + s*rv1(l:n)
                    end do
                end if
                opt_u(i, l:n) = scale*opt_u(i, l:n)
            end if
        end if
        x = max(x, abs(w(i)) + abs(rv1(i)))
    end do
    ! Accumulation of right-hand transformations
    !============================================
    if (outv) then
        do i = n, 1, -1
        if (i /= n) then
            if (g /= zero) then
                opt_v(l:n, i) = (opt_u(i, l:n)/opt_u(i, l))/g
                do j = l, n
                    s = dot_product(opt_u(i, l:n), opt_v(l:n, j))
                    opt_v(l:n, j) = opt_v(l:n, j) + s*opt_v(l:n, i)
                end do
            end if
            opt_v(i, l:n) = zero
            opt_v(l:n, i) = zero
        end if
        opt_v(i, i) = one
        g = rv1(i)
        l = i
        end do
    end if
    ! Accumulation of left-hand transformations
    !===========================================
    if (outu) then
        mn = min(m, n)
        do i = min(m, n), 1, -1
            l = i + 1
            g = w(i)
            if (i /= n) opt_u(i, l:n) = zero
            if (g /= zero) then
                if (i /= mn) then
                    do j = l, n
                        s = dot_product(opt_u(l:m, i), opt_u(l:m, j))
                        f = (s/opt_u(i, i))/g
                        opt_u(i:m, j) = opt_u(i:m, j) + f*opt_u(i:m, i)
                    end do
                end if
                opt_u(i:m, i) = opt_u(i:m, i)/g
            else
                opt_u(i:m, i) = zero
            end if
            opt_u(i, i) = opt_u(i, i) + one
        end do
    end if
    ! Diagonalization of the bidiagonal form
    !========================================
    tst1 = x
    do kk = 1, n
        k1 = n - kk
        k = k1 + 1
        its = 0
        ! Test for splitting
        !====================
520     continue
        do ll = 1, k
            l1 = k - ll
            l = l1 + 1
            tst2 = tst1 + abs(rv1(l))
            if (tst2 == tst1) goto 565
            tst2 = tst1 + abs(w(l1))
            if (tst2 == tst1) exit
        end do
        ! Cancellation of rv1(l) if L greater than 1
        !============================================
        c = zero
        s = one
        do i = l, k
            f = s*rv1(i)
            rv1(i) = c*rv1(i)
            tst2 = tst1 + abs(f)
            if (tst2 == tst1) goto 565
            g = w(i)
            h = pythag_sp(f, g)
            w(i) = h
            c = g/h
            s = -f/h
            if (outu) then
                do j = 1, m
                    y = opt_u(j, l1)
                    z = opt_u(j, i)
                    opt_u(j, l1) = y*c + z*s
                    opt_u(j, i) = -y*s + z*c
                end do
            end if
        end do
        ! Test for convergence
        !======================
565     continue
        z = w(k)
        if (l == k) goto 650
        ! Shift from bottom 2 by 2 minor
        !================================
        if (its >= 30) then
            if (outierr) ierr = k
            return
        end if
        its = its + 1
        x = w(l)
        y = w(k1)
        g = rv1(k1)
        h = rv1(k)
        f = one/2*(((g + z)/h)*((g - z)/y) + y/h - h/y)
        g = pythag_sp(f, one)
        f = x - (z/x)*z + (h/x)*(y/(f + sign(g, f)) - h)
        ! Next QR transformation
        !========================
        c = one
        s = one
        do i1 = l, k1
            i = i1 + 1
            g = rv1(i)
            y = w(i)
            h = s*g
            g = c*g
            z = pythag_sp(f, h)
            rv1(i1) = z
            c = f/z
            s = h/z
            f = x*c + g*s
            g = -x*s + g*c
            h = y*s
            y = y*c
            if (outv) then
                do j = 1, n
                    x = opt_v(j, i1)
                    z = opt_v(j, i)
                    opt_v(j, i1) = x*c + z*s
                    opt_v(j, i) = -x*s + z*c
                end do
            end if
            z = pythag_sp(f, h)
            w(i1) = z
            ! Rotation can be arbitrary if Z is zero
            !========================================
            if (z /= zero) then
                c = f/z
                s = h/z
            end if
            f = c*g + s*y
            x = -s*g + c*y
            if (outu) then
                do j = 1, m
                    y = opt_u(j, i1)
                    z = opt_u(j, i)
                    opt_u(j, i1) = y*c + z*s
                    opt_u(j, i) = -y*s + z*c
                end do
            end if
        end do
        rv1(l) = zero
        rv1(k) = f
        w(k) = x
        go to 520
        ! Convergence
        !=============
650     continue
        if (z <= zero) then
            w(k) = -z
            if (outv) then
                opt_v(1:n, k) = -opt_v(1:n, k)
            end if
        end if
    end do
    ! Sort singular values
    !======================
    if (opt_d) then
        idx = argsort(w, 2)
        w = w(idx)
        if (present(u)) u = opt_u(:, idx)
        if (present(v)) v = opt_v(:, idx)
    else
        if (present(u)) u = opt_u
        if (present(v)) v = opt_v
    end if
    return
    end procedure svd_sp

    function pythag_sp(x1, x2) result(pythag)
        real(sp), intent(in) :: x1, x2
        real(sp):: r, s, t, u
        real(sp)::pythag
        real(sp), parameter::zero = 0.0_sp, one = 1.0_sp
        pythag = max(abs(x1), abs(x2))
        if (pythag /= zero) then
            r = (min(abs(x1), abs(x2))/pythag)**2
            do
                t = 4*one + r
                if (t == 4*one) exit
                s = r/t
                u = one + 2*s
                pythag = u*pythag
                r = (s/u)**2*r
            end do
        end if
        return
    end function pythag_sp
    module procedure svd_dp
    integer :: m, n, i, its, i1, j, k, kk, k1, l, ll, l1, mn
    integer, dimension(:), allocatable :: idx
    real(dp) :: c, f, g, h, s, scale, tst1, tst2, x, y, z
    real(dp), dimension(:), allocatable :: rv1
    real(dp), dimension(:, :), allocatable :: opt_u, opt_v
    logical :: outu, outv, opt_d, outierr
    real(dp), parameter::zero = 0.0_dp, one = 1.0_dp
    outu = .false.
    outv = .false.
    opt_d = .true.
    outierr = .false.
    m = size(a, 1)
    n = size(a, 2)
    if (.not. allocated(w)) allocate (w(n))
    allocate (rv1(n), opt_u(m, n), opt_v(n, n))
    opt_u = a
    if (present(d)) opt_d = d
    if (present(u)) outu = .true.
    if (present(v)) outv = .true.
    if (present(ierr)) outierr = .true.
    ! Householder reduction to bidiagonal form
    !==========================================
    g = zero
    scale = zero
    x = zero
    do i = 1, n
        l = i + 1
        rv1(i) = scale*g
        g = zero
        s = zero
        scale = zero
        if (i <= m) then
            scale = sum(abs(opt_u(i:m, i)))
            if (scale /= zero) then
                opt_u(i:m, i) = opt_u(i:m, i)/scale
                s = sum(opt_u(i:m, i)**2)
                f = opt_u(i, i)
                g = -sign(sqrt(s), f)
                h = f*g - s
                opt_u(i, i) = f - g
                if (i /= n) then
                    do j = l, n
                        s = dot_product(opt_u(i:m, i), opt_u(i:m, j))
                        opt_u(i:m, j) = opt_u(i:m, j) + s*opt_u(i:m, i)/h
                    end do
                end if
                opt_u(i:m, i) = scale*opt_u(i:m, i)
            end if
        end if
        w(i) = scale*g
        g = zero
        s = zero
        scale = zero
        if ((i <= m) .and. (i /= n)) then
            scale = sum(abs(opt_u(i, l:n)))
            if (scale /= zero) then
                opt_u(i, l:n) = opt_u(i, l:n)/scale
                s = sum(opt_u(i, l:n)**2)
                f = opt_u(i, l)
                g = -sign(sqrt(s), f)
                h = f*g - s
                opt_u(i, l) = f - g
                rv1(l:n) = opt_u(i, l:n)/h
                if (i /= m) then
                    do j = l, m
                        s = dot_product(opt_u(j, l:n), opt_u(i, l:n))
                        opt_u(j, l:n) = opt_u(j, l:n) + s*rv1(l:n)
                    end do
                end if
                opt_u(i, l:n) = scale*opt_u(i, l:n)
            end if
        end if
        x = max(x, abs(w(i)) + abs(rv1(i)))
    end do
    ! Accumulation of right-hand transformations
    !============================================
    if (outv) then
        do i = n, 1, -1
        if (i /= n) then
            if (g /= zero) then
                opt_v(l:n, i) = (opt_u(i, l:n)/opt_u(i, l))/g
                do j = l, n
                    s = dot_product(opt_u(i, l:n), opt_v(l:n, j))
                    opt_v(l:n, j) = opt_v(l:n, j) + s*opt_v(l:n, i)
                end do
            end if
            opt_v(i, l:n) = zero
            opt_v(l:n, i) = zero
        end if
        opt_v(i, i) = one
        g = rv1(i)
        l = i
        end do
    end if
    ! Accumulation of left-hand transformations
    !===========================================
    if (outu) then
        mn = min(m, n)
        do i = min(m, n), 1, -1
            l = i + 1
            g = w(i)
            if (i /= n) opt_u(i, l:n) = zero
            if (g /= zero) then
                if (i /= mn) then
                    do j = l, n
                        s = dot_product(opt_u(l:m, i), opt_u(l:m, j))
                        f = (s/opt_u(i, i))/g
                        opt_u(i:m, j) = opt_u(i:m, j) + f*opt_u(i:m, i)
                    end do
                end if
                opt_u(i:m, i) = opt_u(i:m, i)/g
            else
                opt_u(i:m, i) = zero
            end if
            opt_u(i, i) = opt_u(i, i) + one
        end do
    end if
    ! Diagonalization of the bidiagonal form
    !========================================
    tst1 = x
    do kk = 1, n
        k1 = n - kk
        k = k1 + 1
        its = 0
        ! Test for splitting
        !====================
520     continue
        do ll = 1, k
            l1 = k - ll
            l = l1 + 1
            tst2 = tst1 + abs(rv1(l))
            if (tst2 == tst1) goto 565
            tst2 = tst1 + abs(w(l1))
            if (tst2 == tst1) exit
        end do
        ! Cancellation of rv1(l) if L greater than 1
        !============================================
        c = zero
        s = one
        do i = l, k
            f = s*rv1(i)
            rv1(i) = c*rv1(i)
            tst2 = tst1 + abs(f)
            if (tst2 == tst1) goto 565
            g = w(i)
            h = pythag_dp(f, g)
            w(i) = h
            c = g/h
            s = -f/h
            if (outu) then
                do j = 1, m
                    y = opt_u(j, l1)
                    z = opt_u(j, i)
                    opt_u(j, l1) = y*c + z*s
                    opt_u(j, i) = -y*s + z*c
                end do
            end if
        end do
        ! Test for convergence
        !======================
565     continue
        z = w(k)
        if (l == k) goto 650
        ! Shift from bottom 2 by 2 minor
        !================================
        if (its >= 30) then
            if (outierr) ierr = k
            return
        end if
        its = its + 1
        x = w(l)
        y = w(k1)
        g = rv1(k1)
        h = rv1(k)
        f = one/2*(((g + z)/h)*((g - z)/y) + y/h - h/y)
        g = pythag_dp(f, one)
        f = x - (z/x)*z + (h/x)*(y/(f + sign(g, f)) - h)
        ! Next QR transformation
        !========================
        c = one
        s = one
        do i1 = l, k1
            i = i1 + 1
            g = rv1(i)
            y = w(i)
            h = s*g
            g = c*g
            z = pythag_dp(f, h)
            rv1(i1) = z
            c = f/z
            s = h/z
            f = x*c + g*s
            g = -x*s + g*c
            h = y*s
            y = y*c
            if (outv) then
                do j = 1, n
                    x = opt_v(j, i1)
                    z = opt_v(j, i)
                    opt_v(j, i1) = x*c + z*s
                    opt_v(j, i) = -x*s + z*c
                end do
            end if
            z = pythag_dp(f, h)
            w(i1) = z
            ! Rotation can be arbitrary if Z is zero
            !========================================
            if (z /= zero) then
                c = f/z
                s = h/z
            end if
            f = c*g + s*y
            x = -s*g + c*y
            if (outu) then
                do j = 1, m
                    y = opt_u(j, i1)
                    z = opt_u(j, i)
                    opt_u(j, i1) = y*c + z*s
                    opt_u(j, i) = -y*s + z*c
                end do
            end if
        end do
        rv1(l) = zero
        rv1(k) = f
        w(k) = x
        go to 520
        ! Convergence
        !=============
650     continue
        if (z <= zero) then
            w(k) = -z
            if (outv) then
                opt_v(1:n, k) = -opt_v(1:n, k)
            end if
        end if
    end do
    ! Sort singular values
    !======================
    if (opt_d) then
        idx = argsort(w, 2)
        w = w(idx)
        if (present(u)) u = opt_u(:, idx)
        if (present(v)) v = opt_v(:, idx)
    else
        if (present(u)) u = opt_u
        if (present(v)) v = opt_v
    end if
    return
    end procedure svd_dp

    function pythag_dp(x1, x2) result(pythag)
        real(dp), intent(in) :: x1, x2
        real(dp):: r, s, t, u
        real(dp)::pythag
        real(dp), parameter::zero = 0.0_dp, one = 1.0_dp
        pythag = max(abs(x1), abs(x2))
        if (pythag /= zero) then
            r = (min(abs(x1), abs(x2))/pythag)**2
            do
                t = 4*one + r
                if (t == 4*one) exit
                s = r/t
                u = one + 2*s
                pythag = u*pythag
                r = (s/u)**2*r
            end do
        end if
        return
    end function pythag_dp

end submodule forlab_linalg_svd