Last commit for src/predict.F90: 5874abaa643d4472a2aa9d1c5dbe454dadbd8d1f

Initial commit of the AENET code.

Bruno Mundim [2017-01-02 17:48:39]
Initial commit of the AENET code.
!-----------------------------------------------------------------------
!       predict.f90 - predict atomic energies of input structure
!-----------------------------------------------------------------------
!+ This file is part of the AENET package.
!+
!+ Copyright (C) 2012-2016 Nongnuch Artrith and Alexander Urban
!+
!+ This program is free software: you can redistribute it and/or modify
!+ it under the terms of the GNU General Public License as published by
!+ the Free Software Foundation, either version 3 of the License, or
!+ (at your option) any later version.
!+
!+ This program is distributed in the hope that it will be useful, but
!+ WITHOUT ANY WARRANTY; without even the implied warranty of
!+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
!+ General Public License for more details.
!+
!+ You should have received a copy of the GNU General Public License
!+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
!-----------------------------------------------------------------------
! 2011-11-17 Alexander Urban (AU), Nongnuch Artrith (NA)
!-----------------------------------------------------------------------

program predict

  use aeio,      only: aeio_header,                    &
                       aeio_timestamp,                 &
                       aeio_print_copyright

  use aenet,     only: aenet_init,                     &
                       aenet_final,                    &
                       aenet_atomic_energy,            &
                       aenet_atomic_energy_and_forces, &
                       aenet_convert_atom_types,       &
                       aenet_free_atom_energy,         &
                       aenet_load_potential,           &
                       aenet_print_info,               &
                       aenet_Rc_min, aenet_Rc_max,     &
                       aenet_nnb_max

  use constants, only: PI

  use geometry,  only: geo_init,                       &
                       geo_final,                      &
                       pbc,                            &
                       latticeVec,                     &
                       recLattVec,                     &
                       geo_update_bounds,              &
                       origin,                         &
                       nAtoms,                         &
                       nTypes,                         &
                       atomType,                       &
                       atomTypeName,                   &
                       cooLatt

  use input,     only: InputData,                      &
                       read_InpPredict

  use io,        only: io_adjustl

  use lclist,    only: lcl_init,                       &
                       lcl_final,                      &
                       lcl_nmax_nbdist,                &
                       lcl_nbdist_cart

  use optimize,  only: opt_init,                       &
                       opt_final,                      &
                       opt_optimize_coords

  use parallel,  only: pp_init,                        &
                       pp_final,                       &
                       pp_bcast,                       &
                       pp_bcast_coo,                   &
                       pp_print_info,                  &
                       pp_bcast_InputData,             &
                       pp_bcast_latt,                  &
                       pp_sum,                         &
                       ppMaster, ppRank, ppSize

  implicit none

  !--------------------------------------------------------------------!
  ! A '*' in front of the variable name means that it is a broadcasted !
  ! variable and has the same value on each process.  A '+' means that !
  ! an array is allocated on all parallel processes, but does not      !
  ! necessarily have the same contents.                                !
  !                                                                    !
  !----------------------------- general ------------------------------!
  ! inp             structure with input data                          !
  ! inFile          name of the input file                             !
  !                                                                    !
  !---------------------------- structures ----------------------------!
  !*nFiles          number of input files/structures                   !
  ! cooFile         file name of structure file (atomic coordinates)   !
  !                                                                    !
  !------------------------------ output ------------------------------!
  ! Ecoh            cohesive energy of the current structure           !
  ! Etot            total energy                                       !
  !+forCart         cartesian atomic forces of the current structure   !
  !--------------------------------------------------------------------!

  type(InputData)                               :: inp

  character(len=1024)                           :: inFile, cooFile, strucFile

  integer,          dimension(:),   allocatable :: atomType_orig

  integer                                       :: istruc, nStrucs

  double precision                              :: Ecoh, Etot, E0
  double precision, dimension(:,:), allocatable :: forCart

  double precision, dimension(3)                :: F_mav, F_max, F_avg
  double precision                              :: F_rms, F_rms_prev
  double precision, dimension(3)                :: dmax
  integer                                       :: imax

  integer                                       :: iter, stat
  logical                                       :: conv


  !-------------------------- initialization --------------------------!

  call initialize(inFile, strucFile, inp)

  if (ppMaster .and. (inp%verbosity > 0)) call aenet_print_info()

  ! number of structures to calculate
  if (len_trim(strucFile)>0) then
     nStrucs = 1
  else
     if (inp%nStrucs <= 0) then
        if (ppMaster) then
           write(0,*) "Error: no input structures specified in ", &
                      trim(inFile)
        end if
        call finalize()
        stop
     else
        nStrucs = inp%nStrucs
     end if
  end if

  !--------- loop over all the structures from the input file ---------!

  if (ppMaster .and. (inp%verbosity > 0)) then
     call aeio_header('Energy evaluation')
     write(*,*)
  end if

  do istruc = 1, nStrucs

     iter = 0

     ! only the master process reads the input structure:
     if (ppMaster) then
        if (len_trim(strucFile)>0) then
           cooFile = strucFile
        else
           cooFile = inp%strucFile(istruc)
        end if
        call geo_init(cooFile, 'xsf')
        ! convert atom type IDs to ANN potential IDs
        allocate(atomType_orig(nAtoms))
        atomType_orig = atomType
        call aenet_convert_atom_types(&
             atomTypeName, atomType_orig, atomType, stat)
     end if
     call pp_bcast(nAtoms)
     call pp_bcast(nTypes)
     call pp_bcast(pbc)
     call pp_bcast_latt(latticeVec)
     call pp_bcast_latt(recLattVec)

     ! allocate memory for other parallel processes and transfer data
     if (.not. ppMaster) then
        allocate(cooLatt(3,nAtoms),   &
                 atomType(nAtoms))
     end if
     if (inp%do_forces) then
        allocate(forCart(3,nAtoms))
     end if
     call pp_bcast_coo(cooLatt, nAtoms)
     call pp_bcast(atomType, nAtoms)

     ! write out basic structure info
     if (ppMaster .and. (inp%verbosity > 0)) then
        call print_fileinfo(istruc, cooFile, latticeVec, nAtoms, nTypes)
     end if

     ! evaluate atomic energy and forces
     if (inp%do_forces) then
        call get_energy(latticeVec, nAtoms, cooLatt, atomType, pbc, &
                        Ecoh, Etot, forCart=forCart)
     else
        call get_energy(latticeVec, nAtoms, cooLatt, atomType, pbc, &
                        Ecoh, Etot)
     end if

     if (ppMaster) then
        if (inp%do_forces) then
           call print_coordinates(iter, latticeVec, nAtoms, nTypes, cooLatt, &
                                  atomType_orig, atomTypeName, origin, forCart)
        else
           call print_coordinates(iter, latticeVec, nAtoms, nTypes, cooLatt, &
                                  atomType_orig, atomTypeName, origin)
        end if
     end if

     ! optimize coordinates, if requested:
     if (inp%do_relax .and. inp%do_forces) then
        E0 = Ecoh
        if (ppMaster) then
           call opt_init(inp%relax_method, 3*nAtoms, &
                         ftol=inp%relax_E_conv, gtol=inp%relax_F_conv)
           if (inp%verbosity > 0) then
              write(*,*) "Geometry optimization:"
              write(*,*)
              write(*,*) '       ', '   energy change  ', '     rms force    '
              write(*,*) '       ', '        (eV)      ', '     (eV/Ang)     '
              write(*,*) repeat('-',60)
           end if
        end if
        dmax = matmul(recLattVec, inp%relax_dmax)/(2.0d0*PI)
        conv = .false.
        relax : do while(iter < inp%relax_steps)
           iter = iter + 1
           if (ppMaster) then
              call calc_rms_force(forCart, F_mav, F_max, imax, F_avg, F_rms)
              if (inp%verbosity > 0) then
                 if (iter>1) then
                    write(*,'(1x,I5,2x,F16.8,2x,F16.8,2x,F16.8)') &
                         iter, Ecoh-E0, F_rms, F_rms - F_rms_prev
                 else
                    write(*,'(1x,I5,2x,F16.8,2x,F16.8)') iter, Ecoh-E0, F_rms
                 end if
              end if
              F_rms_prev = F_rms
              call opt_optimize_coords(Ecoh, nAtoms, cooLatt, forCart, &
                                       conv, dmax=(/0.1d0, 0.1d0, 0.1d0/))
              if (.not. pbc) then
                 call geo_update_bounds(cooLatt, latticeVec, recLattVec, origin)
              end if
           end if
           call pp_bcast(conv)
           if (conv) then
              if (ppMaster .and. (inp%verbosity > 0)) then
                 write(*,*) "   converged after ", &
                            trim(io_adjustl(iter)), " iterations."
              end if
              exit relax
           end if
           call pp_bcast_coo(cooLatt, nAtoms)
           call get_energy(latticeVec, nAtoms, cooLatt, atomType, pbc, &
                           Ecoh, Etot, forCart=forCart)
        end do relax
        if (ppMaster) then
           call opt_final()
           write(*,*)
           call print_coordinates(iter, latticeVec, nAtoms, nTypes, cooLatt, &
                                  atomType_orig, atomTypeName, origin, forCart)
        end if
     end if

     if (ppMaster) then
        if (inp%do_forces) then
           call print_energy(Ecoh, Etot, forCart)
        else
           call print_energy(Ecoh, Etot)
        end if
        call geo_final()
        deallocate(atomType_orig)
     else
        deallocate(cooLatt, atomType)
     end if
     if (inp%do_forces) then
        deallocate(forCart)
     end if

     if (ppMaster .and. istruc < nStrucs) then
        write(*,*) repeat('+', 70)
        write(*,*)
     end if

  end do

  !--------------------------- finalization ---------------------------!

  call finalize()

