!> Quaim Potential
!! -------------------
!!
!! V(r_{ij}) = B1 * exp(-\eta1 r_{ij})+B2 * exp(-\eta2 \rho_{ij})+B3 * exp(-\eta3 \rho_{ij})
!!           - \frac{C}{r_{ij}^6} * [1 - (sum_{k=0}^{6} \frac{(damp_dd*r_{ij})^k}{k!})*exp(-damp_dd*r_{ij})]
!!           - \frac{D}{r_{ij}^8} * [1 - (sum_{l=0}^{8} \frac{(damp_dq*r_{ij})^l}{l!})*exp(-damp_dq*r_{ij})]
!!
module MW_daim
#ifndef MW_SERIAL
   use MPI
#endif
   use MW_timers
   use MW_kinds, only: wp
   use MW_box, only: MW_box_t
   use MW_electrode, only: MW_electrode_t
   use MW_ion, only: MW_ion_t
   use MW_localwork, only: MW_localwork_t
   use MW_parallel, only: MW_COMM_WORLD
   use MW_molecule, only: MW_molecule_t
   implicit none
   private

   ! Public type
   ! -----------
   public :: MW_daim_t

   ! Public subroutines
   ! ------------------
   public :: define_type
   public :: void_type
   public :: print_type
   public :: set_rcut
   public :: melt_forces
   public :: stress_tensor_tail_correction
   public :: energy
   public :: selfenergy
   public :: diffselfenergy
   public :: repulsionenergy
   public :: diffrepulsionenergy
!  public :: melt_fix_molecule_forces
!  public :: fix_molecule_energy

   ! Data structure to handle daim potentials
   type MW_daim_t
      integer :: num_species = 0                            ! number of species
      real(wp) :: rcut = 0.0_wp                             ! cut-off parameter
      real(wp) :: rcutsq = 0.0_wp                           ! cut-off parameter squared
      real(wp), dimension(:,:), allocatable :: eta1          ! (num_species,num_species) exponential factor
      real(wp), dimension(:,:), allocatable :: eta2          ! (num_species,num_species) exponential factor
      real(wp), dimension(:,:), allocatable :: eta3          ! (num_species,num_species) exponential factor
      real(wp), dimension(:,:), allocatable :: B1            ! (num_species,num_species) exponential scaling factor
      real(wp), dimension(:,:), allocatable :: B2            ! (num_species,num_species) exponential scaling factor
      real(wp), dimension(:,:), allocatable :: B3            ! (num_species,num_species) exponential scaling factor
      real(wp), dimension(:,:), allocatable :: C            ! (num_species,num_species) dipole-dipole van der Waals attractive term
      real(wp), dimension(:,:), allocatable :: D            ! (num_species,num_species) dipole-quadripole van de Waals attractive termp
      real(wp), dimension(:,:), allocatable :: damp_dd      ! (num_species,num_species) dipole-dipole damping factor
      real(wp), dimension(:,:), allocatable :: damp_dq      ! (num_species,num_species) dipole-quadripole damping factor
      logical :: daim_3D_tail_correction = .false.           ! Flag to turn on the dispersion terms tail correction in 3D
   end type MW_daim_t

