dcbcrt Subroutine

public subroutine dcbcrt(a, zr, zi)

Computes the roots of a cubic polynomial of the form a(1) + a(2)*z + a(3)*z**2 + a(4)*z**3 = 0

See also

  • A. H. Morris, "NSWC Library of Mathematical Subroutines", Naval Surface Warfare Center, NSWCDD/TR-92/425, January 1993

History

  • Original version by Alfred H. Morris & William Davis, Naval Surface Weapons Center

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in), dimension(4) :: a

coefficients

real(kind=wp), intent(out), dimension(3) :: zr

real components of roots

real(kind=wp), intent(out), dimension(3) :: zi

imaginary components of roots


Calls

proc~~dcbcrt~~CallsGraph proc~dcbcrt polyroots_module::dcbcrt proc~dcbrt polyroots_module::dcbrt proc~dcbcrt->proc~dcbrt proc~dqdcrt polyroots_module::dqdcrt proc~dcbcrt->proc~dqdcrt

Called by

proc~~dcbcrt~~CalledByGraph proc~dcbcrt polyroots_module::dcbcrt proc~dqtcrt polyroots_module::dqtcrt proc~dqtcrt->proc~dcbcrt

Source Code

subroutine dcbcrt(a, zr, zi)

    implicit none

    real(wp), dimension(4), intent(in) :: a     !! coefficients
    real(wp), dimension(3), intent(out) :: zr   !! real components of roots
    real(wp), dimension(3), intent(out) :: zi   !! imaginary components of roots

    real(wp) :: arg, c, cf, d, p, p1, q, q1, &
                r, ra, rb, rq, rt, r1, s, sf, sq, sum, &
                t, tol, t1, w, w1, w2, x, x1, x2, x3, y, &
                y1, y2, y3

    real(wp), parameter :: sqrt3 = sqrt(3.0_wp)

    if (a(4) == 0.0_wp) then  ! quadratic equation

        call dqdcrt(a(1:3), zr(1:2), zi(1:2))

        !there are only two roots, so just duplicate the second one:
        zr(3) = zr(2)
        zi(3) = zi(2)

    else

        if (a(1) == 0.0_wp) then ! quadratic

            zr(1) = 0.0_wp
            zi(1) = 0.0_wp
            call dqdcrt(a(2:4), zr(2:3), zi(2:3))

        else

            p = a(3)/(3.0_wp*a(4))
            q = a(2)/a(4)
            r = a(1)/a(4)
            tol = 4.0_wp*eps

            c = 0.0_wp
            t = a(2) - p*a(3)
            if (abs(t) > tol*abs(a(2))) c = t/a(4)

            t = 2.0_wp*p*p - q
            if (abs(t) <= tol*abs(q)) t = 0.0_wp
            d = r + p*t

            if (abs(d) <= tol*abs(r)) then

                !case when d = 0

                zr(1) = -p
                zi(1) = 0.0_wp
                w = sqrt(abs(c))
                if (c < 0.0_wp) then

                    if (p /= 0.0_wp) then

                        x = -(p + sign(w, p))
                        zr(3) = x
                        zi(2) = 0.0_wp
                        zi(3) = 0.0_wp
                        t = 3.0_wp*a(1)/(a(3)*x)

                        if (abs(p) > abs(t)) then
                            zr(2) = zr(1)
                            zr(1) = t
                        else
                            zr(2) = t
                        end if

                    else

                        zr(2) = w
                        zr(3) = -w
                        zi(2) = 0.0_wp
                        zi(3) = 0.0_wp

                    end if

                else

                    zr(2) = -p
                    zr(3) = zr(2)
                    zi(2) = w
                    zi(3) = -w

                end if

            else

                !set  sq = (a(4)/s)**2 * (c**3/27 + d**2/4)

                s = max(abs(a(1)), abs(a(2)), abs(a(3)))
                p1 = a(3)/(3.0_wp*s)
                q1 = a(2)/s
                r1 = a(1)/s

                t1 = q - 2.25_wp*p*p
                if (abs(t1) <= tol*abs(q)) t1 = 0.0_wp
                w = 0.25_wp*r1*r1
                w1 = 0.5_wp*p1*r1*t
                w2 = q1*q1*t1/27.0_wp

                if (w1 < 0.0_wp) then

                    if (w2 < 0.0_wp) then
                        sq = w + (w1 + w2)
                    else
                        w = w + w2
                        sq = w + w1
                    end if

                else

                    w = w + w1
                    sq = w + w2

                end if

                if (abs(sq) <= tol*w) sq = 0.0_wp
                rq = abs(s/a(4))*sqrt(abs(sq))

                if (sq < 0.0_wp) then

                    !all roots are real

                    arg = atan2(rq, -0.5_wp*d)
                    cf = cos(arg/3.0_wp)
                    sf = sin(arg/3.0_wp)
                    rt = sqrt(-c/3.0_wp)
                    y1 = 2.0_wp*rt*cf
                    y2 = -rt*(cf + sqrt3*sf)
                    y3 = -(d/y1)/y2

                    x1 = y1 - p
                    x2 = y2 - p
                    x3 = y3 - p
                    if (abs(x1) > abs(x2)) then
                        t = x1
                        x1 = x2
                        x2 = t
                    end if
                    if (abs(x2) > abs(x3)) then
                        t = x2
                        x2 = x3
                        x3 = t
                        if (abs(x1) > abs(x2)) then
                            t = x1
                            x1 = x2
                            x2 = t
                        end if
                    end if

                    w = x3
                    if (abs(x2) < 0.1_wp*abs(x3)) then
                        call roundoff()
                    else
                        if (abs(x1) < 0.1_wp*abs(x2)) x1 = -(r/x3)/x2
                        zr(1) = x1
                        zr(2) = x2
                        zr(3) = x3
                        zi(1) = 0.0_wp
                        zi(2) = 0.0_wp
                        zi(3) = 0.0_wp
                    end if

                else

                    !real and complex roots

                    ra = dcbrt(-0.5_wp*d - sign(rq, d))
                    rb = -c/(3.0_wp*ra)
                    t = ra + rb
                    w = -p
                    x = -p
                    if (abs(t) > tol*abs(ra)) then
                        w = t - p
                        x = -0.5_wp*t - p
                        if (abs(x) <= tol*abs(p)) x = 0.0_wp
                    end if
                    t = abs(ra - rb)
                    y = 0.5_wp*sqrt3*t

                    if (t > tol*abs(ra)) then

                        if (abs(x) < abs(y)) then
                            s = abs(y)
                            t = x/y
                        else
                            s = abs(x)
                            t = y/x
                        end if

                        if (s < 0.1_wp*abs(w)) then
                            call roundoff()
                        else
                            w1 = w/s
                            sum = 1.0_wp + t*t
                            if (w1*w1 < 1.0e-2_wp*sum) w = -((r/sum)/s)/s
                            zr(1) = w
                            zr(2) = x
                            zr(3) = x
                            zi(1) = 0.0_wp
                            zi(2) = y
                            zi(3) = -y
                        end if

                    else

                        !at least two roots are equal

                        zi(1) = 0.0_wp
                        zi(2) = 0.0_wp
                        zi(3) = 0.0_wp

                        if (abs(x) < abs(w)) then
                            if (abs(x) < 0.1_wp*abs(w)) then
                                call roundoff()
                            else
                                zr(1) = x
                                zr(2) = x
                                zr(3) = w
                            end if
                        else
                            if (abs(w) < 0.1_wp*abs(x)) w = -(r/x)/x
                            zr(1) = w
                            zr(2) = x
                            zr(3) = x
                        end if

                    end if

                end if

            end if

        end if

    end if

contains

    !*************************************************************
    subroutine roundoff()

        !!  here `w` is much larger in magnitude than the other roots.
        !!  as a result, the other roots may be exceedingly inaccurate
        !!  because of roundoff error. to deal with this, a quadratic
        !!  is formed whose roots are the same as the smaller roots of
        !!  the cubic. this quadratic is then solved.
        !!
        !!  this code was written by william l. davis (nswc).

        implicit none

        real(wp), dimension(3) :: aq

        aq(1) = a(1)
        aq(2) = a(2) + a(1)/w
        aq(3) = -a(4)*w
        call dqdcrt(aq, zr(1:2), zi(1:2))
        zr(3) = w
        zi(3) = 0.0_wp

        if (zi(1) /= 0.0_wp) then
            zr(3) = zr(2)
            zi(3) = zi(2)
            zr(2) = zr(1)
            zi(2) = zi(1)
            zr(1) = w
            zi(1) = 0.0_wp
        end if

    end subroutine roundoff
    !*************************************************************

end subroutine dcbcrt