! Algorithm AS 319 and test program
! Converted to Fortran 90 free-format style by Alan Miller
! e-mail: Alan.Miller @ vic.cmis.csiro.au
! URL: www.ozemail.com.au/~milleraj

MODULE as319
IMPLICIT NONE

! COMMON /funerr/ler
! COMMON /test/ig,ifn
LOGICAL, SAVE      :: ler
INTEGER, SAVE      :: ig, ifn

INTEGER, PARAMETER :: dp = SELECTED_REAL_KIND(14, 50)

END MODULE as319



!-----------------------------------------------------------------------

SUBROUTINE varme(fun, npar, b, f0, nsig, maxfn, iout, ier)
!-----------------------------------------------------------------------

!     CALLING SUBROUTINE FOR SUBROUTINE VARMET

!     ALLOWS FOR SETUP OF DEFAULT PARAMETERS
!     AND EFFICIENT USE OF STORAGE
!     AS WELL AS WRITING OF ERROR MESSAGES

!    VERSION 0.1
!    CODED BY JOHN J. KOVAL
!    MARCH 1986

!    VERSION 0.2
!    CODED BY JOHN J. KOVAL
!    JULY 1988

!    VERSION 0.26
!    CODED BY MURRAY ALEXANDER FOR JOHN J. KOVAL
!    JULY 1989

!    VERSION 0.27
!    MODIFIED BY NAZIH HASSAN, JULY 1993

!    VERSION 0.28
!    MODIFIED BY JOHN KOVAL, JUNE 1996
!    BECAUSE OF COMMENTS FROM REVIEWER FOR APPLIED STATISTICS
!    CHANGES TO ORDER OF PARAMETERS IN GRAD

!    PARAMETERS              MEANING                       DEFAULT
!    ----------              -------                       -------

!     FUN         NAME OF FUNCTION TO BE MINIMIZED

!     NPAR        ORDER OF PARAMETER VECTOR
!                 (NUMBER OF UNKNOWNS)

!     B           ARRAY CONTAINING INITIAL ESTIMATES
!                 ON OUTPUT CONTAINING FINAL ESTIMATES

!     F0          VALUE OF FUNCTION AT MINIMUM

!     NSIG        MACHINE ACCURACY AS NEGATIVE POWER       10 OR 5
!                 OF TEN

!     MAXFN       MAXIMUM NUMBER OF FUNCTION EVALUATIONS     1000
!                 (DOES INCLUDE EVALUATIONS BY SUBROUTINE
!                 GRAD WHICH CALCULATES APPROXIMATE GRADIENT)

!     IOUT        OUTPUT CHANNEL FOR ERROR MESSAGES            0
!                 (IF 0, THEN MESSAGES NOT WRITTEN)

!      IER        ERROR INDICATOR                              0
!                 INTEGER

!-----------------------------------------------------------------------

USE as319
IMPLICIT NONE
INTEGER, INTENT(IN)               :: npar
REAL (dp), INTENT(IN OUT)         :: b(:)
REAL (dp), INTENT(IN OUT)         :: f0
INTEGER, INTENT(IN OUT)           :: nsig
INTEGER, INTENT(IN OUT)           :: maxfn
INTEGER, INTENT(IN OUT)           :: iout
INTEGER, INTENT(OUT)              :: ier

INTERFACE
  SUBROUTINE fun(nord, bp, q)
    USE as319
    IMPLICIT NONE
    INTEGER, INTENT(IN)           :: nord
    REAL (dp), INTENT(IN)         :: bp(:)
    REAL (dp), INTENT(OUT)        :: q
  END SUBROUTINE fun
END INTERFACE

REAL (dp) :: gradtl

INTEGER, PARAMETER :: maxf = 1000, msig = 10

!     INITIALIZE

ier=0

IF(nsig == 0) nsig = msig
gradtl = 1.0 / (10.0**(nsig))

IF(gradtl < 0.0) THEN
  IF(iout > 0) WRITE(iout, 300) nsig, gradtl
  300    FORMAT(' NSIG VALUE OF ', i3, ' CREATES NEGATIVE VALUE OF',  &
      ' GRADTL, NAMELY, ', g12.5)
  gradtl = 1.0/(10**(msig))
  IF(iout > 0) WRITE(iout, 310) msig, gradtl
  310    FORMAT(' PROGRAM SUBSTITUTES NSIG VALUE OF ', i3, ' WHICH',  &
      ' GIVES GRADTL VALUE OF ', g12.5)
END IF

IF(maxfn == 0) maxfn = maxf

!      NOW WE ARE READY TO CALL THE MINIMIZATION SUBROUTINE

CALL varmet(fun, npar, b, f0, gradtl, maxfn, ier)