contains

   !================================================================================
   ! Define the data structure
   subroutine define_type(this, num_species, rcut)
      use MW_errors, only: &
            MW_errors_allocate_error => allocate_error, &
            MW_errors_parameter_error => parameter_error
      implicit none
      ! Parameters
      ! ---------_
      type(MW_daim_t), intent(inout) :: this      !> structure to be defined
      integer, intent(in) :: num_species                    !> number of species
      real(wp), intent(in) :: rcut

      ! Local
      ! -----
      integer :: ierr

      call set_rcut(this, rcut)

      if (num_species <= 0) then
         call MW_errors_parameter_error("define_type", "daim.f90",&
               "num_species", num_species)
      end if

      this%num_species = num_species

      allocate(this%eta1(num_species,num_species), &
            this%eta2(num_species,num_species), &
            this%eta3(num_species,num_species), &
            this%B1(num_species,num_species), &
            this%B2(num_species,num_species), &
            this%B3(num_species,num_species), &
            this%C(num_species,num_species), &
            this%D(num_species,num_species), &
            this%damp_dd(num_species,num_species), &
            this%damp_dq(num_species,num_species), stat=ierr)
      if (ierr /= 0) &
            call MW_errors_allocate_error("define_type", "daim.f90", ierr)
      this%eta1(:,:) = 0.0_wp
      this%eta2(:,:) = 0.0_wp
      this%eta3(:,:) = 0.0_wp
      this%B1(:,:) = 0.0_wp
      this%B2(:,:) = 0.0_wp
      this%B3(:,:) = 0.0_wp
      this%C(:,:) = 0.0_wp
      this%D(:,:) = 0.0_wp
      this%damp_dd(:,:) = 0.0_wp
      this%damp_dq(:,:) = 0.0_wp

   end subroutine define_type

   !================================================================================
   ! Void the data structure
   subroutine void_type(this)
      use MW_errors, only: MW_errors_deallocate_error => deallocate_error
      implicit none
      type(MW_daim_t), intent(inout) :: this

      integer :: ierr

      if (allocated(this%eta1)) then
         deallocate(this%eta1, this%eta2, this%eta3, this%B1, &
            this%B2, this%B3, this%C, this%D, this%damp_dd, this%damp_dq, stat=ierr)
         if (ierr /= 0) &
               call MW_errors_deallocate_error("define_type", "daim.f90", ierr)
      end if

      this%num_species = 0
      this%rcut = 0.0_wp
      this%rcutsq = 0.0_wp
   end subroutine void_type

   ! ================================================================================
   ! set rcut value
   subroutine set_rcut(this, rcut)
      implicit none
      type(MW_daim_t), intent(inout) :: this
      real(wp), intent(in) :: rcut

      this%rcut = rcut
      this%rcutsq = rcut*rcut
   end subroutine set_rcut

   !================================================================================
   ! Print the data structure to ounit
   subroutine print_type(this, species_names, ounit)
      use MW_kinds, only: PARTICLE_NAME_LEN
      implicit none
      type(MW_daim_t), intent(in) :: this               ! structure to be printed
      character(PARTICLE_NAME_LEN),   intent(in) :: species_names(:)   ! names of the species
      integer,                   intent(in) :: ounit              ! output unit

      integer :: i, j

      write(ounit, '("|daim| cut-off distance: ",es12.5)') this%rcut
      write(ounit, '("|daim| ",8x,1x,8x,1x,"    eta1     ",1x,"    eta2     ",1x,'// &
            '"    eta3     ",1x,"     B1     ",1x,"     B2     ",1x,"     B3     ",1x,'// &
            '"     C      ",1x,"     D      ",1x,"  damp_dd   ",1x,"  damp_dq   ",1x)')

      do j = 1, this%num_species
         do i = j, this%num_species
            write(ounit, '("|daim| ",a8,1x,a8,10(1x,es12.5))') &
                  species_names(i), species_names(j), this%eta1(i,j),this%eta2(i,j), &
                  this%eta3(i,j), this%B1(i,j),this%B2(i,j),this%B3(i,j),&
                  this%C(i,j), this%D(i,j), this%damp_dd(i,j), this%damp_dq(i,j)
         end do
      end do

   end subroutine print_type

   ! ==============================================================================
   ! Compute daim forces felt by melt ions
   subroutine melt_forces(num_pbc, localwork, daim, box, ions, xyz_ions, &
         electrodes, xyz_elec, force,stress_tensor, delta, epsilon, compute_force)
      implicit none

      ! Parameters in
      ! -------------
      integer, intent(in) :: num_pbc !< number of periodic boundary conditions
      type(MW_localwork_t), intent(in) :: localwork !< Local work assignment
      type(MW_daim_t), intent(in) :: daim      !< daim potential parameters
      type(MW_box_t),   intent(in) :: box     !< Simulation box parameters

      type(MW_ion_t), intent(in) :: ions(:)        !< ions parameters
      real(wp),       intent(in) :: xyz_ions(:,:)  !< ions xyz positions

      type(MW_electrode_t), intent(inout) :: electrodes(:)  !< electrode parameters
      real(wp),             intent(in) :: xyz_elec(:,:)  !< electrode atoms xyz positions
      real(wp), intent(in) :: delta(:) !< Deltas of ions
      real(wp), intent(in) :: epsilon(:,:) !< Epsilons of ions
      logical,              intent(in) :: compute_force !< compute_force on electrodes

      ! Parameters out
      ! --------------
      real(wp), intent(inout) :: force(:,:) !< DAIM force on ions
      real(wp), intent(inout) :: stress_tensor(:,:) !< DAIM force contribution to the stress tensor

      ! Local
      ! -----
      integer :: ierr, count, i, num_elec_types

      select case(num_pbc)
      case (2)
         call melt_forces_2DPBC(localwork, daim, box, ions, xyz_ions, &
               electrodes, xyz_elec, force,stress_tensor, delta, epsilon)
      case(3)
         call melt_forces_3DPBC(localwork, daim, box, ions, xyz_ions, &
         electrodes, xyz_elec, force,stress_tensor, delta, epsilon)
      end select