contains

  subroutine initialize(inFile, strucFile, inp)

    implicit none

    character(len=*), intent(out) :: inFile
    character(len=*), intent(out) :: strucFile
    type(InputData),  intent(out) :: inp


    logical :: fexists
    integer :: nargs
    integer :: stat
    integer :: itype

    call pp_init()

    if (ppMaster) then
       nargs = command_argument_count()
       if (nargs < 1) then
          write(0,*) "Error: No input file specified."
          call print_usage()
          call finalize()
          stop
       end if

       call get_command_argument(1, value=inFile)
       inquire(file=trim(inFile), exist=fexists)
       if (.not. fexists) then
          write(0,*) "Error: File not found: ", trim(inFile)
          call print_usage()
          call finalize()
          stop
       end if

       ! read name of structure from command line, if present
       if (nargs > 1) then
          call get_command_argument(2, value=strucFile)
       else
          strucFile = ''
       end if

       ! read general input file
       inp = read_InpPredict(inFile)
    end if
    call pp_bcast(inFile)
    call pp_bcast(strucFile)
    call pp_bcast_InputData(inp)

    if (inp%verbosity > 0) call pp_print_info()

    ! initialize aenet
    call aenet_init(inp%typeName, stat)
    if (stat /= 0) then
       write(0,*) 'Error: aenet initialization failed'
       call finalize()
       stop
    end if

    ! load ANN potentials
    do itype = 1, inp%nTypes
       call aenet_load_potential(itype, inp%netFile(itype), stat)
       if (stat /= 0) then
       write(0,*) 'Error: could not load ANN potentials'
          call finalize()
          stop
       end if
    end do

    if (ppMaster .and. (inp%verbosity > 0)) then
       ! write header and copyright info
       call aeio_header("Atomic Energy Network Interpolation", char='=')
       call aeio_header(aeio_timestamp(), char=' ')
       write(*,*)
       call aeio_print_copyright('2015-2016', 'Nongnuch Artrith and Alexander Urban')
    end if

  end subroutine initialize

  !--------------------------------------------------------------------!

  subroutine finalize()

    implicit none

    integer :: stat

    if (allocated(atomType_orig)) deallocate(atomType_orig)

    if (ppMaster .and. (inp%verbosity > 0)) then
       call aeio_header(aeio_timestamp(), char=' ')
       call aeio_header("Atomic Energy Network done.", char='=')
    end if

    call aenet_final(stat)
    call pp_final()

 end subroutine finalize

  !--------------------------------------------------------------------!

  subroutine print_usage()

    implicit none

    write(*,*)
    write(*,*) "predict.x -- Predict/interpolate atomic energy."
    write(*,'(1x,70("-"))')
    write(*,*) 'Usage: predict.x <input-file> [<structure files>]'
    write(*,*)
    write(*,*) 'See the documentation or the source code for a description of the'
    write(*,*) 'input file format.  Structure files can either be listed in the'
    write(*,*) 'input file, or specified on the command line.'
    write(*,*)

  end subroutine print_usage

  !--------------------------------------------------------------------!
  !                           general output                           !
  !--------------------------------------------------------------------!

  subroutine print_fileinfo(istruc, file, latticeVec, nAtoms, nTypes)

    implicit none

    integer,                                         intent(in) :: istruc
    character(len=*),                                intent(in) :: file
    double precision, dimension(3,3),                intent(in) :: latticeVec
    integer,                                         intent(in) :: nAtoms
    integer,                                         intent(in) :: nTypes

    write(*,*) 'Structure number  : ', trim(io_adjustl(istruc))
    write(*,*) 'File name         : ', trim(adjustl(file))
    write(*,*) 'Number of atoms   : ', trim(io_adjustl(nAtoms))
    write(*,*) 'Number of species : ', trim(io_adjustl(nTypes))
    write(*,*)

    write(*,*) 'Lattice vectors (Angstrom):'
    write(*,*)
    write(*,'(3x,"a = ( ",3(2x,F15.8)," )")') latticeVec(1:3,1)
    write(*,'(3x,"b = ( ",3(2x,F15.8)," )")') latticeVec(1:3,2)
    write(*,'(3x,"c = ( ",3(2x,F15.8)," )")') latticeVec(1:3,3)
    write(*,*)

  end subroutine print_fileinfo

  !--------------------------------------------------------------------!

  subroutine print_coordinates(iter, latticeVec, nAtoms, nTypes, cooLatt, &
                               atomType, atomTypeName, origin, forCart)

    implicit none

    integer,                                         intent(in) :: iter
    double precision, dimension(3,3),                intent(in) :: latticeVec
    integer,                                         intent(in) :: nAtoms
    integer,                                         intent(in) :: nTypes
    double precision, dimension(3,nAtoms),           intent(in) :: cooLatt
    integer,          dimension(nAtoms),             intent(in) :: atomType
    character(len=*), dimension(nTypes),             intent(in) :: atomTypeName
    double precision, dimension(3),                  intent(in) :: origin
    double precision, dimension(3,nAtoms), optional, intent(in) :: forCart

    character(len=80)              :: header
    integer                        :: iat
    character(len=2)               :: symbol
    double precision, dimension(3) :: cooCart

    header = 'Cartesian atomic coordinates'
    if (iter == 0) then
       header = trim(header) // ' (input)'
    else
       header = trim(header) // ' (optimized)'
    end if
    if (present(forCart)) then
       header = trim(header) // ' and corresponding atomic forces'
    end if
    header = trim(header) // ':'

    write(*,*) trim(header)
    write(*,*)
    write(*,'(1x,2x,3(2x,A12))', advance='no') &
         '     x      ', '     y      ', '     z      '
    if (present(forCart)) then
       write(*,'(3(2x,A12))') '     Fx     ', '    Fy      ', '    Fz      '
    else
       write(*,*)
    end if
    write(*,'(1x,2x,3(2x,A12))', advance='no') &
         '    (Ang)   ', '    (Ang)   ', '    (Ang)   '
    if (present(forCart)) then
       write(*,'(3(2x,A12))') '  (eV/Ang)  ', '  (eV/Ang)  ', '  (eV/Ang)  '
    else
       write(*,*)
    end if
    write(*,'(1x,44("-"))', advance='no')
    if (present(forCart)) then
       write(*,'(42("-"))')
    else
       write(*,*)
    end if
    do iat = 1, nAtoms
       symbol = atomTypeName(atomType(iat))
       cooCart(1:3) = matmul(latticeVec, cooLatt(1:3,iat)) + origin
       write(*,'(1x,A2,3(2x,F12.6))', advance='no') symbol, cooCart(1:3)
       if (present(forCart)) then
          write(*,'(3(2x,F12.6))') forCart(1:3,iat)
       else
          write(*,*)
       end if
    end do
    write(*,*)

  end subroutine print_coordinates

  !--------------------------------------------------------------------!

  subroutine print_energy(Ecoh, Etot, forCart)

    implicit none

    double precision,                           intent(in) :: Ecoh, Etot
    double precision, dimension(:,:), optional, intent(in) :: forCart

    double precision, dimension(3) :: F_mav, F_max, F_avg
    double precision               :: F_rms
    integer                        :: imax

    write(*,'(1x,"Cohesive energy            :",2x,F20.8," eV")') Ecoh
    write(*,'(1x,"Total energy               :",2x,F20.8," eV")') Etot
    if (present(forCart)) then
       call calc_rms_force(forCart, F_mav, F_max, imax, F_avg, F_rms)
       write(*,'(1x,"Mean force (must be zero)  :",3(2x,F12.6))') F_avg(1:3)
       write(*,'(1x,"Mean absolute force        :",3(2x,F12.6))') F_mav(1:3)
       write(*,'(1x,"Maximum force              :",3(2x,F12.6))') F_max(1:3)
       write(*,'(1x,"RMS force                  :",2x,F12.6)')    F_rms
       write(*,*) 'The maximum force is acting on atom ', &
            trim(io_adjustl(imax)), '.'
       write(*,*) 'All forces are given in eV/Angstrom.'
    end if
    write(*,*)

  end subroutine print_energy

  !--------------------------------------------------------------------!
  !                 calculate atomic energy and forces                 !
  !--------------------------------------------------------------------!

  subroutine get_energy(latticeVec, nAtoms, cooLatt, atomType, &
                        pbc, Ecoh, Etot, forCart)

    implicit none

    double precision, dimension(3,3),                intent(in)  :: latticeVec
    integer,                                         intent(in)  :: nAtoms
    double precision, dimension(3,nAtoms),           intent(in)  :: cooLatt
    integer,          dimension(nAtoms),             intent(in)  :: atomType
    logical,                                         intent(in)  :: pbc
    double precision,                                intent(out) :: Ecoh
    double precision,                                intent(out) :: Etot
    double precision, dimension(3,nAtoms), optional, intent(out) :: forCart