IF(ier > 0.AND.iout > 0)THEN
  WRITE(iout, 30) ier
  30   FORMAT(/' SUBROUTINE VARMET ERROR NUMBER ', i3)
  IF(ier == 1) THEN
    WRITE(iout, 40)
  ELSE IF(ier == 2) THEN
    WRITE(iout, 60)
  ELSE IF(ier == 3) THEN
    WRITE(iout, 70)
  ELSE IF(ier == 4) THEN
    WRITE(iout, 80)
  END IF
  40   FORMAT(' FUNCTION UNDEFINED AT INITIAL VALUE         ')
  60   FORMAT(' GRADIENT UNDEFINED IN TOO MANY DIMENSIONS   ')
  70   FORMAT(' FUNCTON NOT MINIMIZED BUT'  &
      /' UNABLE TO FIND MINIMUM IN DIRECTION OF SEARCH')
  80   FORMAT(' TOO MANY FUNCTION EVALUATIONS REQUIRED      ')
  
END IF

RETURN

CONTAINS


!----------------------------------------------------------------------

SUBROUTINE varmet(fun, npar, b, f0, gradtl, maxfn, ifault)

!       ALGORITHM AS 319 APPL.STATIST. (1997), VOL.46, NO.4

!               VARIABLE METRIC FUNCTION MINIMISATION

INTEGER, INTENT(IN)               :: npar
REAL (dp), INTENT(IN OUT)         :: b(:)
REAL (dp), INTENT(OUT)            :: f0
REAL (dp), INTENT(OUT)            :: gradtl
INTEGER, INTENT(OUT)              :: maxfn
INTEGER, INTENT(OUT)              :: ifault

INTERFACE
  SUBROUTINE fun(nord, bp, q)
    USE as319
    IMPLICIT NONE
    INTEGER, INTENT(IN)           :: nord
    REAL (dp), INTENT(IN)         :: bp(:)
    REAL (dp), INTENT(OUT)        :: q
  END SUBROUTINE fun
END INTERFACE

REAL (dp)            :: d1, s, ck, f1, d2
INTEGER              :: i, ic, icount, ifn, ig, ilast, j, k, np
INTEGER, PARAMETER   :: icmax=20
REAL (dp), PARAMETER :: toler=0.00001, w=0.2
REAL (dp)            :: g(npar), h(npar,npar), c(npar), d(npar), t(2*npar)

ig = 0
ifn = 0
ler = .false.
ifault = 0
np = npar + 1

IF (maxfn == 0) maxfn = 1000
IF (gradtl == 0.0) gradtl = 1.0D-10

CALL fun(npar, b, f0)
IF(ler) THEN
  ifault = 1
  RETURN
END IF
ifn = ifn + 1

CALL grad(fun, npar, b, f0, g, t(np:), gradtl, ifault)
IF(ifault > 0) RETURN

ig = ig + 1
ifn = ifn + npar
IF(ifn > maxfn) THEN
  ifault = 4
  RETURN
END IF

10 DO k = 1, npar
  h(k,1:npar) = 0.0
  h(k,k) = 1.00
END DO
ilast = ig

40 DO i = 1, npar
  d(i) = b(i)
  c(i) = g(i)
END DO

d1 = 0.0
DO i = 1, npar
  s = - DOT_PRODUCT( h(i,1:npar), g(1:npar) )
  t(i) = s
  d1 = d1 - s*g(i)
END DO

IF(d1 <= 0.0) THEN
  IF(ilast == ig) THEN
    RETURN
  END IF
  GO TO 10
ELSE
  ck = 1.0
  ic = 0
  90    icount = 0
  DO i = 1,npar
    b(i) = d(i) + ck*t(i)
    IF(b(i) == d(i)) THEN
      icount = icount + 1
    END IF
  END DO
  
  IF(icount >= npar) THEN
    IF(ilast == ig) THEN
      RETURN
    END IF
    GO TO 10
  ELSE
    CALL fun(npar, b, f1)
    
    ifn = ifn + 1
    IF(ifn > maxfn) THEN
      ifault = 4
      RETURN
    ELSE IF(ler) THEN
      ck = w * ck
      ic = ic+1
      IF(ic > icmax) THEN
        ifault = 3
        RETURN
      END IF
      GO TO 90
      
    ELSE IF(f1 >= f0 - d1*ck*toler) THEN
      ck = w * ck
      GO TO 90
    ELSE
      f0 = f1
      CALL grad(fun, npar, b, f0, g, t(np:), gradtl, ifault)
      IF(ifault > 0) THEN
        RETURN
      END IF
      ig = ig + 1
      ifn = ifn + npar
      IF(ifn > maxfn) THEN
        ifault = 4
        RETURN
      END IF
      
      d1 = 0.0
      DO i = 1, npar
        t(i) = ck*t(i)
        c(i) = g(i) - c(i)
        d1 = d1 + t(i)*c(i)
      END DO
      
      IF(d1 <= 0.0) THEN
        GO TO 10
      END IF
      
      d2 = 0.0
      DO i = 1, npar
        s = 0.0
        DO j = 1, npar
          s = s + h(i,j)*c(j)
        END DO
        d(i) = s
        d2 = d2 + s*c(i)
      END DO
      d2 = 1.0 + d2/d1
      
      DO i = 1, npar
        DO j = 1, npar
          h(i,j) = h(i,j) - (t(i)*d(j) + d(i)*t(j) - d2*t(i)*t(j))/d1
        END DO
      END DO
    END IF
  END IF