#ifndef MW_SERIAL
      count = size(force,1) * size(force,2)
      call MPI_Allreduce(MPI_IN_PLACE, force, count, MPI_DOUBLE_PRECISION, MPI_SUM, MW_COMM_WORLD, ierr)

      count = size(stress_tensor,1)*size(stress_tensor,2)      ! this should always be 9
      call MPI_Allreduce(MPI_IN_PLACE, stress_tensor, count, MPI_DOUBLE_PRECISION, MPI_SUM, MW_COMM_WORLD, ierr)     

      if (compute_force) then
         num_elec_types = size(electrodes, 1)
         do i = 1, num_elec_types
            call MPI_Allreduce(MPI_IN_PLACE, electrodes(i)%force_ions(:,10), 3, &
                  MPI_DOUBLE_PRECISION, MPI_SUM, MW_COMM_WORLD, ierr)
         end do
      end if
#endif
   end subroutine melt_forces


   ! ==============================================================================
   ! Compute tail correction to the stress tensor 
   subroutine stress_tensor_tail_correction(daim, box, ions,tail_correction)
      use MW_constants, only : pi     
      implicit none

      ! Parameters in
      ! -------------
      type(MW_daim_t), intent(in) :: daim      !< Fumi-Tosi potential parameters
      type(MW_box_t),   intent(in) :: box     !< Simulation box parameters

      type(MW_ion_t), intent(in) :: ions(:)        !< ions parameters

      ! Parameters out
      ! --------------
      real(wp), intent(inout) :: tail_correction !< tail correction to the stress tensor

      ! Local
      ! -----
      integer :: itype,jtype,num_ion_types
      integer :: count_n,count_m
      real(wp) :: rcutsq,rcut,C_ij,D_ij

      num_ion_types = size(ions,1)

      rcutsq = daim%rcutsq
      rcut=sqrt(rcutsq)
      tail_correction=0.0_wp
      do itype = 1, num_ion_types
         count_n = ions(itype)%count
!        do jtype =  itype, num_ion_types
         do jtype =  1, num_ion_types
            count_m = ions(jtype)%count
            C_ij = daim%C(itype, jtype)
            D_ij = daim%D(itype, jtype)
            tail_correction=tail_correction- &
                    2.0*pi*count_m*count_n/product(box%length(:))*(C_ij*2.0/(rcut**3.0)+D_ij*(8.0/5.0)/(rcut**5.0))
         end do
      end do

   end subroutine stress_tensor_tail_correction   

   ! ==============================================================================
   ! Compute Quaim potential felt by melt ions
   subroutine energy(num_pbc, localwork, daim, box, ions, xyz_ions, &
         electrodes, xyz_atoms,delta,epsilon, h)
      implicit none

      ! Parameters in
      ! -------------
      integer, intent(in) :: num_pbc !< number of periodic boundary conditions
      type(MW_localwork_t), intent(in) :: localwork !< Local work assignment
      type(MW_daim_t), intent(in) :: daim      !< daim potential parameters
      type(MW_box_t),   intent(in) :: box     !< Simulation box parameters

      type(MW_ion_t), intent(in) :: ions(:)        !< ions parameters
      real(wp),       intent(in) :: xyz_ions(:,:)  !< ions xyz positions

      type(MW_electrode_t), intent(in) :: electrodes(:)  !< electrode parameters
      real(wp),             intent(in) :: xyz_atoms(:,:)  !< electrode atoms xyz positions
      real(wp), intent(in) :: delta(:) !< Deltas of ions
      real(wp), intent(in) :: epsilon(:,:) !< Epsilons of ions

      ! Parameters out
      ! --------------
      real(wp), intent(inout) :: h       !< Potential energy due to L-J interactions

      ! Local
      ! -----
      integer :: ierr

      select case(num_pbc)
      case (2)
         call energy_2DPBC(localwork, daim, box, ions, xyz_ions, &
         electrodes, xyz_atoms,delta,epsilon, h)
      case(3)
         call energy_3DPBC(localwork, daim, box, ions, xyz_ions, &
         electrodes, xyz_atoms,delta,epsilon, h)
      end select
#ifndef MW_SERIAL
      call MPI_Allreduce(MPI_IN_PLACE, h, 1, MPI_DOUBLE_PRECISION, MPI_SUM, MW_COMM_WORLD, ierr)
