nlesolver_solver Subroutine

private subroutine nlesolver_solver(me, x)

Main solver.

Type Bound

nlesolver_type

Arguments

Type IntentOptional Attributes Name
class(nlesolver_type), intent(inout) :: me
real(kind=wp), intent(inout), dimension(:) :: x

Calls

proc~~nlesolver_solver~~CallsGraph proc~nlesolver_solver nlesolver_module::nlesolver_type%nlesolver_solver initialize initialize proc~nlesolver_solver->initialize lsmr_ez lsmr_ez proc~nlesolver_solver->lsmr_ez proc~linear_solver nlesolver_module::linear_solver proc~nlesolver_solver->proc~linear_solver proc~set_status nlesolver_module::nlesolver_type%set_status proc~nlesolver_solver->proc~set_status solve solve proc~nlesolver_solver->solve

Source Code

    subroutine nlesolver_solver(me,x)

    implicit none

    class(nlesolver_type),intent(inout) :: me
    real(wp),dimension(:),intent(inout) :: x

    real(wp),dimension(:)  ,allocatable :: fvec             !! function vector
    real(wp),dimension(:,:),allocatable :: fjac             !! jacobian matrix [dense]
    real(wp),dimension(:),  allocatable :: fjac_sparse      !! jacobian matrix [sparse]
    real(wp),dimension(:),  allocatable :: prev_fjac_sparse !! previous jacobian matrix [sparse]
    real(wp),dimension(:)  ,allocatable :: rhs              !! linear system right-hand side
    real(wp),dimension(:)  ,allocatable :: p                !! search direction
    real(wp),dimension(:)  ,allocatable :: xold             !! previous value of `x`
    real(wp),dimension(:)  ,allocatable :: prev_fvec        !! previous function vector
    real(wp),dimension(:,:),allocatable :: prev_fjac        !! previous jacobian matrix
    real(wp),dimension(:,:),allocatable :: delf             !! used for Broyden (rank 2 for `matmul`)
    real(wp),dimension(:,:),allocatable :: delx             !! used for Broyden (rank 2 for `matmul`)
    logical                             :: user_stop        !! user stop button flag
    integer                             :: info             !! status flag from the [[linear_solver]]
    integer                             :: iter             !! iteration counter
    real(wp)                            :: f                !! magnitude of `fvec`
    real(wp)                            :: fold             !! previous value of `f`
    integer                             :: n_uphill         !! number of steps taken in the "uphill" direction
                                                            !! (where `f` is increasing)
    real(wp)                            :: delxmag2         !! used for Broyden
    logical                             :: recompute_jac    !! if using Broyden, and we want to call the user
                                                            !! jacobian routine instead
    integer                             :: broyden_counter  !! number of times the broyden update has been used
    integer                             :: alloc_stat       !! allocation status flag
    type(lsqr_solver_ez) :: sparse_solver  !! sparse LSQR solver class
    type(lusol_settings) :: lusol_options
    integer :: i !! counter
    integer,dimension(:),allocatable :: idx, index_array !! for sparse indexing
    character(len=10) :: i_str !! string version of `i` for row string
    real(wp) :: normA, condA, normr, normAr, normx !! for LSMR
    integer :: itn !! for LSMR

    if (me%istat<0) return ! class was not initialized properly

    if (.not. associated(me%func)) then
        call me%set_status(istat = -10, string = 'Error: function routine is not associated')
        return
    end if
    if (me%sparsity_mode==NLESOLVER_SPARSITY_DENSE .and. .not. associated(me%grad)) then
        call me%set_status(istat = -11, string = 'Error: gradient routine is not associated')
        return
    end if
    if (me%sparsity_mode>NLESOLVER_SPARSITY_DENSE .and. .not. associated(me%grad_sparse)) then
        call me%set_status(istat = -11, string = 'Error: gradient routine is not associated')
        return
    end if

    ! initialize:
    iter            = 0
    n_uphill        = 0
    recompute_jac   = .false.
    broyden_counter = 0

    ! allocate the arrays:
    alloc_stat = 0
    if (alloc_stat==0) allocate(fvec     (me%m)      , stat=alloc_stat)

    if (me%sparsity_mode==NLESOLVER_SPARSITY_DENSE) then
        ! dense
        if (alloc_stat==0) allocate(fjac (me%m,me%n) , stat=alloc_stat)
    else
        ! sparse
        if (alloc_stat==0) allocate(fjac_sparse (me%n_nonzeros) , stat=alloc_stat)
        if (me%use_broyden .and. alloc_stat==0) allocate(prev_fjac_sparse(me%n_nonzeros) , stat=alloc_stat)
    end if

    if (alloc_stat==0) allocate(rhs      (me%m)      , stat=alloc_stat)
    if (alloc_stat==0) allocate(p        (me%n)      , stat=alloc_stat)
    if (alloc_stat==0) allocate(xold     (me%n)      , stat=alloc_stat)
    if (me%use_broyden) then
        ! only need these for broyden:
        if (alloc_stat==0) allocate(prev_fvec(me%m)      , stat=alloc_stat)
        if (me%sparsity_mode/=NLESOLVER_SPARSITY_DENSE .and. alloc_stat==0) then
            allocate(prev_fjac(me%m,me%n) , stat=alloc_stat)
            index_array = [(i, i=1,me%n_nonzeros)] ! an array to index the sparse jacobian elements
        end if
        if (alloc_stat==0) allocate(delf     (me%m,1)    , stat=alloc_stat)
        if (alloc_stat==0) allocate(delx     (me%n,1)    , stat=alloc_stat)
    end if
    if (alloc_stat/=0) then
        call me%set_status(istat = -9, string = 'Error: Out of memory')
        return
    else
        me%istat = -998
        me%message = 'Unknown error'
    end if

    ! evaluate the function:
    call me%func(x,fvec)
    f = norm2(fvec)

    ! check to see if initial guess is a root:
    if (f <= me%tol) then

        call me%set_status(istat = 1, string = 'Required accuracy achieved')

    else

        ! main iteration loop:
        do iter = 1,me%max_iter

            ! Export the current iteration:
            if (associated(me%export_iteration)) call me%export_iteration(x,fvec,iter)

            ! Check for user stop:
            if (associated(me%user_input_check)) then
                call me%user_input_check(user_stop)
                if (user_stop) then
                    call me%set_status(istat = 4, string = 'Stopped by the user')
                    exit
                end if
            end if

            if (me%use_broyden .and. .not. recompute_jac) then
                if (iter==1) then
                    ! always compute Jacobian on the first iteration
                    !call me%grad(x,fjac)
                    select case (me%sparsity_mode)
                    case (NLESOLVER_SPARSITY_DENSE)
                        call me%grad(x,fjac)
                    case default ! sparse modes
                        call me%grad_sparse(x,fjac_sparse)
                    end select
                    broyden_counter = 0
                else
                    ! and use Broyden update to estimate Jacobian
                    ! for subsequent iterations.

                    ! note: fvec was computed the last iteration
                    delx(:,1) = x - xold
                    delf(:,1) = fvec - prev_fvec

                    if (me%sparsity_mode==NLESOLVER_SPARSITY_DENSE) then ! dense

                        delxmag2 = dot_product(delx(:,1),delx(:,1))
                        if (delxmag2 < eps) then
                            call me%set_status(istat = -8, &
                                    string = 'Error: Divide by zero when computing Broyden update')
                            exit
                        end if

                        ! Jacobian estimate:
                        ! This is the equation from wikipedia : https://en.wikipedia.org/wiki/Broyden%27s_method
                        fjac = prev_fjac + matmul((delf-matmul(prev_fjac,delx)), transpose(delx)) / delxmag2

                        ! see also: eqn 4 in Schubert, which is different ...

                    else ! using a sparse option

                        ! Just a row-by-row version of the fjac equation above.
                        ! see L.K. Schubert, 1970
                        do i = 1, me%m

                            idx = pack(index_array, mask = me%irow==i) ! the nonzero elements in this row
                            if (size(idx)==0) cycle ! no non-zeros in this row

                            associate (dx => delx(me%icol(idx),:)) ! nonzero x vec for this row
                                delxmag2 = dot_product(dx(:,1),dx(:,1)) ! only those x's for this row
                                if (delxmag2 < eps) then
                                    write(i_str,'(I10)') i
                                    call me%set_status(istat = -8, &
                                            string = 'Error: Divide by zero when computing sparse Broyden update for row '//&
                                            trim(adjustl(i_str)))
                                    exit
                                end if
                                fjac_sparse(idx) = prev_fjac_sparse(idx) + &
                                        matmul((delf(i,1)-matmul(prev_fjac_sparse(idx),dx)), transpose(dx)) / delxmag2
                            end associate

                        end do

                    end if
                    broyden_counter = broyden_counter + 1
                end if
                prev_fvec = fvec
                if (me%sparsity_mode==NLESOLVER_SPARSITY_DENSE) then
                    prev_fjac = fjac
                else
                    prev_fjac_sparse = fjac_sparse
                end if
            else
                ! compute the jacobian:
                select case (me%sparsity_mode)
                case (NLESOLVER_SPARSITY_DENSE)
                    call me%grad(x,fjac)
                case default ! sparse
                    call me%grad_sparse(x,fjac_sparse)
                end select
                recompute_jac = .false.  ! for broyden
                broyden_counter = 0
            end if

            xold = x
            fold = f

            ! compute the search direction p by solving linear system:
            rhs = -fvec ! RHS of the linear system
            select case (me%sparsity_mode)

            case (NLESOLVER_SPARSITY_DENSE)
                ! use dense solver
                call linear_solver(me%m,me%n,fjac,rhs,p,info)

            case (NLESOLVER_SPARSITY_LSQR)
                ! initialize the LSQR sparse solver
                call sparse_solver%initialize(me%m,me%n,fjac_sparse,me%irow,me%icol,&
                                              atol   = me%atol, &
                                              btol   = me%btol, &
                                              conlim = me%conlim, &
                                              itnlim = me%itnlim, &
                                              nout   = me%nout)
                call sparse_solver%solve(rhs,me%damp,p,info) ! solve the linear system
                ! check convergence:
                select case (info)
                case(4)
                    call me%set_status(istat = -1004, &
                                string = 'LSQR Error: The system appears to be ill-conditioned. istop =', i=info)
                    exit
                case(5)
                    call me%set_status(istat = -1005, &
                                string = 'LSQR Error: The iteration limit was reached. istop =', i=info)
                    exit
                case default
                    info = 0
                end select

            case (NLESOLVER_SPARSITY_LUSOL)
                ! use lusol solver
                lusol_options%method = me%lusol_method
                call solve(me%n,me%m,me%n_nonzeros,me%irow,me%icol,fjac_sparse,rhs,p,info,&
                           settings=lusol_options)

            case (NLESOLVER_SPARSITY_LSMR)

                ! use LSMR solver:
                !me%conlim = 1.0_wp/(100*epsilon(1.0_wp))
                call lsmr_ez  ( me%m, me%n, me%irow, me%icol, fjac_sparse, rhs, me%damp, &
                                me%atol, me%btol, me%conlim, me%itnlim, me%localSize, me%nout, &
                                p, info, itn, normA, condA, normr, normAr, normx )

                ! check convergence:
                select case (info)
                case(4)
                    call me%set_status(istat = -1004, &
                                string = 'LSMR Error: The system appears to be ill-conditioned. istop =', i=info)
                    exit
                case(5)
                    call me%set_status(istat = -1005, &
                                string = 'LSMR Error: The iteration limit was reached. istop =', i=info)
                    exit
                case default
                    info = 0
                end select

            case (NLESOLVER_SPARSITY_CUSTOM_SPARSE)
                if (associated(me%custom_solver_sparse)) then
                    ! a user-provided sparse solver:
                    call me%custom_solver_sparse(me%n,me%m,me%n_nonzeros,me%irow,me%icol,fjac_sparse,rhs,p,info)
                else
                    call me%set_status(istat = -1006, &
                                string = 'Error: The custom_solver_sparse procedure has not been set.')
                    exit
                end if
            case default
                error stop 'invalid sparsity_mode'
            end select

            ! check for errors:
            if (info /= 0) then

                call me%set_status(istat = -6, string = 'Error solving linear system. info =', i=info)
                exit

            else

                ! next step, using the specified method:
                call me%linesearch(xold,p,x,f,fvec,fjac,fjac_sparse)

                ! keep track of the number of steps in the "uphill" direction:
                if (f>fold) then
                    n_uphill = n_uphill + 1
                else
                    n_uphill = 0
                end if

                ! check for stopping conditions
                if (f <= me%tol) then

                    call me%set_status(istat = 1, string = 'Required accuracy achieved')
                    exit

                elseif ( maxval(abs(x-xold)) <= me%tolx ) then

                    call me%set_status(istat = 2, string = 'Solution cannot be improved')
                    exit

                elseif (iter == me%max_iter) then

                    call me%set_status(istat = 3, string = 'Maximum number of iterations reached')
                    exit

                elseif (n_uphill > me%n_uphill_max) then

                    call me%set_status(istat = 5, string = 'Too many steps in the uphill direction')
                    exit

                elseif (me%use_broyden) then

                    ! If delxmag2 is too small when using broyden, just
                    ! call the user-supplied jacobian function to avoid
                    ! a divide by zero on the next step. This should
                    ! normally only happen when the solution is almost converged.
                    if (norm2(x-xold)**2 <= eps) then
                        recompute_jac = .true.
                    else
                        if (me%broyden_update_n>0) then
                            ! Note that we also recompute if we have taken an uphill step
                            if (broyden_counter==me%broyden_update_n .or. n_uphill>0) then
                                ! time to recompute the full jacobian
                                recompute_jac = .true.
                            end if
                        end if
                    end if

                endif

            end if

        end do   !end of iterations loop

    end if

    !Export the last iteration:
    iter = iter + 1
    if (associated(me%export_iteration)) call me%export_iteration(x,fvec,iter)

    end subroutine nlesolver_solver