#ifdef CHECK_FORCES
    double precision, dimension(3,nAtoms) :: forCart_num
    double precision                              :: E_i1, E_i2
    double precision :: d
    double precision, dimension(3,3) :: dd
    integer :: i, j
#endif

    logical                                       :: do_F

    integer                                       :: nnb
    integer,          dimension(aenet_nnb_max)    :: nblist
    double precision, dimension(3,aenet_nnb_max)  :: nbcoo
    double precision, dimension(aenet_nnb_max)    :: nbdist
    integer,          dimension(aenet_nnb_max)    :: nbtype

    integer                                       :: type_i
    double precision, dimension(3)                :: coo_i
    double precision                              :: E_i

    integer                                       :: iatom, stat

    do_F = .false.
    if (present(forCart)) do_F = .true.
    if (do_F) forCart(1:3,1:nAtoms) = 0.0d0

    call lcl_init(aenet_Rc_min, aenet_Rc_max, latticeVec, nAtoms, &
                  atomType, cooLatt, pbc)

#ifdef CHECK_FORCES
    d = 0.01d0
    dd(:,1) = [d, 0.0d0, 0.0d0]
    dd(:,2) = [0.0d0, d, 0.0d0]
    dd(:,3) = [0.0d0, 0.0d0, d]
    forCart_num = 0.0d0