#endif
   end subroutine energy

   !================================================================================
   subroutine selfenergy(num_ion_types,ions,delta,epsilon,selfenergydaim)
      use MW_kinds, only: wp
      use MW_ion, only: MW_ion_t
      implicit none
      ! Parameters
      ! ----------
      type(MW_ion_t), intent(in) :: ions(:)        !< ions parameters
      integer, intent(in) :: num_ion_types
      real(wp), intent(in) :: delta(:) !< Deltas of ions
      real(wp), intent(in) :: epsilon(:,:) !< Epsilons of ions

      real(wp), intent(inout) :: selfenergydaim

      ! Local
      ! -----
      integer :: iion, itype, i, offset
      real(wp) :: daimD,daimbeta,daimzeta,epsilonsq

      selfenergydaim = 0.0_wp
      do itype = 1, num_ion_types
         if (ions(itype)%selfdaimD>0.0_wp) then
            daimD=ions(itype)%selfdaimD
            daimbeta=ions(itype)%selfdaimbeta
            daimzeta=ions(itype)%selfdaimzeta
            offset = ions(itype)%offset
            do i = 1, ions(itype)%count
               iion = offset + i
               selfenergydaim = selfenergydaim  &
                  +daimD*(dexp(-daimbeta*delta(iion))+dexp(daimbeta*delta(iion)))
               epsilonsq=epsilon(iion,1)**2.0_wp+ epsilon(iion,2)**2.0_wp+epsilon(iion,3)**2.0_wp
               selfenergydaim = selfenergydaim  &
                  +dexp(daimzeta*daimzeta*epsilonsq)-1.0_wp   !  selfeps**2 in pimaim?
            end do
         end if
      end do

   end subroutine selfenergy


   !================================================================================
   subroutine diffselfenergy(num_ion_types,ions,delta,epsilon,selfenergydaim,xi)
      use MW_kinds, only: wp
      use MW_ion, only: MW_ion_t
      implicit none
      ! Parameters
      ! ----------
      type(MW_ion_t), intent(in) :: ions(:)        !< ions parameters
      integer, intent(in) :: num_ion_types
      real(wp), intent(in) :: delta(:) !< Deltas of ions
      real(wp), intent(in) :: epsilon(:,:) !< Epsilons of ions

      real(wp), intent(inout) :: selfenergydaim
      real(wp), intent(inout) :: xi(:)       !< Derivative of the energy w.r.t. the d.o.f.

      ! Local
      ! -----
      integer :: iion, itype, i, offset,num
      real(wp) :: daimD,daimbeta,daimzeta,epsilonsq,daimzeta2

      selfenergydaim = 0.0_wp
      xi=0.0_wp
      num=ions(num_ion_types)%offset+ions(num_ion_types)%count
      do itype = 1, num_ion_types
         if (ions(itype)%selfdaimD>0.0_wp) then
            daimD=ions(itype)%selfdaimD
            daimbeta=ions(itype)%selfdaimbeta
            daimzeta=ions(itype)%selfdaimzeta
            daimzeta2=daimzeta*daimzeta
            offset = ions(itype)%offset
            do i = 1, ions(itype)%count
               iion = offset + i
               selfenergydaim = selfenergydaim  &
                  +daimD*(dexp(-daimbeta*delta(iion))+dexp(daimbeta*delta(iion)))
               epsilonsq=epsilon(iion,1)**2.0_wp+ epsilon(iion,2)**2.0_wp+epsilon(iion,3)**2.0_wp
               selfenergydaim = selfenergydaim  &
                  +dexp(daimzeta2*epsilonsq)-1.0_wp   !  selfeps**2 in pimaim?
               xi(iion)=daimD*daimbeta*(-dexp(-daimbeta*delta(iion))+dexp(daimbeta*delta(iion)))
               xi(num+iion)=2.0_wp*epsilon(iion,1)*daimzeta2*dexp(daimzeta2*epsilonsq)
               xi(2*num+iion)=2.0_wp*epsilon(iion,2)*daimzeta2*dexp(daimzeta2*epsilonsq)
               xi(3*num+iion)=2.0_wp*epsilon(iion,3)*daimzeta2*dexp(daimzeta2*epsilonsq)
            end do
         end if
      end do

   end subroutine diffselfenergy

   ! ==============================================================================
   ! Compute daim potential felt by melt ions
   subroutine repulsionenergy(num_pbc, localwork, daim, box, ions, xyz_ions, &
         electrodes, xyz_atoms,delta,epsilon, h)
      implicit none

      ! Parameters in
      ! -------------
      integer, intent(in) :: num_pbc !< number of periodic boundary conditions
      type(MW_localwork_t), intent(in) :: localwork !< Local work assignment
      type(MW_daim_t), intent(in) :: daim      !< daim potential parameters
      type(MW_box_t),   intent(in) :: box     !< Simulation box parameters

      type(MW_ion_t), intent(in) :: ions(:)        !< ions parameters
      real(wp),       intent(in) :: xyz_ions(:,:)  !< ions xyz positions

      type(MW_electrode_t), intent(in) :: electrodes(:)  !< electrode parameters
      real(wp),             intent(in) :: xyz_atoms(:,:)  !< electrode atoms xyz positions
      real(wp), intent(in) :: delta(:) !< Deltas of ions
      real(wp), intent(in) :: epsilon(:,:) !< Epsilons of ions

      ! Parameters out
      ! --------------
      real(wp), intent(inout) :: h       !< Potential energy due to L-J interactions
