LCOV - code coverage report
Current view: top level - src - mo_dds.F90 (source / functions) Hit Total Coverage
Test: forces coverage Lines: 149 227 65.6 %
Date: 2024-03-13 19:03:28 Functions: 3 3 100.0 %

          Line data    Source code
       1             : !> \file mo_dds.f90
       2             : !> \brief \copybrief mo_dds
       3             : !> \details \copydetails mo_dds
       4             : 
       5             : !> \brief Dynamically Dimensioned Search (DDS)
       6             : !> \details This module provides routines for Dynamically Dimensioned Search (DDS)
       7             : !! of Tolson and Shoemaker (2007). It searches the minimum or maximum of a user-specified function,
       8             : !! using an n-dimensional continuous global optimization algorithm (DDS).
       9             : !> \authors Bryan Tolson, modified by Rohini Kumar, Matthias Cuntz and Juliane Mai.
      10             : !> \date Jul 2012
      11             : !> \copyright Copyright 2005-\today, the CHS Developers, Sabine Attinger: All rights reserved.
      12             : !! FORCES is released under the LGPLv3+ license \license_note
      13             : module mo_dds
      14             : 
      15             :   IMPLICIT NONE
      16             : 
      17             :   PRIVATE
      18             : 
      19             :   PUBLIC :: DDS    ! Dynamically Dimensioned Search (DDS)
      20             :   PUBLIC :: MDDS   ! Modified Dynamically Dimensioned Search (DDS)
      21             : 
      22             :   ! ------------------------------------------------------------------
      23             : 
      24             : CONTAINS
      25             : 
      26             :   ! ------------------------------------------------------------------
      27             : 
      28             :   !>        \brief DDS
      29             : 
      30             :   !>        \details Searches Minimum or Maximum of a user-specified function using
      31             :   !!        Dynamically Dimensioned Search (DDS).\n
      32             :   !!        DDS is an n-dimensional continuous global optimization algorithm.
      33             :   !!        It is coded as a minimizer but one can give maxit=True in a maximization problem,
      34             :   !!        so that the algorithm minimizes the negative of the objective function F=(-1*F).
      35             :   !!
      36             :   !!        The function to be minimized is the first argument of DDS and must be defined as \n
      37             :   !!        \code{.f90}
      38             :   !!        function func(p)
      39             :   !!          use mo_kind, only: dp
      40             :   !!          implicit none
      41             :   !!          real(dp), dimension(:), intent(in) :: p
      42             :   !!          real(dp) :: func
      43             :   !!        end function func
      44             :   !!        \endcode
      45             :   !!
      46             :   !!        \b Example
      47             :   !!
      48             :   !!        \code{.f90}
      49             :   !!        dv_range(:,1) = (/ -600.0, -600.0, -600.0, -600.0, -600.0, -600.0, -600.0, -600.0, -600.0, -600.0 /)
      50             :   !!        dv_range(:,2) = (/ 600.0, 600.0, 600.0, 600.0, 600.0, 600.0, 600.0, 600.0, 600.0, 600.0 /)
      51             :   !!        dv_ini        = (/ -.226265E+01, -.130187E+01, -.151219E+01, 0.133983E+00, 0.988159E+00, &
      52             :   !!                           -.495074E+01, -.126574E+02, 0.572684E+00, 0.303864E+01, 0.343031E+01 /)
      53             :   !!        dv_opt = DDS(griewank, dv_ini, dv_range)
      54             :   !!        \endcode
      55             :   !!
      56             :   !!        See also example in test directory.
      57             :   !!
      58             :   !!        \b Literature
      59             :   !!        1. Tolson, B. A., and C. A. Shoemaker (2007)
      60             :   !!            _Dynamically dimensioned search algorithm for computationally efficient watershed
      61             :   !!            model calibration_, Water Resour. Res., 43, W01413, doi:10.1029/2005WR004723.
      62             :   !!
      63             :   !>        \param[in] "real(dp) :: obj_func(p)"             Function on which to search the minimum
      64             :   !>        \param[in] "real(dp) :: pini(:)"                 inital value of decision variables
      65             :   !>        \param[in] "real(dp) :: prange(size(pini),2)"    Min/max range of decision variables
      66             :   !>        \param[in] "real(dp), optional           :: r"                 DDS perturbation parameter\n
      67             :   !!                                                                       (default: 0.2)
      68             :   !>        \param[in] "integer(i8), optional        :: seed"              User seed to initialise the random number generator
      69             :   !!                                                                       (default: None)
      70             :   !>        \param[in] "integer(i8), optional        :: maxiter"           Maximum number of iteration or function evaluation
      71             :   !!                                                                       (default: 1000)
      72             :   !>        \param[in] "logical, optional            :: maxit"             Maximization (.True.) or
      73             :   !!                                                                       minimization (.False.) of function
      74             :   !!                                                                       (default: .False.)
      75             :   !>        \param[in] "logical, optional            :: mask(size(pini))"  parameter to be optimized (true or false)
      76             :   !!                                                                       (default: .True.)
      77             :   !>        \param[in]  "character(len=*) , optional :: tmp_file"          file with temporal output
      78             :   !>        \param[out] "real(dp), optional              :: funcbest"    the best value of the function.
      79             :   !>        \param[out] "real(dp), optional, allocatable :: history(:)"  the history of best function values,
      80             :   !!                                                                     history(maxiter)=funcbest\n
      81             :   !!                                          allocatable only to be in correspondance with other optimization routines
      82             :   !>        \retval "real(dp) :: DDS"   The parameters of the point which is estimated to minimize the function.
      83             : 
      84             :   !>        \author Bryan Tolson
      85             :   !>        \date Feb 2007
      86             : 
      87             :   !>        \author Rohini Kumar
      88             :   !>        \date Feb 2008
      89             : 
      90             :   !>        \author Matthias Cuntz & Juliane Mai
      91             :   !>        \date Jul 2012
      92             :   !!          - module
      93             : 
      94             :   !>        \author Juliane Mai
      95             :   !>        \date Aug 2012
      96             :   !!          - optional argument funcbest added
      97             :   !>        \date Nov 2012
      98             :   !!          - masked parameter
      99             :   !>        \date Dec 2012
     100             :   !!          - history output
     101             : 
     102             :   !>        \author Pallav Kumar Shrestha
     103             :   !>        \date Jun 2019
     104             :   !!          - Added "dds_results.out.current" output file with current iteration (non-updated) parameters
     105             : 
     106             : #ifdef MPI
     107             :   function DDS(eval, obj_func, pini, prange, r, seed, maxiter, maxit, mask, tmp_file, comm, funcbest, history)
     108             : #else
     109           4 :   function DDS(eval, obj_func, pini, prange, r, seed, maxiter, maxit, mask, tmp_file, funcbest, history)
     110             : #endif
     111             : 
     112             :     use mo_kind, only : i4, i8, dp
     113             :     use mo_xor4096, only : xor4096, xor4096g
     114             :     use mo_optimization_utils, only : eval_interface, objective_interface
     115             : #ifdef MPI
     116             :     use mpi_f08
     117             : #endif
     118             : 
     119             :     implicit none
     120             : 
     121             :     procedure(eval_interface), INTENT(IN), POINTER :: eval
     122             :     procedure(objective_interface), intent(in), pointer :: obj_func
     123             : 
     124             :     real(dp), dimension(:), intent(in) :: pini     ! inital value of decision variables
     125             :     real(dp), dimension(:, :), intent(in) :: prange   ! Min/max values of decision variables
     126             :     real(dp), optional, intent(in) :: r        ! DDS perturbation parameter (-> 0.2 by default)
     127             :     integer(i8), optional, intent(in) :: seed     ! User seed to initialise the random number generator
     128             :     integer(i8), optional, intent(in) :: maxiter  ! Maximum number of iteration or function evaluation
     129             :     logical, optional, intent(in) :: maxit    ! Maximization or minimization of function
     130             :     logical, dimension(:), optional, intent(in) :: mask     ! parameter to be optimized (true or false)
     131             :     character(len = *), optional, intent(in) :: tmp_file    ! file for temporal output
     132             : #ifdef MPI
     133             :     type(MPI_Comm), optional, intent(in)  :: comm                ! MPI communicator
     134             : #endif
     135             :     real(dp), optional, intent(out) :: funcbest ! Best value of the function.
     136             :     real(dp), dimension(:), optional, intent(out), &
     137             :             allocatable :: history  ! History of objective function values
     138             :     real(dp), dimension(size(pini)) :: DDS      ! Best value of decision variables
     139             : 
     140             :     ! Local variables
     141             :     integer(i4) :: pnum                   ! Total number of decision variables
     142             :     integer(i8) :: iseed                  ! User given seed
     143             :     integer(i8) :: imaxiter               ! Maximum number of iteration or function evaluation
     144           1 :     real(dp) :: ir                     ! DDS perturbation parameter
     145           1 :     real(dp) :: imaxit                 ! Maximization or minimization of function
     146           1 :     real(dp) :: of_new, of_best        ! intermediate results
     147           1 :     real(dp) :: Pn, new_value          ! intermediate results
     148          11 :     real(dp), dimension(size(pini)) :: pnew                   ! Test value of decision variables
     149           1 :     real(dp) :: ranval                 ! random value
     150             :     integer(i8) :: i                      ! maxiter=i8
     151             :     integer(i4) :: j, dvn_count, dv       ! pnum=i4
     152             :     integer(i4) :: idummy                 ! dummy vaiable
     153             :     integer, dimension(8) :: sdate                  ! date_and_time return
     154           1 :     logical, dimension(size(pini)) :: maske                  ! parameter to be optimized (true or false)
     155           1 :     integer(i4), dimension(:), allocatable :: truepara               ! parameter to be optimized (their indexes)
     156             : #ifdef MPI
     157             :     integer(i4) :: ierror
     158             :     logical :: do_obj_loop
     159             :     integer(i4) :: iproc, nproc
     160             : #endif
     161             : 
     162             :     ! Check input
     163           1 :     pnum = size(pini)
     164           1 :     if (size(prange, 1) /= pnum) stop 'Error DDS: size(prange,1) /= size(pini)'
     165           1 :     if (size(prange, 2) /= 2)    stop 'Error DDS: size(prange,2) /= 2'
     166             :     ! r Perturbation parameter
     167           1 :     ir = 0.2_dp
     168           1 :     if (present(r)) ir = r
     169           1 :     if (ir <= 0.0_dp .or. ir > 1.0_dp) stop 'Error DDS: DDS perturbation parameter (0.0, 1.0]'
     170             :     ! max. iteration
     171           1 :     imaxiter = 1000
     172           1 :     if (present(maxiter)) imaxiter = maxiter
     173           1 :     if (imaxiter < 6) stop 'Error DDS: max function evals must be minimum 6'
     174             :     ! history output
     175           1 :     if (present(history)) then
     176           0 :       allocate(history(imaxiter))
     177             :     end if
     178             :     ! Min or max objective function
     179           1 :     imaxit = 1.0_dp
     180           1 :     if (present(maxit)) then
     181           1 :       if (maxit) imaxit = -1.0_dp
     182             :     end if
     183             :     ! Given seed
     184           1 :     iseed = 0
     185           1 :     if (present(seed)) iseed = seed
     186           1 :     iseed = max(iseed, 0_i8)
     187             : 
     188           1 :     if (present(mask)) then
     189           0 :       if (count(mask) .eq. 0_i4) then
     190           0 :         stop 'Input argument mask: At least one element has to be true'
     191             :       else
     192           0 :         maske = mask
     193             :       end if
     194             :     else
     195          11 :       maske = .true.
     196             :     end if
     197             : 
     198          13 :     allocate (truepara(count(maske)))
     199           1 :     idummy = 0_i4
     200          11 :     do j = 1, size(pini, 1)
     201          11 :       if (maske(j)) then
     202          10 :         idummy = idummy + 1_i4
     203          10 :         truepara(idummy) = j
     204             :       end if
     205             :     end do
     206             : 
     207             :     ! Seed random numbers
     208           1 :     if (iseed == 0) then
     209           0 :       call date_and_time(values = sdate)
     210             :       iseed = sdate(1) * 31536000000_i8 + sdate(2) * 2592000000_i8 + sdate(3) * 86400000_i8 + &
     211           0 :               sdate(5) * 3600000_i8 + sdate(6) * 60000_i8 + sdate(7) * 1000_i8 + sdate(8)
     212           0 :       call xor4096(iseed, ranval)
     213           0 :       call xor4096g(iseed, ranval)
     214             :     else
     215           1 :       call xor4096(iseed, ranval)
     216           1 :       call xor4096g(iseed, ranval)
     217             :     end if
     218             : 
     219             :     ! Temporal file writing
     220           1 :     if(present(tmp_file)) then
     221           0 :       open(unit = 999, file = trim(adjustl(tmp_file)), action = 'write', status = 'unknown')
     222           0 :       write(999, *) '# settings :: general'
     223           0 :       write(999, *) '# nIterations    iseed'
     224           0 :       write(999, *) imaxiter, iseed
     225           0 :       write(999, *) '# settings :: dds specific'
     226           0 :       write(999, *) '# dds_r'
     227           0 :       write(999, *) ir
     228           0 :       write(999, *) '# iter   bestf   (bestx(j),j=1,nopt)'
     229           0 :       close(999)
     230             : 
     231             :       ! Second file writing with corresponding parameter set for each iteration rather than the current best
     232           0 :       open(unit = 998, file = trim(adjustl( tmp_file )) // ".current" , action = 'write', status = 'unknown')
     233           0 :       write(998, *) '# settings :: general'
     234           0 :       write(998, *) '# nIterations    iseed'
     235           0 :       write(998, *) imaxiter, iseed
     236           0 :       write(998, *) '# settings :: dds specific'
     237           0 :       write(998, *) '# dds_r'
     238           0 :       write(998, *) ir
     239           0 :       write(998, *) '# iter   bestf   (bestx(j),j=1,nopt)'
     240           0 :       close(998)
     241             :     end if
     242             : 
     243             :     ! Evaluate initial solution and return objective function value
     244             :     ! and Initialise the other variables (e.g. of_best)
     245             :     ! imaxit is 1.0 for MIN problems, -1 for MAX problems
     246          11 :     DDS = pini
     247           1 :     print *, imaxit
     248             : #ifdef MPI
     249             :     call MPI_Comm_size(comm, nproc, ierror)
     250             :     do iproc = 1, nproc-1
     251             :       do_obj_loop = .true.
     252             :       call MPI_Send(do_obj_loop,1, MPI_LOGICAL,iproc,0,comm,ierror)
     253             :     end do
     254             : #endif
     255           1 :     of_new = imaxit * obj_func(pini, eval)
     256           1 :     of_best = of_new
     257           1 :     if (present(history)) history(1) = of_new
     258             : 
     259           1 :     file_write : if (present(tmp_file)) then
     260           0 :       open(unit = 999, file = trim(adjustl(tmp_file)), action = 'write', position = 'append', recl = (pnum + 2) * 30)
     261             :       open(unit = 998, file = trim(adjustl( tmp_file )) // ".current" , action = 'write', position = 'append', &
     262           0 :         recl = (pnum + 2) * 30)
     263           0 :       if (imaxit .lt. 0.0_dp) then
     264             :         ! Maximize
     265           0 :         write(999, *) '0', -of_best, pini
     266           0 :         write(998, *) '0', -of_best, pini
     267             :       else
     268             :         ! Minimize
     269           0 :         write(999, *) '0', of_best, pini
     270           0 :         write(998, *) '0', of_best, pini
     271             :       end if
     272           0 :       close(999)
     273           0 :       close(998)
     274             :     end if file_write
     275             : 
     276             :     ! Code below is now the DDS algorithm as presented in Figure 1 of Tolson and Shoemaker (2007)
     277             : 
     278      100000 :     do i = 1, imaxiter - 1
     279             :       ! Determine Decision Variable (DV) selected for perturbation:
     280       99999 :       Pn = 1.0_dp - log(real(i, dp)) / log(real(imaxiter - 1, dp)) ! probability each DV selected
     281       99999 :       dvn_count = 0                                                 ! counter for how many DVs selected for perturbation
     282     1099989 :       pnew = DDS                                               ! define pnew initially as best current solution
     283             : 
     284             :       ! Step 3 of Fig 1 of Tolson and Shoemaker (2007)
     285     1099989 :       do j = 1, size(truepara) !pnum
     286      999990 :         call xor4096(0_i8, ranval)                           ! selects next uniform random number in sequence
     287             :         ! Step 4 of Fig 1 of Tolson and Shoemaker (2007)
     288     1099989 :         if (ranval < Pn) then                               ! jth DV selected for perturbation
     289       86800 :           dvn_count = dvn_count + 1
     290             :           ! call 1-D perturbation function to get new DV value (new_value)
     291       86800 :           call neigh_value(DDS(truepara(j)), prange(truepara(j), 1), prange(truepara(j), 2), ir, new_value)
     292       86800 :           pnew(truepara(j)) = new_value
     293             :         end if
     294             :       end do
     295             : 
     296             :       ! Step 3 of Fig 1 of Tolson and Shoemaker (2007) in case {N} empty
     297       99999 :       if (dvn_count == 0) then                               ! no DVs selected at random, so select one
     298       52256 :         call xor4096(0_i8, ranval)                           ! selects next uniform random number in sequence
     299       52256 :         dv = truepara(int((ranval * real(size(truepara), dp)) + 1.0_dp, i4))  ! index for one DV
     300             :         ! call 1-D perturbation function to get new DV value (new_value):
     301       52256 :         call neigh_value(DDS(dv), prange(dv, 1), prange(dv, 2), ir, new_value)
     302       52256 :         pnew(dv) = new_value                                ! change relevant DV value in stest
     303             :       end if
     304             : 
     305             :       ! Step 5 of Fig 1 of Tolson and Shoemaker (2007)
     306             :       ! Evaluate obj function value for test
     307             : #ifdef MPI
     308             :       call MPI_Comm_size(comm, nproc, ierror)
     309             :       do iproc = 1, nproc-1
     310             :         do_obj_loop = .true.
     311             :         call MPI_Send(do_obj_loop,1, MPI_LOGICAL,iproc,0,comm,ierror)
     312             :       end do
     313             : #endif
     314       99999 :       of_new = imaxit * obj_func(pnew, eval)                       ! imaxit handles min(=1) and max(=-1) problems
     315             :       ! update current best solution
     316       99999 :       if (of_new <= of_best) then
     317          45 :         of_best = of_new
     318         495 :         DDS = pnew
     319             :       end if
     320       99999 :       if (present(history)) history(i + 1) = of_best
     321             : 
     322      100000 :       file_write2 : if (present(tmp_file)) then
     323           0 :         open(unit = 999, file = trim(adjustl(tmp_file)), action = 'write', position = 'append', recl = (pnum + 2) * 30)
     324             :         open(unit = 998, file = trim(adjustl( tmp_file )) // ".current" , action = 'write', position = 'append', &
     325           0 :           recl = (pnum + 2) * 30)
     326           0 :         if (imaxit .lt. 0.0_dp) then
     327             :           ! Maximize
     328           0 :           write(999, *) i, -of_best, dds
     329           0 :           write(998, *) i, -of_new, pnew
     330             :         else
     331             :           ! Minimize
     332           0 :           write(999, *) i, of_best, dds
     333           0 :           write(998, *) i, of_new, pnew
     334             :         end if
     335           0 :         close(999)
     336           0 :         close(998)
     337             :       end if file_write2
     338             : 
     339             :     end do
     340           1 :     if (present(funcbest)) funcbest = of_best
     341             : #ifdef MPI
     342             :     call MPI_Comm_size(comm, nproc, ierror)
     343             :     do iproc = 1, nproc-1
     344             :       do_obj_loop = .false.
     345             :       call MPI_Send(do_obj_loop,1, MPI_LOGICAL,iproc,0,comm,ierror)
     346             :     end do
     347             : #endif
     348             : 
     349           1 :   end function DDS
     350             : 
     351             :   ! ------------------------------------------------------------------
     352             : 
     353             :   !>        \brief MDDS
     354             : 
     355             : 
     356             :   !>        \details Searches Minimum or Maximum of a user-specified function using the
     357             :   !!        Modified Dynamically Dimensioned Search (DDS).\n
     358             :   !!        DDS is an n-dimensional continuous global optimization algorithm.
     359             :   !!        It is coded as a minimizer but one can give maxit=True in a maximization problem,
     360             :   !!        so that the algorithm minimizes the negative of the objective function F=(-1*F).\n
     361             :   !!        The function to be minimized is the first argument of DDS and must be defined as
     362             :   !!        \code
     363             :   !!            function func(p)
     364             :   !!              use mo_kind, only: dp
     365             :   !!              implicit none
     366             :   !!              real(dp), dimension(:), intent(in) :: p
     367             :   !!              real(dp) :: func
     368             :   !!            end function func
     369             :   !!        \endcode
     370             :   !!
     371             :   !!       MDDS extents normal DDS by a continuous reduction of the DDS pertubation parameter r from 0.3 to 0.05,
     372             :   !!       and by allowing a larger function value with a certain probablity.
     373             : 
     374             :   !>        \param[in] "real(dp) :: obj_func(p)"             Function on which to search the minimum
     375             :   !>        \param[in] "real(dp) :: pini(:)"                 inital value of decision variables
     376             :   !>        \param[in] "real(dp) :: prange(size(pini),2)"    Min/max range of decision variables
     377             :   !>        \param[in] "integer(i8), optional        :: seed"              User seed to initialise the random number generator
     378             :   !!                                                                       (default: None)
     379             :   !>        \param[in] "integer(i8), optional        :: maxiter"           Maximum number of iteration or function evaluation
     380             :   !!                                                                       (default: 1000)
     381             :   !>        \param[in] "logical, optional            :: maxit"             Maximization (.True.) or
     382             :   !!                                                                       minimization (.False.) of function
     383             :   !!                                                                       (default: .False.)
     384             :   !>        \param[in] "logical, optional            :: mask(size(pini))"  parameter to be optimized (true or false)
     385             :   !!                                                                       (default: .True.)
     386             :   !>        \param[in]  "character(len=*) , optional :: tmp_file"          file with temporal output
     387             :   !>        \param[out] "real(dp), optional              :: funcbest"    the best value of the function.
     388             :   !>        \param[out] "real(dp), optional, allocatable :: history(:)"  the history of best function values,
     389             :   !!                                                                      history(maxiter)=funcbest\n
     390             :   !!                                          allocatable only to be in correspondance with other optimization routines
     391             :   !>        \return real(dp) :: MDDS  &mdash;  The parameters of the point which is estimated to minimize the function.
     392             : 
     393             :   !>     ## Restrictions
     394             :   !!         None.
     395             : 
     396             :   !>     ## Example
     397             :   !!
     398             :   !!         dv_range(:,1) = (/ -600.0, -600.0, -600.0, -600.0, -600.0, -600.0, -600.0, -600.0, -600.0, -600.0 /)
     399             :   !!         dv_range(:,2) = (/ 600.0, 600.0, 600.0, 600.0, 600.0, 600.0, 600.0, 600.0, 600.0, 600.0 /)
     400             :   !!         dv_ini        = (/ -.226265E+01, -.130187E+01, -.151219E+01, 0.133983E+00, 0.988159E+00, &
     401             :   !!                            -.495074E+01, -.126574E+02, 0.572684E+00, 0.303864E+01, 0.343031E+01 /)
     402             :   !!         dv_opt = MDDS(griewank, dv_ini, dv_range)
     403             :   !!
     404             :   !!     See also example in test directory.
     405             : 
     406             :   !>     ## Literature
     407             :   !!
     408             :   !!     1. Tolson, B. A., and C. A. Shoemaker (2007),
     409             :   !!         _Dynamically dimensioned search algorithm for computationally efficient watershed
     410             :   !!         model calibration_, Water Resour. Res., 43, W01413, doi:10.1029/2005WR004723.
     411             :   !!     2. Huang X-L and Xiong J (2010),
     412             :   !!         _Parameter Optimization of Multi-tank Model with Modified Dynamically Dimensioned
     413             :   !!         Search Algorithm_, Proceedings of the Third International Symposium on Computer
     414             :   !!         Science and Computational Technology(ISCSCT ''10), Jiaozuo, P. R. China,
     415             :   !!         14-15 August 2010, pp. 283-288\n
     416             : 
     417             :   !>        \author Written Matthias Cuntz and Juliane Mai
     418             :   !>        \date Aug 2012
     419             :   !         Modified, Juliane Mai,                  Nov 2012 - masked parameter
     420             :   !                   Juliane Mai,                  Dec 2012 - history output
     421             : 
     422           4 :   function MDDS(eval, obj_func, pini, prange, seed, maxiter, maxit, mask, tmp_file, funcbest, history)
     423             : 
     424           1 :     use mo_kind, only : i4, i8, dp
     425             :     use mo_xor4096, only : xor4096, xor4096g
     426             :     use mo_optimization_utils, only : eval_interface, objective_interface
     427             : 
     428             :     implicit none
     429             : 
     430             :     procedure(eval_interface), INTENT(IN), POINTER :: eval
     431             :     procedure(objective_interface), intent(in), pointer :: obj_func
     432             :     real(dp), dimension(:), intent(in) :: pini     ! inital value of decision variables
     433             :     real(dp), dimension(:, :), intent(in) :: prange   ! Min/max values of decision variables
     434             :     integer(i8), optional, intent(in) :: seed     ! User seed to initialise the random number generator
     435             :     integer(i8), optional, intent(in) :: maxiter  ! Maximum number of iteration or function evaluation
     436             :     logical, optional, intent(in) :: maxit    ! Maximization or minimization of function
     437             :     logical, dimension(:), optional, intent(in) :: mask     ! parameter to be optimized (true or false)
     438             :     character(len = *), optional, intent(in) :: tmp_file ! file for temporal output
     439             :     real(dp), optional, intent(out) :: funcbest ! Best value of the function.
     440             :     real(dp), dimension(:), optional, intent(out), &
     441             :             allocatable :: history  ! History of objective function values
     442             :     real(dp), dimension(size(pini)) :: MDDS     ! Best value of decision variables
     443             : 
     444             :     ! Local variables
     445             :     integer(i4) :: pnum                   ! Total number of decision variables
     446             :     integer(i8) :: iseed                  ! User given seed
     447             :     integer(i8) :: imaxiter               ! Maximum number of iteration or function evaluation
     448           1 :     real(dp) :: ir                     ! MDDS perturbation parameter
     449           1 :     real(dp) :: imaxit                 ! Maximization or minimization of function
     450           1 :     real(dp) :: of_new, of_best        ! intermediate results
     451           1 :     real(dp) :: Pn, new_value          ! intermediate results
     452          11 :     real(dp), dimension(size(pini)) :: pnew                   ! Test value of decision variables
     453           1 :     real(dp) :: ranval                 ! random value
     454             :     integer(i8) :: i                      ! maxiter=i8
     455             :     integer(i4) :: j, dvn_count, dv       ! pnum=i4
     456             :     integer(i4) :: idummy                 ! dummy vaiable
     457             :     integer, dimension(8) :: sdate                  ! date_and_time return
     458           1 :     logical, dimension(size(pini)) :: maske                  ! parameter to be optimized (true or false)
     459           1 :     integer(i4), dimension(:), allocatable :: truepara               ! parameter to be optimized (their indexes)
     460             : 
     461             : 
     462             :     ! Check input
     463           1 :     pnum = size(pini)
     464           1 :     if (size(prange, 1) /= pnum) stop 'Error MDDS: size(prange,1) /= size(pini)'
     465           1 :     if (size(prange, 2) /= 2)    stop 'Error MDDS: size(prange,2) /= 2'
     466             :     ! max. iteration
     467           1 :     imaxiter = 1000
     468           1 :     if (present(maxiter)) imaxiter = maxiter
     469           1 :     if (imaxiter < 6) stop 'Error MDDS: max function evals must be minimum 6'
     470             :     ! history output
     471           1 :     if (present(history)) then
     472           0 :       allocate(history(imaxiter))
     473             :     end if
     474             :     ! Min or max objective function
     475           1 :     imaxit = 1.0_dp
     476           1 :     if (present(maxit)) then
     477           1 :       if (maxit) imaxit = -1.0_dp
     478             :     end if
     479             :     ! Given seed
     480           1 :     iseed = 0
     481           1 :     if (present(seed)) iseed = seed
     482           1 :     iseed = max(iseed, 0_i8)
     483             : 
     484             :     ! Seed random numbers
     485           1 :     if (iseed == 0) then
     486           0 :       call date_and_time(values = sdate)
     487             :       iseed = sdate(1) * 31536000000_i8 + sdate(2) * 2592000000_i8 + sdate(3) * 86400000_i8 + &
     488           0 :               sdate(5) * 3600000_i8 + sdate(6) * 60000_i8 + sdate(7) * 1000_i8 + sdate(8)
     489           0 :       call xor4096(iseed, ranval)
     490           0 :       call xor4096g(iseed, ranval)
     491             :     else
     492           1 :       call xor4096(iseed, ranval)
     493           1 :       call xor4096g(iseed, ranval)
     494             :     end if
     495             : 
     496             :     ! Masked parameters
     497           1 :     if (present(mask)) then
     498           0 :       if (count(mask) .eq. 0_i4) then
     499           0 :         stop 'Input argument mask: At least one element has to be true'
     500             :       else
     501           0 :         maske = mask
     502             :       end if
     503             :     else
     504          11 :       maske = .true.
     505             :     end if
     506             : 
     507          13 :     allocate (truepara(count(maske)))
     508           1 :     idummy = 0_i4
     509          11 :     do j = 1, size(pini, 1)
     510          11 :       if (maske(j)) then
     511          10 :         idummy = idummy + 1_i4
     512          10 :         truepara(idummy) = j
     513             :       end if
     514             :     end do
     515             : 
     516             :     ! Temporal file writing
     517           1 :     if(present(tmp_file)) then
     518           0 :       open(unit = 999, file = trim(adjustl(tmp_file)), action = 'write', status = 'unknown')
     519           0 :       write(999, *) '# settings :: general'
     520           0 :       write(999, *) '# nIterations    iseed'
     521           0 :       write(999, *) imaxiter, iseed
     522           0 :       write(999, *) '# settings :: mdds specific'
     523           0 :       write(999, *) '# None'
     524           0 :       write(999, *) ''
     525           0 :       write(999, *) '# iter   bestf   (bestx(j),j=1,nopt)'
     526           0 :       close(999)
     527             :     end if
     528             : 
     529             :     ! Evaluate initial solution and return objective function value
     530             :     ! and Initialise the other variables (e.g. of_best)
     531             :     ! imaxit is 1.0 for MIN problems, -1 for MAX problems
     532          11 :     MDDS = pini
     533           1 :     of_new = imaxit * obj_func(pini, eval)
     534           1 :     of_best = of_new
     535           1 :     if (present(history)) history(1) = of_new
     536             : 
     537           1 :     file_write : if (present(tmp_file)) then
     538           0 :       open(unit = 999, file = trim(adjustl(tmp_file)), action = 'write', position = 'append', recl = (pnum + 2) * 30)
     539           0 :       if (imaxit .lt. 0.0_dp) then
     540             :         ! Maximize
     541           0 :         write(999, *) '0', -of_best, mdds
     542             :       else
     543             :         ! Minimize
     544           0 :         write(999, *) '0', of_best, mdds
     545             :       end if
     546           0 :       close(999)
     547             :     end if file_write
     548             : 
     549             :     ! Code below is now the MDDS algorithm as presented in Figure 1 of Tolson and Shoemaker (2007)
     550             : 
     551      100000 :     do i = 1, imaxiter - 1
     552             :       ! Determine Decision Variable (DV) selected for perturbation:
     553       99999 :       Pn = 1.0_dp - log(real(i, dp)) / log(real(imaxiter - 1, dp)) ! probability each DV selected
     554       99999 :       dvn_count = 0                                                 ! counter for how many DVs selected for perturbation
     555     1099989 :       pnew = MDDS                                               ! define pnew initially as best current solution
     556             :       ! Modifications by Huang et al. (2010)
     557       99999 :       Pn = max(Pn, 0.05_dp)
     558       99999 :       ir = max(min(0.3_dp, Pn), 0.05_dp)
     559             : 
     560             :       ! Step 3 of Fig 1 of Tolson and Shoemaker (2007)
     561     1099989 :       do j = 1, pnum
     562      999990 :         call xor4096(0_i8, ranval)                           ! selects next uniform random number in sequence
     563             :         ! Step 4 of Fig 1 of Tolson and Shoemaker (2007)
     564     1099989 :         if (ranval < Pn) then                               ! jth DV selected for perturbation
     565       98852 :           dvn_count = dvn_count + 1
     566             :           ! call 1-D perturbation function to get new DV value (new_value)
     567       98852 :           call neigh_value(MDDS(truepara(j)), prange(truepara(j), 1), prange(truepara(j), 2), ir, new_value)
     568       98852 :           pnew(truepara(j)) = new_value
     569             :         end if
     570             :       end do
     571             : 
     572             :       ! Step 3 of Fig 1 of Tolson and Shoemaker (2007) in case {N} empty
     573       99999 :       if (dvn_count == 0) then                               ! no DVs selected at random, so select one
     574       43387 :         call xor4096(0_i8, ranval)                           ! selects next uniform random number in sequence
     575       43387 :         dv = truepara(int((ranval * real(size(truepara), dp)) + 1.0_dp, i4))  ! index for one DV
     576             :         ! call 1-D perturbation function to get new DV value (new_value):
     577       43387 :         call neigh_value(MDDS(dv), prange(dv, 1), prange(dv, 2), ir, new_value)
     578       43387 :         pnew(dv) = new_value                                ! change relevant DV value in stest
     579             :       end if
     580             : 
     581             :       ! Step 5 of Fig 1 of Tolson and Shoemaker (2007)
     582             :       ! Evaluate obj function value for test
     583       99999 :       of_new = imaxit * obj_func(pnew, eval)                       ! imaxit handles min(=1) and max(=-1) problems
     584             :       ! update current best solution
     585       99999 :       if (of_new <= of_best) then
     586         500 :         of_best = of_new
     587        5500 :         MDDS = pnew
     588             :       else ! Modifications by Huang et al. (2010)
     589       99499 :         call xor4096(0_i8, ranval)
     590       99499 :         if (exp(-(of_new - of_best) / of_best) > (1.0_dp - ranval * Pn)) then
     591         502 :           of_best = of_new
     592        5522 :           MDDS = pnew
     593             :         end if
     594             :       end if
     595       99999 :       if (present(history)) then
     596           0 :         if (present(maxit)) then
     597           0 :           if (maxit) then
     598           0 :             history(i + 1) = max(history(i), of_best)
     599             :           else
     600           0 :             history(i + 1) = min(history(i), of_best)
     601             :           end if
     602             :         else
     603           0 :           history(i + 1) = min(history(i), of_best)
     604             :         end if
     605             :       end if
     606             : 
     607      100000 :       file_write2 : if (present(tmp_file)) then
     608           0 :         open(unit = 999, file = trim(adjustl(tmp_file)), action = 'write', position = 'append', recl = (pnum + 2) * 30)
     609           0 :         if (imaxit .lt. 0.0_dp) then
     610             :           ! Maximize
     611           0 :           write(999, *) i, -of_best, mdds
     612             :         else
     613             :           ! Minimize
     614           0 :           write(999, *) i, of_best, mdds
     615             :         end if
     616           0 :         close(999)
     617             :       end if file_write2
     618             : 
     619             :     end do
     620           1 :     if (present(funcbest)) funcbest = of_best
     621             : 
     622           1 :   end function MDDS
     623             : 
     624             :   ! ------------------------------------------------------------------
     625             : 
     626             :   ! Purpose is to generate a neighboring decision variable value for a single
     627             :   !  decision variable value being perturbed by the DDS optimization algorithm.
     628             :   !  New DV value respects the upper and lower DV bounds.
     629             :   !  Coded by Bryan Tolson, Nov 2005.
     630             : 
     631             :   ! I/O variable definitions:
     632             :   !  x_cur     - current decision variable (DV) value
     633             :   !  x_min     - min DV value
     634             :   !  x_max     - max DV value
     635             :   !  r         - the neighborhood perturbation factor
     636             :   !  new_value - new DV variable value (within specified min and max)
     637             : 
     638      281295 :   subroutine neigh_value(x_cur, x_min, x_max, r, new_value)
     639             : 
     640           1 :     use mo_kind, only : i8, dp
     641             :     use mo_xor4096, only : xor4096g
     642             : 
     643             :     implicit none
     644             : 
     645             :     real(dp), intent(in) :: x_cur, x_min, x_max, r
     646             :     real(dp), intent(out) :: new_value
     647      281295 :     real(dp) :: x_range
     648      281295 :     real(dp) :: zvalue
     649             : 
     650      281295 :     x_range = x_max - x_min
     651             : 
     652             :     ! generate a standard normal random variate (zvalue)
     653      281295 :     call xor4096g(0_i8, zvalue)
     654             : 
     655             :     ! calculate new decision variable value:
     656      281295 :     new_value = x_cur + zvalue * r * x_range
     657             : 
     658             :     !  check new value is within bounds. If not, bounds are reflecting.
     659      281295 :     if (new_value < x_min) then
     660        1777 :       new_value = x_min + (x_min - new_value)
     661        1777 :       if (new_value > x_max) then
     662             :         ! if reflection goes past x_max then value should be x_min since
     663             :         ! without reflection the approach goes way past lower bound.
     664             :         ! This keeps x close to lower bound when x_cur is close to lower bound
     665             :         ! Practically speaking, this should never happen with r values <0.3.
     666           0 :         new_value = x_min
     667             :       end if
     668      279518 :     else if (new_value > x_max) then
     669        1837 :       new_value = x_max - (new_value - x_max)
     670        1837 :       if (new_value < x_min) then
     671             :         ! if reflection goes past x_min then value should be x_max for same reasons as above.
     672           0 :         new_value = x_max
     673             :       end if
     674             :     end if
     675             : 
     676      281295 :   end subroutine neigh_value
     677             : 
     678             :   ! ------------------------------------------------------------------
     679             : 
     680             : end module mo_dds

Generated by: LCOV version 1.16