#endif

    Ecoh = 0.0d0
    Etot = 0.0d0
    atoms : do iatom = 1, nAtoms

       ! distribute atoms over processes:
       if (mod(iatom-1,ppSize) /= ppRank) cycle

       type_i = atomType(iatom)
       coo_i(1:3) = matmul(latticeVec, cooLatt(1:3,iatom))

       ! get all atoms of species type_i within the cut-off:
       nnb = aenet_nnb_max
       call lcl_nbdist_cart(iatom, nnb, nbcoo, nbdist, aenet_Rc_max, &
                            nblist=nblist, nbtype=nbtype)

       if (do_F) then
          call aenet_atomic_energy_and_forces( &
               coo_i, type_i, iatom, nnb, nbcoo, nbtype, nblist, &
               nAtoms, E_i, forCart, stat)
#ifdef CHECK_FORCES
          do i = 1, 3
             coo_i = coo_i - dd(:,i)
             call aenet_atomic_energy(coo_i, type_i, nnb, nbcoo, nbtype, &
                                   E_i1, stat)
             coo_i = coo_i + 2.0d0*dd(:,i)
             call aenet_atomic_energy(coo_i, type_i, nnb, nbcoo, nbtype, &
                                   E_i2, stat)
             coo_i = coo_i - dd(:,i)
             forCart_num(i,iatom) = forCart_num(i,iatom) - (E_i2 - E_i1)/(2.0d0*d)
          end do
          do j = 1, nnb
             do i = 1, 3
                nbcoo(:,j) = nbcoo(:,j) - dd(:,i)
                call aenet_atomic_energy(coo_i, type_i, nnb, nbcoo, nbtype, &
                                         E_i1, stat)
                nbcoo(:,j) = nbcoo(:,j) + 2.0d0*dd(:,i)
                call aenet_atomic_energy(coo_i, type_i, nnb, nbcoo, nbtype, &
                                         E_i2, stat)
                nbcoo(:,j) = nbcoo(:,j) - dd(:,i)
                forCart_num(i,nblist(j)) = forCart_num(i,nblist(j)) - (E_i2 - E_i1)/(2.0d0*d)
             end do
          end do