!     real(wp), intent(inout) :: xi(:)       !< Derivative of the energy w.r.t. the d.o.f.

      ! Local
      ! -----
      integer :: ierr!, count

      select case(num_pbc)
      case (2)
         call repulsionenergy_2DPBC(localwork, daim, box, ions, xyz_ions, &
         electrodes, xyz_atoms,delta,epsilon, h)
      case(3)
         call repulsionenergy_3DPBC(localwork, daim, box, ions, xyz_ions, &
         electrodes, xyz_atoms,delta,epsilon, h)
      end select
#ifndef MW_SERIAL
!     count=size(xi,1)
      call MPI_Allreduce(MPI_IN_PLACE, h, 1, MPI_DOUBLE_PRECISION, MPI_SUM, MW_COMM_WORLD, ierr)
!     call MPI_Allreduce(MPI_IN_PLACE, xi, count, MPI_DOUBLE_PRECISION, MPI_SUM, MW_COMM_WORLD, ierr)
#endif
   end subroutine repulsionenergy

   ! ==============================================================================
   ! Compute daim potential felt by melt ions
   subroutine diffrepulsionenergy(num_pbc, localwork, daim, box, ions, xyz_ions, &
         electrodes, xyz_atoms,delta,epsilon, h,xi)
      implicit none

      ! Parameters in
      ! -------------
      integer, intent(in) :: num_pbc !< number of periodic boundary conditions
      type(MW_localwork_t), intent(in) :: localwork !< Local work assignment
      type(MW_daim_t), intent(in) :: daim      !< daim potential parameters
      type(MW_box_t),   intent(in) :: box     !< Simulation box parameters

      type(MW_ion_t), intent(in) :: ions(:)        !< ions parameters
      real(wp),       intent(in) :: xyz_ions(:,:)  !< ions xyz positions

      type(MW_electrode_t), intent(in) :: electrodes(:)  !< electrode parameters
      real(wp),             intent(in) :: xyz_atoms(:,:)  !< electrode atoms xyz positions
      real(wp), intent(in) :: delta(:) !< Deltas of ions
      real(wp), intent(in) :: epsilon(:,:) !< Epsilons of ions

      ! Parameters out
      ! --------------
      real(wp), intent(inout) :: h       !< Potential energy due to L-J interactions
      real(wp), intent(inout) :: xi(:)       !< Derivative of the energy w.r.t. the d.o.f.

      ! Local
      ! -----
      integer :: ierr,count

      h=0.0_wp
      xi=0.0_wp
      select case(num_pbc)
      case (2)
         call diffrepulsionenergy_2DPBC(localwork, daim, box, ions, xyz_ions, &
         electrodes, xyz_atoms,delta,epsilon, h,xi)
      case(3)
         call diffrepulsionenergy_3DPBC(localwork, daim, box, ions, xyz_ions, &
         electrodes, xyz_atoms,delta,epsilon, h,xi)
      end select
#ifndef MW_SERIAL
      count=size(xi,1)
      call MPI_Allreduce(MPI_IN_PLACE, h, 1, MPI_DOUBLE_PRECISION, MPI_SUM, MW_COMM_WORLD, ierr)
      call MPI_Allreduce(MPI_IN_PLACE, xi, count, MPI_DOUBLE_PRECISION, MPI_SUM, MW_COMM_WORLD, ierr)
#endif
   end subroutine diffrepulsionenergy



   ! ================================================================================
   ! Function to compute force coeffcient between 2 particles
   !
   ! ft_ij = (dV(r_ij) / dr_ij) * (1/ r_ij)
   function force_ij(dx,dy,dz,drnorm2,deltai,deltaj,epsilonix,epsiloniy,epsiloniz,&
            epsilonjx,epsilonjy,epsilonjz,eta1_ij, eta2_ij, eta3_ij, &
            B1_ij, B2_ij, B3_ij, C_ij, D_ij, damp_dd_ij, damp_dq_ij) result(ft_ij)
      implicit none
      ! Passed in
      ! ---------
      real(wp), intent(in) :: dx,dy,dz,drnorm2
      real(wp), intent(in) :: deltai,deltaj
      real(wp), intent(in) :: epsilonix,epsiloniy,epsiloniz
      real(wp), intent(in) :: epsilonjx,epsilonjy,epsilonjz