END IF
GO TO 40
END SUBROUTINE varmet


SUBROUTINE grad(f, npar, b, f0, g, sa, er, ifault)

!     CALCULATE APPROXIMATE GRADIENT

INTEGER, INTENT(IN)           :: npar
REAL (dp), INTENT(IN OUT)     :: b(:)
REAL (dp), INTENT(IN)         :: f0
REAL (dp), INTENT(OUT)        :: g(:)
REAL (dp), INTENT(OUT)        :: sa(:)
REAL (dp), INTENT(IN)         :: er
INTEGER, INTENT(OUT)          :: ifault

INTERFACE
  SUBROUTINE f(nord, bp, q)
    USE as319
    IMPLICIT NONE
    INTEGER, INTENT(IN)           :: nord
    REAL (dp), INTENT(IN)         :: bp(:)
    REAL (dp), INTENT(OUT)        :: q
  END SUBROUTINE f
END INTERFACE

REAL (dp) :: h, f1
INTEGER   :: i, jc, jcmax

jcmax=npar - 2
jc = 0

DO i = 1, npar
  h =(ABS(b(i)) + SQRT(er)) * SQRT(er)
  sa(i) = b(i)
  b(i) = b(i) + h
  CALL f(npar, b, f1)
  b(i) = sa(i)
  
  IF(ler) THEN
    f1 = f0 + h
    jc = jc + 1
  END IF
  
  g(i) = (f1 - f0)/h
END DO

IF(jc > jcmax) ifault = 2
RETURN
END SUBROUTINE grad

END SUBROUTINE varme


!----------------------------------------------------------------------
PROGRAM  var
!----------------------------------------------------------------------
!       A PROGRAM TO IMPLEMENT A QUASI-NEWTON METHOD.
!       USING NEW ALGORITHM VARMET   JULY 1994
!----------------------------------------------------------------------

USE as319
IMPLICIT NONE
INTEGER, PARAMETER :: n=2
INTEGER            :: ier
INTEGER, SAVE      :: gradtl = 12, maxfn = 1000, mess = 6
REAL (dp)          :: x(n), xtmp(n), fp

INTERFACE
  SUBROUTINE fun(nord, bp, q)
    USE as319
    IMPLICIT NONE
    INTEGER, INTENT(IN)           :: nord
    REAL (dp), INTENT(IN)         :: bp(:)
    REAL (dp), INTENT(OUT)        :: q
  END SUBROUTINE fun

  SUBROUTINE varme(fun, npar, b, f0, nsig, maxfn, iout, ier)
    USE as319
    IMPLICIT NONE
    INTEGER, INTENT(IN)               :: npar
    REAL (dp), INTENT(IN OUT)         :: b(:)
    REAL (dp), INTENT(IN OUT)         :: f0
    INTEGER, INTENT(IN OUT)           :: nsig
    INTEGER, INTENT(IN OUT)           :: maxfn
    INTEGER, INTENT(IN OUT)           :: iout
    INTEGER, INTENT(OUT)              :: ier
    INTERFACE
      SUBROUTINE fun(nord, bp, q)
        USE as319
        IMPLICIT NONE
        INTEGER, INTENT(IN)           :: nord
        REAL (dp), INTENT(IN)         :: bp(:)
        REAL (dp), INTENT(OUT)        :: q
      END SUBROUTINE fun
    END INTERFACE
  END SUBROUTINE varme
END INTERFACE

WRITE(*,*)'  '
WRITE(*,*) 'INPUT YOUR STARTING GUESS: '
READ(*,*) xtmp(1:n)
WRITE(*,*)' '
WRITE(*,*)'INITIALIZATION COMPLETE.'
WRITE(*,*)'***************************************'

x(1:n)=xtmp(1:n)
ifn = 0
CALL varme(fun, n, x, fp, gradtl, maxfn, mess, ier)

WRITE(*,*)' '
IF (ier /= 0) WRITE(*, *) '** IER =', ier, ' **'

WRITE(*,*)' THE NUMBER OF FUNCTION EVALUATIONS IS ', ifn
WRITE(*,*)' '
WRITE(*,*)'THE MINIMUM FOUND IS ', x(1:n)
WRITE(*,*)' '
CALL fun(n, x, fp)
WRITE(*, *)'THE FUNCTION VALUE IS: ', fp
STOP
END PROGRAM  var
!----------------------------------------------------------------------

SUBROUTINE fun(nord, bp, q)
!----------------------------------------------------------------------

USE as319
IMPLICIT NONE
INTEGER, INTENT(IN)           :: nord
REAL (dp), INTENT(IN)         :: bp(:)
REAL (dp), INTENT(OUT)        :: q

q=100.*(bp(2) - bp(1)**2)**2 + (bp(1) - 1.)**2
ifn = ifn + 1
ler = .false.
IF (nord < 1) WRITE(*, *)'** NORD must be > 0, actual value =', nord
RETURN
END SUBROUTINE fun