#endif
       else
          call aenet_atomic_energy(coo_i, type_i, nnb, nbcoo, nbtype, &
                                   E_i, stat)
       end if

       Etot = Etot + E_i
       Ecoh = Ecoh + E_i - aenet_free_atom_energy(type_i)

    end do atoms

#ifdef CHECK_FORCES
    open(99, file='CHECK_FORCES.dat', status='replace', action='write')
    do iatom = 1, nAtoms
       write(99,'(9(1x,ES15.8))') &
            forCart(1:3,iatom), forCart_num(1:3,iatom), &
            forCart(1:3,iatom) - forCart_num(1:3,iatom)
    end do
    close(99)
#endif

    call lcl_final()

    call pp_sum(Ecoh)
    call pp_sum(Etot)

    if (do_F) then
       if (ppSize>1) then
          ! gather results from all processes
          do iatom = 1, nAtoms
             call pp_sum(forCart(1:3,iatom), 3)
          end do
       end if
    end if

  end subroutine get_energy

  !--------------------------------------------------------------------!
  !                       analyze atomic forces                        !
  !--------------------------------------------------------------------!

  subroutine calc_rms_force(forCart, F_mav, F_max, imax, F_avg, F_rms)

    implicit none

    double precision, dimension(:,:), optional, intent(in)  :: forCart
    double precision, dimension(3),             intent(out) :: F_mav
    double precision, dimension(3),             intent(out) :: F_max
    integer,                                    intent(out) :: imax
    double precision, dimension(3),             intent(out) :: F_avg
    double precision,                           intent(out) :: F_rms

    integer                        :: nAtoms
    double precision               :: F_abs2, F_abs2_max
    integer                        :: iat

    nAtoms = size(forCart(1,:))
    F_rms       = 0.0d0
    F_mav(1:3)  = 0.0d0
    F_max(1:3)  = 0.0d0
    F_avg(1:3)  = 0.0d0
    F_abs2      = 0.0d0
    F_abs2_max  = 0.0d0
    do iat = 1, nAtoms
       F_avg(1:3) = F_avg(1:3) + forCart(1:3,iat)
       F_mav(1:3) = F_mav(1:3) + abs(forCart(1:3,iat))
       F_abs2 = sum(forCart(1:3,iat)*forCart(1:3,iat))
       F_rms  = F_rms + F_abs2
       if (F_abs2 > F_abs2_max) then
          F_abs2_max  = F_abs2
          F_max(1:3) = forCart(1:3,iat)
          imax       = iat
       end if
    end do
    F_avg(1:3) = F_avg(1:3)/dble(nAtoms)
    F_mav(1:3) = F_mav(1:3)/dble(nAtoms)
    F_rms = sqrt(F_rms/dble(nAtoms))

  end subroutine calc_rms_force

end program predict
ViewGit