!     real(wp), intent(in) :: kappaixx,kappaiyy,kappaizz
!     real(wp), intent(in) :: kappaixy,kappaixz,kappaiyz
!     real(wp), intent(in) :: kappajxx,kappajyy,kappajzz
!     real(wp), intent(in) :: kappajxy,kappajxz,kappajyz
      real(wp), intent(in) :: eta1_ij
      real(wp), intent(in) :: eta2_ij
      real(wp), intent(in) :: eta3_ij
      real(wp), intent(in) :: B1_ij
      real(wp), intent(in) :: B2_ij
      real(wp), intent(in) :: B3_ij
      real(wp), intent(in) :: C_ij
      real(wp), intent(in) :: D_ij
      real(wp), intent(in) :: damp_dd_ij
      real(wp), intent(in) :: damp_dq_ij

      ! Result
      ! ------
      real(wp), dimension(3) :: ft_ij

      ! Local
      ! -----

      real(wp) :: drnorm, drnormrec, drnorm2rec,drnorm4rec,drnorm6rec,drnorm8rec
      real(wp) :: rho,repsiloni,repsilonj
!     real(wp) :: rho,repsiloni,repsilonj,rkappai,rkappaj
      real(wp) :: sgam,sgam2x,sgam2y,sgam2z
!     real(wp) :: sgam,sgam2x,sgam2xx,sgam2y,sgam2yy,sgam2z,sgam2zz
!     real(wp) :: txx,tyy,tzz,txy,txz,tyz
      real(wp) :: damp_dd_sum, damp_dd_term
      real(wp) :: damp_dq_sum, damp_dq_term
      integer :: k, l
      drnorm = sqrt(drnorm2)
      drnormrec  = 1.0_wp / drnorm
      drnorm2rec = 1.0_wp / drnorm2
      drnorm4rec = drnorm2rec * drnorm2rec
      drnorm6rec = drnorm2rec * drnorm4rec 
      drnorm8rec = drnorm2rec * drnorm6rec

      ! damp_dd_sum = sum_{k=0}^{6} (d_{dd}^k r_{ij}^k) / k!
      damp_dd_sum = 1.0_wp
      damp_dd_term = 1.0_wp
      do k = 1, 6
         damp_dd_term = damp_dd_term * (damp_dd_ij * drnorm / real(k,wp)) ! damp_dd_term =  (d_{dd}^k r_{ij}^k) / k!
         damp_dd_sum = damp_dd_sum + damp_dd_term
      end do

      ! damp_dq_sum = sum_{k=0}^{8} (d_{dq}^k r_{ij}^k) / k!
      damp_dq_sum = 1.0_wp
      damp_dq_term = 1.0_wp
      do l = 1, 8
         damp_dq_term = damp_dq_term * (damp_dq_ij * drnorm / real(l,wp)) ! damp_dq_term =  (d_{dq}^k r_{ij}^k) / k!
         damp_dq_sum = damp_dq_sum + damp_dq_term
      end do

      ft_ij =  (C_ij * drnorm6rec) * (6.0_wp * drnorm2rec * (1.0_wp - damp_dd_sum*exp(-damp_dd_ij*drnorm)) &
            - damp_dd_ij * damp_dd_term * drnormrec* exp(-damp_dd_ij * drnorm)) &
            + (D_ij * drnorm8rec) * (8.0_wp * drnorm2rec * (1.0_wp - damp_dq_sum*exp(-damp_dq_ij*drnorm)) &
            - damp_dq_ij * damp_dq_term * drnormrec* exp(-damp_dq_ij * drnorm))

      ft_ij=ft_ij-eta1_ij*B1_ij*exp(-eta1_ij*drnorm)*drnormrec
      ft_ij(1)=ft_ij(1)*dx
      ft_ij(2)=ft_ij(2)*dy
      ft_ij(3)=ft_ij(3)*dz

!     txx=3.0_wp*dx*dx*drnorm2rec-1.0_wp
!     tyy=3.0_wp*dy*dy*drnorm2rec-1.0_wp
!     tzz=3.0_wp*dz*dz*drnorm2rec-1.0_wp
!     txy=3.0_wp*dx*dy*drnorm2rec
!     txz=3.0_wp*dx*dz*drnorm2rec
!     tyz=3.0_wp*dy*dz*drnorm2rec
!     rkappai=kappaixx*txx+kappaiyy*tyy+kappaizz*tzz+ &
!             2.0_wp*(kappaixy*txy+kappaixz*txz+kappaiyz*tyz)
!     rkappaj=kappajxx*txx+kappajyy*tyy+kappajzz*tzz+ &
!             2.0_wp*(kappajxy*txy+kappajxz*txz+kappajyz*tyz)
!     rho=drnorm-deltai-deltaj-repsiloni-repsilonj-rkappai-rkappaj
      repsiloni=drnormrec*(-epsilonix*dx-epsiloniy*dy-epsiloniz*dz)
      repsilonj=drnormrec*(-epsilonjx*dx-epsilonjy*dy-epsilonjz*dz)
      rho=drnorm-deltai-deltaj-repsiloni+repsilonj

      sgam=eta2_ij*B2_ij*exp(-eta2_ij*rho)  +  eta3_ij*B3_ij*exp(-eta3_ij*rho)

      sgam2x=drnormrec*(epsilonix-epsilonjx)+dx*drnorm2rec*(repsiloni-repsilonj)
      sgam2y=drnormrec*(epsiloniy-epsilonjy)+dy*drnorm2rec*(repsiloni-repsilonj)
      sgam2z=drnormrec*(epsiloniz-epsilonjz)+dz*drnorm2rec*(repsiloni-repsilonj)

      ft_ij(1)=ft_ij(1)-sgam*(dx*drnormrec+sgam2x)
      ft_ij(2)=ft_ij(2)-sgam*(dy*drnormrec+sgam2y)
      ft_ij(3)=ft_ij(3)-sgam*(dz*drnormrec+sgam2z)
      
   end function force_ij

   ! ================================================================================
   ! Function to compute the potential felt between 2 particles
   !
   ! ft_ij = V(r_ij)
   function potential_ij(dx,dy,dz,drnorm2,deltai,deltaj,epsilonix,epsiloniy,epsiloniz,&
            epsilonjx,epsilonjy,epsilonjz,eta1_ij, eta2_ij, eta3_ij, &
            B1_ij, B2_ij, B3_ij, C_ij, D_ij, damp_dd_ij, damp_dq_ij) result(ft_ij)
      implicit none
      !$acc routine seq
      ! Passed in
      ! ---------
      real(wp), intent(in) :: dx,dy,dz,drnorm2
      real(wp), intent(in) :: deltai,deltaj
      real(wp), intent(in) :: epsilonix,epsiloniy,epsiloniz
      real(wp), intent(in) :: epsilonjx,epsilonjy,epsilonjz
      real(wp), intent(in) :: eta1_ij
      real(wp), intent(in) :: eta2_ij
      real(wp), intent(in) :: eta3_ij
      real(wp), intent(in) :: B1_ij
      real(wp), intent(in) :: B2_ij
      real(wp), intent(in) :: B3_ij
      real(wp), intent(in) :: C_ij
      real(wp), intent(in) :: D_ij
      real(wp), intent(in) :: damp_dd_ij
      real(wp), intent(in) :: damp_dq_ij

      ! Result
      ! ------
      real(wp) :: ft_ij

      ! Local
      ! -----
      real(wp) :: drnorm, drnormrec,drnorm2rec, drnorm6rec, drnorm8rec
      real(wp) :: rho,repsiloni,repsilonj
      real(wp) :: damp_dd_sum, damp_dd_term
      real(wp) :: damp_dq_sum, damp_dq_term
      integer :: k, l

      drnorm = sqrt(drnorm2)
      drnormrec = 1.0_wp / drnorm
      drnorm2rec = 1.0_wp / drnorm2
      drnorm6rec = drnorm2rec * drnorm2rec * drnorm2rec
      drnorm8rec = drnorm2rec * drnorm6rec

      ! damp_dd_sum = sum_{k=0}^{6} (d_{dd}^k r_{ij}^k) / k!
      damp_dd_sum = 1.0_wp
      damp_dd_term = 1.0_wp
      do k = 1, 6
         damp_dd_term = damp_dd_term * (damp_dd_ij * drnorm / real(k,wp)) ! damp_dd_term =  (d_{dd}^k r_{ij}^k) / k!
         damp_dd_sum = damp_dd_sum + damp_dd_term
      end do

      ! damp_dq_sum = sum_{k=0}^{8} (d_{dq}^k r_{ij}^k) / k!
      damp_dq_sum = 1.0_wp
      damp_dq_term = 1.0_wp
      do l = 1, 8
         damp_dq_term = damp_dq_term * (damp_dq_ij * drnorm / real(l,wp)) ! damp_dq_term =  (d_{dq}^k r_{ij}^k) / k!
         damp_dq_sum = damp_dq_sum + damp_dq_term
      end do

      repsiloni=drnormrec*(-epsilonix*dx-epsiloniy*dy-epsiloniz*dz)
      repsilonj=drnormrec*(-epsilonjx*dx-epsilonjy*dy-epsilonjz*dz)
      rho=drnorm-deltai-deltaj-repsiloni+repsilonj

      ft_ij = B1_ij * exp(-eta1_ij * drnorm) &
            + B2_ij * exp(-eta2_ij * rho) &
            + B3_ij * exp(-eta3_ij * rho) &
            - C_ij * drnorm6rec * (1.0_wp - damp_dd_sum*exp(-damp_dd_ij*drnorm)) &
            - D_ij * drnorm8rec * (1.0_wp - damp_dq_sum*exp(-damp_dq_ij*drnorm))
   end function potential_ij

   ! ================================================================================
   ! Function to compute the repulsive potential felt between 2 particles
   !
   ! ft_ij = V(r_ij)
   function reppotential_ij(dx,dy,dz,drnorm2,deltai,deltaj,epsilonix,epsiloniy,epsiloniz,&
            epsilonjx,epsilonjy,epsilonjz, eta2_ij, eta3_ij, &
             B2_ij, B3_ij) result(ft_ij)
      implicit none
      !$acc routine seq
      ! Passed in
      ! ---------
      real(wp), intent(in) :: dx,dy,dz,drnorm2
      real(wp), intent(in) :: deltai,deltaj
      real(wp), intent(in) :: epsilonix,epsiloniy,epsiloniz
      real(wp), intent(in) :: epsilonjx,epsilonjy,epsilonjz
      real(wp), intent(in) :: eta2_ij
      real(wp), intent(in) :: eta3_ij
      real(wp), intent(in) :: B2_ij
      real(wp), intent(in) :: B3_ij

      ! Result
      ! ------
      real(wp) :: ft_ij

      ! Local
      ! -----
      real(wp) :: drnorm, drnormrec
      real(wp) :: rho,repsiloni,repsilonj

      drnorm = sqrt(drnorm2)
      drnormrec = 1.0_wp /drnorm

      repsiloni=drnormrec*(-epsilonix*dx-epsiloniy*dy-epsiloniz*dz)
      repsilonj=drnormrec*(-epsilonjx*dx-epsilonjy*dy-epsilonjz*dz)
      rho=drnorm-deltai-deltaj-repsiloni+repsilonj

      ft_ij =  B2_ij * exp(-eta2_ij * rho) &
            + B3_ij * exp(-eta3_ij * rho) 
   end function reppotential_ij

   ! ================================================================================
   ! Function to compute the repulsive potential felt between 2 particles and its 
   ! derivative w.r.t. the deltas and the epsilons
   !
   ! ft_ij = V(r_ij)
   function diffreppotential_ij(dx,dy,dz,drnorm2,deltai,deltaj,epsilonix,epsiloniy,epsiloniz,&
            epsilonjx,epsilonjy,epsilonjz, eta2_ij, eta3_ij, &
             B2_ij, B3_ij) result(ft_ij)
      implicit none
      !$acc routine seq
      ! Passed in
      ! ---------
      real(wp), intent(in) :: dx,dy,dz,drnorm2
      real(wp), intent(in) :: deltai,deltaj
      real(wp), intent(in) :: epsilonix,epsiloniy,epsiloniz
      real(wp), intent(in) :: epsilonjx,epsilonjy,epsilonjz
      real(wp), intent(in) :: eta2_ij
      real(wp), intent(in) :: eta3_ij
      real(wp), intent(in) :: B2_ij
      real(wp), intent(in) :: B3_ij

      ! Result
      ! ------
      real(wp),dimension(5) :: ft_ij

      ! Local
      ! -----
      real(wp) :: drnorm, drnormrec
      real(wp) :: rho,repsiloni,repsilonj
      real(wp) :: exprho2,exprho3

      drnorm = sqrt(drnorm2)
      drnormrec = 1.0_wp /drnorm

      repsiloni=drnormrec*(-epsilonix*dx-epsiloniy*dy-epsiloniz*dz)
      repsilonj=drnormrec*(-epsilonjx*dx-epsilonjy*dy-epsilonjz*dz)
      rho=drnorm-deltai-deltaj-repsiloni+repsilonj

      exprho2=B2_ij*exp(-eta2_ij*rho)
      exprho3=B3_ij*exp(-eta3_ij*rho)

      ft_ij(1)=eta2_ij*exprho2+eta3_ij*exprho3
      ft_ij(2)=ft_ij(1)*dx*drnormrec
      ft_ij(3)=ft_ij(1)*dy*drnormrec
      ft_ij(4)=ft_ij(1)*dz*drnormrec
      ft_ij(5) =  exprho2+exprho3 
   end function diffreppotential_ij



   ! ================================================================================
   include 'daim_2DPBC.inc'
   include 'daim_3DPBC.inc'
   ! ================================================================================
   include 'minimum_image_displacement_2DPBC.inc'
   include 'minimum_image_displacement_3DPBC.inc'
   include 'minimum_image_distance_2DPBC.inc'
   include 'minimum_image_distance_3DPBC.inc'
   ! ================================================================================
   include 'update_diag_block_boundaries.inc'
   include 'update_tri_block_boundaries.inc'
   include 'update_other_block_boundaries.inc'

end module MW_daim
