! Module defining Ewald summation parameters
module MW_ewald
   use MW_kinds, only: wp
   implicit none
   private

   ! Public types
   ! ------------
   public :: MW_ewald_t

   ! Public subroutines
   ! ------------------
   public :: define_type
   public :: define_zscale
   public :: define_kpoints
   public :: redefine_kpoints
   public :: define_Skcossin
   public :: define_Skmucossin
   public :: redefine_Skcossin
   public :: redefine_Skmucossin
   public :: void_type
   public :: print_type
   public :: setup_cossin_ions
   public :: setup_cossin_elec
   public :: update_kmode_index
   public :: set_num_pbc

   !> Ewald summation parameters
   type MW_ewald_t
      real(wp) :: alpha         !< numerical parameter which split computation between
      !! long range and short range contribution

      integer :: num_pbc = 0 !< number of periodic boundary conditions

      ! Real space summation parameters
      real(wp) :: rtol    !< Tolerance on real space summation terms
      real(wp) :: rcut    !< cut-off distance for real space summation
      real(wp) :: rcutsq  !< cut-off distance (squared) for real space summation
      logical, allocatable :: sr_interaction_ion2ion(:,:) !< Flag if 2 particle types interact via short range interactions
      logical, allocatable :: sr_interaction_atom2atom(:,:) !< Flag if 2 particle types interact via short range interactions
      logical, allocatable :: sr_interaction_ion2atom(:,:) !< Flag if 2 particle types interact via short range interactions
      logical, allocatable :: sr_interaction_atom2ion(:,:) !< Flag if 2 particle types interact via short range interactions

      ! Reciprocal space summation parameters
      real(wp) :: ktol
      integer  :: kmax_x
      integer  :: kmax_y
      integer  :: kmax_z
      real(wp) :: knorm2_max
      real(wp) :: zscale_weight = 1.0_wp
      real(wp) :: zscale_range  = 1.0_wp


      ! Z integral parameter (quadrature must be symmetric around 0)
      real(wp), allocatable :: zpoint(:)
      real(wp), allocatable :: zweight(:)

      ! cos and sin for reciprocal summation
      real(wp), allocatable :: cos_kx_ions(:,:)
      real(wp), allocatable :: cos_ky_ions(:,:)
      real(wp), allocatable :: cos_kz_ions(:,:)

      real(wp), allocatable :: sin_kx_ions(:,:)
      real(wp), allocatable :: sin_ky_ions(:,:)
      real(wp), allocatable :: sin_kz_ions(:,:)

      real(wp), allocatable :: cos_kx_elec(:,:)
      real(wp), allocatable :: cos_ky_elec(:,:)
      real(wp), allocatable :: cos_kz_elec(:,:)

      real(wp), allocatable :: sin_kx_elec(:,:)
      real(wp), allocatable :: sin_ky_elec(:,:)
      real(wp), allocatable :: sin_kz_elec(:,:)

      real(wp), allocatable :: Sk_cos(:)
      real(wp), allocatable :: Sk_sin(:)
      real(wp), allocatable :: Skmux_cos(:)
      real(wp), allocatable :: Skmux_sin(:)
      real(wp), allocatable :: Skmuy_cos(:)
      real(wp), allocatable :: Skmuy_sin(:)
      real(wp), allocatable :: Skmuz_cos(:)
      real(wp), allocatable :: Skmuz_sin(:)

   end type MW_ewald_t

contains

   !================================================================================
   ! Define ewald parameters
   subroutine define_type(this, rtol, rcut, ktol)
      use MW_kinds, only: wp
      implicit none

      ! Parameters in
      ! -------------
      real(wp),       intent(in) :: rtol      !< Short-range precision on summation terms
      real(wp),       intent(in) :: rcut      !< Short-range cutoff
      real(wp),       intent(in) :: ktol      !< Long-range summation precision

      ! Parameters out
      ! --------------
      type(MW_ewald_t), intent(inout) :: this

      ! Local
      ! -----
      real(wp) :: alpha

      ! summation gaussian parameters
      call compute_alpha(rcut, rtol, alpha)
      this%alpha = alpha

      ! Real space summation parameters
      this%rtol = rtol
      this%rcut = rcut
      this%rcutsq = rcut * rcut

      ! Reciprocal space summation parameters
      this%ktol = ktol

   end subroutine define_type

   !================================================================================
   ! Define ewald parameters in z-direction for 2DPBC
   subroutine define_zscale(this, zscale_range, zscale_weight)
      use MW_kinds, only: wp
      implicit none

      ! Parameters in
      ! -------------
      real(wp),       intent(in) :: zscale_range    !< Scaling factor for range on integral cut-off
      real(wp),       intent(in) :: zscale_weight   !< Scaling factor for integral weights

      ! Parameters out
      ! --------------
      type(MW_ewald_t), intent(inout) :: this

      this%zscale_range = zscale_range
      this%zscale_weight = zscale_weight

   end subroutine define_zscale

   !================================================================================
   ! Define reciprocal space parameters
   subroutine define_kpoints(this, num_pbc, box, num_ions, num_atoms)
      use MW_kinds, only: wp
      use MW_errors, only: &
            MW_errors_allocate_error => allocate_error, &
            MW_errors_parameter_error => parameter_error
      use MW_constants, only: twopi
      use MW_box, only: MW_box_t
      implicit none

      ! Parameters in
      ! -------------
      integer,        intent(in) :: num_pbc   !< number of periodic boundary conditions
      type(MW_box_t), intent(in) :: box       !< simulation box
      integer,        intent(in) :: num_ions  !< number of ions in the system
      integer,        intent(in) :: num_atoms !< number of electrode atoms in the system

      ! Parameters out
      ! --------------
      type(MW_ewald_t), intent(inout) :: this

      ! Local
      ! -----
      integer :: i, ios
      real(wp) :: rkmin, rkmax
      real(wp) :: dh
      integer :: kmax_z

      ! Choose rkmax such that truncature error < ktol
      !
      ! since
      ! error < 8 \sum_{kmax_x}^{\infty} \sum_{kmax_y}^{\infty} \int_{kmax_z}^{infty} \frac{e^{-(|k|^2| + u^2)/(4\alpha^2)}}{|k|^2 + u^2} du
      ! error < 8 * 4 * \pi \int_{rkmax}^{\infty} e^{-r^2/(4\alpha^2)} dr
      ! error < 32 * \pi * \frac{\sqrt{\pi}}{2} * 2 * \alpha \erfc{rkmax/(2\alpha)}
      !
      ! we choose rkmax such that
      ! exp(-|kcut|^2/(4*alpha^2)) = epsilon
      !
      rkmax = sqrt(-log(this%ktol)*4.0_wp*this%alpha*this%alpha)

      this%kmax_x = floor(rkmax*box%length(1)/(twopi))
      this%kmax_y = floor(rkmax*box%length(2)/(twopi))
      this%knorm2_max = rkmax*rkmax

      select case (num_pbc)
      case(2)
         rkmin = this%zscale_weight * twopi / box%length(3)
         rkmax = this%zscale_range * sqrt(-log(this%ktol))*2.0_wp*this%alpha / rkmin
         kmax_z = floor(rkmax)
      case(3)
         kmax_z = floor(rkmax*box%length(3)/(twopi))
      case default
         kmax_z = 0
      end select
      this%kmax_z = kmax_z

      allocate(this%zpoint(0:kmax_z), this%zweight(0:kmax_z), stat=ios)
      if (ios /= 0) then
         call MW_errors_allocate_error("define_kpoints", "ewald.f90", ios)
      end if

      select case (num_pbc)
      case(2)
         dh   = this%zscale_weight * twopi / box%length(3)
      case(3)
         dh   = twopi / box%length(3)
      case default
         dh = 0.0_wp
      end select

      do i = 0, kmax_z
         this%zpoint(i) = real(i,wp)*dh
         this%zweight(i) = dh
      end do

      allocate(this%cos_kx_ions(num_ions,0:this%kmax_x),&
           this%cos_ky_ions(num_ions,0:this%kmax_y),    &
           this%cos_kz_ions(num_ions,0:this%kmax_z),    &
           this%sin_kx_ions(num_ions,0:this%kmax_x),    &
           this%sin_ky_ions(num_ions,0:this%kmax_y),    &
           this%sin_kz_ions(num_ions,0:this%kmax_z),    &
           this%cos_kx_elec(num_atoms,0:this%kmax_x),   &
           this%cos_ky_elec(num_atoms,0:this%kmax_y),   &
           this%cos_kz_elec(num_atoms,0:this%kmax_z),   &
           this%sin_kx_elec(num_atoms,0:this%kmax_x),   &
           this%sin_ky_elec(num_atoms,0:this%kmax_y),   &
           this%sin_kz_elec(num_atoms,0:this%kmax_z), stat=ios)
      if (ios /= 0) then
         call MW_errors_allocate_error("define_kpoints", "ewald.f90", ios)
      end if

      this%cos_kx_ions(:,:) = 0.0_wp
      this%cos_ky_ions(:,:) = 0.0_wp
      this%cos_kz_ions(:,:) = 0.0_wp

      this%sin_kx_ions(:,:) = 0.0_wp
      this%sin_ky_ions(:,:) = 0.0_wp
      this%sin_kz_ions(:,:) = 0.0_wp

      this%cos_kx_elec(:,:) = 0.0_wp
      this%cos_ky_elec(:,:) = 0.0_wp
      this%cos_kz_elec(:,:) = 0.0_wp

      this%sin_kx_elec(:,:) = 0.0_wp
      this%sin_ky_elec(:,:) = 0.0_wp
      this%sin_kz_elec(:,:) = 0.0_wp

   end subroutine define_kpoints

    !================================================================================
   ! Define reciprocal space parameters
   subroutine redefine_kpoints(this, box, num_ions)
      use MW_kinds, only: wp
      use MW_errors, only: &
            MW_errors_allocate_error => allocate_error, &
            MW_errors_deallocate_error => deallocate_error, &
            MW_errors_parameter_error => parameter_error
      use MW_constants, only: twopi
      use MW_box, only: MW_box_t
      implicit none

      ! Parameters in
      ! -------------
      type(MW_box_t), intent(in) :: box       !< simulation box
      integer,        intent(in) :: num_ions  !< number of ions in the system

      ! Parameters out
      ! --------------
      type(MW_ewald_t), intent(inout) :: this

      ! Local
      ! -----
      integer :: i, ios
      real(wp) :: rkmax
      real(wp) :: dh

      ! Choose rkmax such that truncature error < ktol
      !
      ! since
      ! error < 8 \sum_{kmax_x}^{\infty} \sum_{kmax_y}^{\infty} \int_{kmax_z}^{infty} \frac{e^{-(|k|^2| + u^2)/(4\alpha^2)}}{|k|^2 + u^2} du
      ! error < 8 * 4 * \pi \int_{rkmax}^{\infty} e^{-r^2/(4\alpha^2)} dr
      ! error < 32 * \pi * \frac{\sqrt{\pi}}{2} * 2 * \alpha \erfc{rkmax/(2\alpha)}
      !
      ! we choose rkmax such that
      ! exp(-|kcut|^2/(4*alpha^2)) = epsilon
      !
      rkmax = sqrt(-log(this%ktol)*4.0_wp*this%alpha*this%alpha)

      this%kmax_x = floor(rkmax*box%length(1)/(twopi))
      this%kmax_y = floor(rkmax*box%length(2)/(twopi))
      this%kmax_z = floor(rkmax*box%length(3)/(twopi))
      this%knorm2_max = rkmax*rkmax

      deallocate(this%zpoint, this%zweight, stat=ios)
      if (ios /= 0) then
         call MW_errors_deallocate_error("redefine_kpoints", "ewald.f90", ios)
      end if

      allocate(this%zpoint(0:this%kmax_z), this%zweight(0:this%kmax_z), stat=ios)
      if (ios /= 0) then
         call MW_errors_allocate_error("redefine_kpoints", "ewald.f90", ios)
      end if

      dh   = twopi / box%length(3)

      do i = 0, this%kmax_z
         this%zpoint(i) = real(i,wp)*dh
         this%zweight(i) = dh
      end do

      deallocate(this%cos_kx_ions, this%cos_ky_ions, this%cos_kz_ions, &
                 this%sin_kx_ions, this%sin_ky_ions, this%sin_kz_ions, & 
                 this%cos_kx_elec, this%cos_ky_elec, this%cos_kz_elec, &
                 this%sin_kx_elec, this%sin_ky_elec, this%sin_kz_elec, & 
                 stat=ios)
      if (ios /= 0) then
         call MW_errors_deallocate_error("redefine_kpoints", "ewald.f90", ios)
      end if

      allocate(this%cos_kx_ions(num_ions,0:this%kmax_x),&
           this%cos_ky_ions(num_ions,0:this%kmax_y),    &
           this%cos_kz_ions(num_ions,0:this%kmax_z),    &
           this%sin_kx_ions(num_ions,0:this%kmax_x),    &
           this%sin_ky_ions(num_ions,0:this%kmax_y),    &
           this%sin_kz_ions(num_ions,0:this%kmax_z),    &
           this%cos_kx_elec(0,0:this%kmax_x),   &
           this%cos_ky_elec(0,0:this%kmax_y),   &
           this%cos_kz_elec(0,0:this%kmax_z),   &
           this%sin_kx_elec(0,0:this%kmax_x),   &
           this%sin_ky_elec(0,0:this%kmax_y),   &
           this%sin_kz_elec(0,0:this%kmax_z), stat=ios)
      if (ios /= 0) then
         call MW_errors_allocate_error("redefine_kpoints", "ewald.f90", ios)
      end if

      this%cos_kx_ions(:,:) = 0.0_wp
      this%cos_ky_ions(:,:) = 0.0_wp
      this%cos_kz_ions(:,:) = 0.0_wp

      this%sin_kx_ions(:,:) = 0.0_wp
      this%sin_ky_ions(:,:) = 0.0_wp
      this%sin_kz_ions(:,:) = 0.0_wp

      this%cos_kx_elec(:,:) = 0.0_wp
      this%cos_ky_elec(:,:) = 0.0_wp
      this%cos_kz_elec(:,:) = 0.0_wp

      this%sin_kx_elec(:,:) = 0.0_wp
      this%sin_ky_elec(:,:) = 0.0_wp
      this%sin_kz_elec(:,:) = 0.0_wp

   end subroutine redefine_kpoints  

   !================================================================================
   ! Allocate sk cos/sin arrays
   subroutine define_Skcossin(this, num_modes)
      use MW_kinds, only: wp
      use MW_errors, only: &
            MW_errors_allocate_error => allocate_error, &
            MW_errors_parameter_error => parameter_error
      implicit none
      ! Parameter
      ! --------
      type(MW_ewald_t), intent(inout) :: this
      integer, intent(in) :: num_modes

      ! Local
      ! -----
      integer :: ios
      allocate(this%Sk_cos(num_modes), this%Sk_sin(num_modes), stat=ios)
      if (ios /= 0) then
         call MW_errors_allocate_error("define_Skcossin", "ewald.f90", ios)
      end if
      this%Sk_cos(:) = 0.0_wp
      this%Sk_sin(:) = 0.0_wp
   end subroutine define_Skcossin

   !================================================================================
   ! Allocate sk cos/sin arrays
   subroutine define_Skmucossin(this, num_modes)
      use MW_kinds, only: wp
      use MW_errors, only: &
            MW_errors_allocate_error => allocate_error, &
            MW_errors_parameter_error => parameter_error
      implicit none
      ! Parameter
      ! --------
      type(MW_ewald_t), intent(inout) :: this
      integer, intent(in) :: num_modes

      ! Local
      ! -----
      integer :: ios
      allocate(this%Skmux_cos(num_modes), this%Skmux_sin(num_modes), stat=ios)
      allocate(this%Skmuy_cos(num_modes), this%Skmuy_sin(num_modes), stat=ios)
      allocate(this%Skmuz_cos(num_modes), this%Skmuz_sin(num_modes), stat=ios)
      if (ios /= 0) then
         call MW_errors_allocate_error("define_Skmucossin", "ewald.f90", ios)
      end if
      this%Skmux_cos(:) = 0.0_wp
      this%Skmux_sin(:) = 0.0_wp
      this%Skmuy_cos(:) = 0.0_wp
      this%Skmuy_sin(:) = 0.0_wp
      this%Skmuz_cos(:) = 0.0_wp
      this%Skmuz_sin(:) = 0.0_wp
   end subroutine define_Skmucossin

   !================================================================================
   ! Allocate sk cos/sin arrays
   subroutine redefine_Skcossin(this, num_modes)
      use MW_kinds, only: wp
      use MW_errors, only: &
            MW_errors_allocate_error => allocate_error, &
            MW_errors_deallocate_error => deallocate_error, &
            MW_errors_parameter_error => parameter_error
      implicit none
      ! Parameter
      ! --------
      type(MW_ewald_t), intent(inout) :: this
      integer, intent(in) :: num_modes

      ! Local
      ! -----
      integer :: ios

      deallocate(this%Sk_cos, this%Sk_sin, stat=ios)
      if (ios /= 0) then
         call MW_errors_deallocate_error("redefine_Skcossin", "ewald.f90", ios)
      end if
      allocate(this%Sk_cos(num_modes), this%Sk_sin(num_modes), stat=ios)
      if (ios /= 0) then
         call MW_errors_allocate_error("redefine_Skcossin", "ewald.f90", ios)
      end if
      this%Sk_cos(:) = 0.0_wp
      this%Sk_sin(:) = 0.0_wp
   end subroutine redefine_Skcossin

   !================================================================================
   ! Allocate sk cos/sin arrays
   subroutine redefine_Skmucossin(this, num_modes)
      use MW_kinds, only: wp
      use MW_errors, only: &
            MW_errors_allocate_error => allocate_error, &
            MW_errors_deallocate_error => deallocate_error, &
            MW_errors_parameter_error => parameter_error
      implicit none
      ! Parameter
      ! --------
      type(MW_ewald_t), intent(inout) :: this
      integer, intent(in) :: num_modes

      ! Local
      ! -----
      integer :: ios

      deallocate(this%Skmux_cos, this%Skmux_sin, stat=ios)
      deallocate(this%Skmuy_cos, this%Skmuy_sin, stat=ios)
      deallocate(this%Skmuz_cos, this%Skmuz_sin, stat=ios)
      if (ios /= 0) then
         call MW_errors_deallocate_error("redefine_Skmucossin", "ewald.f90", ios)
      end if
      allocate(this%Skmux_cos(num_modes), this%Skmux_sin(num_modes), stat=ios)
      allocate(this%Skmuy_cos(num_modes), this%Skmuy_sin(num_modes), stat=ios)
      allocate(this%Skmuz_cos(num_modes), this%Skmuz_sin(num_modes), stat=ios)
      if (ios /= 0) then
         call MW_errors_allocate_error("define_Skmucossin", "ewald.f90", ios)
      end if
      this%Skmux_cos(:) = 0.0_wp
      this%Skmux_sin(:) = 0.0_wp
      this%Skmuy_cos(:) = 0.0_wp
      this%Skmuy_sin(:) = 0.0_wp
      this%Skmuz_cos(:) = 0.0_wp
      this%Skmuz_sin(:) = 0.0_wp
   end subroutine redefine_Skmucossin   

   !================================================================================
   ! Void ewald parameters
   subroutine void_type(this)
      use MW_kinds, only: wp
      use MW_errors, only: MW_errors_deallocate_error => deallocate_error
      implicit none

      ! Parameters out
      ! --------------
      type(MW_ewald_t), intent(inout) :: this

      ! Local
      ! ----
      integer :: ios

      this%alpha = 0.0_wp
      this%rcut = 0.0_wp
      this%rcutsq = 0.0_wp
      this%ktol = 0.0_wp
      this%kmax_x = 0
      this%kmax_y = 0
      this%kmax_z = 0
      this%knorm2_max = 0.0_wp

      if (allocated(this%sr_interaction_ion2ion)) then
         deallocate(this%sr_interaction_ion2ion, &
               this%sr_interaction_atom2atom, &
               this%sr_interaction_ion2atom, &
               this%sr_interaction_atom2ion, stat=ios)
         if (ios /= 0) then
            call MW_errors_deallocate_error("void_type", "ewald.f90", ios)
         end if
      end if

      if (allocated(this%zpoint)) then
         deallocate(this%zpoint, this%zweight, stat=ios)
         if (ios /= 0) then
            call MW_errors_deallocate_error("void_type", "ewald.f90", ios)
         end if
      end if

      if (allocated(this%cos_kx_ions)) then
         deallocate(this%cos_kx_ions, this%cos_ky_ions, this%cos_kz_ions, &
              this%sin_kx_ions, this%sin_ky_ions, this%sin_kz_ions, &
               this%cos_kx_elec, this%cos_ky_elec, this%cos_kz_elec,&
               this%sin_kx_elec, this%sin_ky_elec, this%sin_kz_elec, stat=ios)
         if (ios /= 0) then
            call MW_errors_deallocate_error("void_type", "ewald.f90", ios)
         end if
      end if

      if (allocated(this%Sk_cos)) then
         deallocate(this%Sk_cos, this%Sk_sin, stat=ios)
         if (ios /= 0) then
            call MW_errors_deallocate_error("void_type", "ewald.f90", ios)
         end if
      end if
      if (allocated(this%Skmux_cos)) then
         deallocate(this%Skmux_cos, this%Skmux_sin, stat=ios)
         deallocate(this%Skmuy_cos, this%Skmuy_sin, stat=ios)
         deallocate(this%Skmuz_cos, this%Skmuz_sin, stat=ios)
         if (ios /= 0) then
            call MW_errors_deallocate_error("void_type", "ewald.f90", ios)
         end if
      end if
   end subroutine void_type

   !================================================================================
   ! Print ewald parameters
   subroutine print_type(this, ounit)
      implicit none
      ! Parameters out
      ! --------------
      type(MW_ewald_t), intent(in) :: this
      integer,          intent(in) :: ounit

      integer :: itype, jtype
      write(ounit,'("|ewald| alpha:                                ", es12.5)') this%alpha
      write(ounit,'("|ewald| real space summation tolerance:       ", es12.5)') this%rtol
      write(ounit,'("|ewald| real space cut-off distance:          ", es12.5)') this%rcut
      write(ounit,'("|ewald| erfc(alpha*|rcut|)/|rcut|:            ", es12.5)') &
            erfc(this%alpha*this%rcut)/this%rcut
      write(ounit,'("|ewald| exp(-alpha^2*|rcut|^2)/|rcut|:        ", es12.5)') &
            exp(-this%alpha*this%alpha*this%rcut*this%rcut)/this%rcut
      write(ounit,'("|ewald| reciprocal space summation tolerance:       ", es12.5)') this%ktol
      write(ounit,'("|ewald| reciprocal space cut-off distance |kcut|^2: ", es12.5)') sqrt(this%knorm2_max)
      write(ounit,'("|ewald| exp(-|kcut|^2/(4*alpha^2))/|kcut|^2:        ", es12.5)') &
            exp(-this%knorm2_max/(4.0_wp*this%alpha*this%alpha))/this%knorm2_max
      write(ounit,'("|ewald| reciprocal space image count (kmax):  ",3(i12,1x))') this%kmax_x, this%kmax_y, this%kmax_z
      write(ounit,'("|ewald| Z integral points:                    ",i12)') 2*this%kmax_z + 1
      write(ounit,'("|ewald| Z integral range:                     ",2(es12.5,1x))') &
            -this%zpoint(this%kmax_z) - 0.5_wp*this%zweight(0), &
            +this%zpoint(this%kmax_z) + 0.5_wp*this%zweight(0)
      write(ounit,'("|ewald| Z integral weights:                   ",es12.5)') this%zweight(0)
      write(ounit,'("|ewald| Short-Range interaction matrices:")')

      write(ounit,'("|ewald| ion <-> ion ")')
      do itype = 1, size(this%sr_interaction_ion2ion,2)
         write(ounit, '("|ewald| ")', advance='no')
         do jtype = 1, size(this%sr_interaction_ion2ion,1)
            if (this%sr_interaction_ion2ion(jtype,itype)) then
               write(ounit, '(1a,1x)', advance='no') 'X'
            else
               write(ounit, '(1a,1x)', advance='no') 'O'
            end if
         end do
         write(ounit, *)
      end do

      write(ounit,'("|ewald| electrode <-> electrode ")')
      do itype = 1, size(this%sr_interaction_atom2atom,2)
         write(ounit, '("|ewald| ")', advance='no')
         do jtype = 1, size(this%sr_interaction_atom2atom,1)
            if (this%sr_interaction_atom2atom(jtype,itype)) then
               write(ounit, '(1a,1x)', advance='no') 'X'
            else
               write(ounit, '(1a,1x)', advance='no') 'O'
            end if
         end do
         write(ounit, *)
      end do

      write(ounit,'("|ewald| electrolyte -> electrode ")')
      do itype = 1, size(this%sr_interaction_ion2atom,2)
         write(ounit, '("|ewald| ")', advance='no')
         do jtype = 1, size(this%sr_interaction_ion2atom,1)
            if (this%sr_interaction_ion2atom(jtype,itype)) then
               write(ounit, '(1a,1x)', advance='no') 'X'
            else
               write(ounit, '(1a,1x)', advance='no') 'O'
            end if
         end do
         write(ounit, *)
      end do

      write(ounit,'("|ewald| electrode -> electrolyte ")')
      do itype = 1, size(this%sr_interaction_atom2ion,2)
         write(ounit, '("|ewald| ")', advance='no')
         do jtype = 1, size(this%sr_interaction_atom2ion,1)
            if (this%sr_interaction_atom2ion(jtype,itype)) then
               write(ounit, '(1a,1x)', advance='no') 'X'
            else
               write(ounit, '(1a,1x)', advance='no') 'O'
            end if
         end do
         write(ounit, *)
      end do

   end subroutine print_type

   !================================================================================
   ! Setup cos and sin for Ewald summation
   subroutine setup_cossin_ions(this, box, xyz_ions)
      use MW_constants, only: twopi
      use MW_box, only: MW_box_t
      implicit none
      ! Parameters
      ! ----------
      type(MW_ewald_t), intent(inout) :: this
      type(MW_box_t), intent(in) :: box
      real(wp), intent(in) :: xyz_ions(:,:)

      ! Locals
      ! ------
      integer :: i, k
      integer :: num_ions
      real(wp) :: knorm

      num_ions = size(xyz_ions,1)

      !$acc data &
      !$acc present(xyz_ions(:,:), this, this%zpoint(0:), &
      !$acc         this%cos_kx_ions(:,0:), this%sin_kx_ions(:,0:), &
      !$acc         this%cos_ky_ions(:,0:), this%sin_ky_ions(:,0:), &
      !$acc         this%cos_kz_ions(:,0:), this%sin_kz_ions(:,0:))      

      knorm = twopi / box%length(1)
      !$acc parallel loop collapse(2)
      do k = 0, this%kmax_x
         do i = 1, num_ions
            this%cos_kx_ions(i,k) = cos(real(k,wp)*knorm*xyz_ions(i,1))
            this%sin_kx_ions(i,k) = sin(real(k,wp)*knorm*xyz_ions(i,1))
         end do
      end do
      !$acc end parallel loop 

      knorm = twopi / box%length(2)
      ! Setup for kx >= 2 using trigonometric rule
      !$acc parallel loop collapse(2)
      do k = 0, this%kmax_y
         do i = 1, num_ions
            this%cos_ky_ions(i,k) = cos(real(k,wp)*knorm*xyz_ions(i,2))
            this%sin_ky_ions(i,k) = sin(real(k,wp)*knorm*xyz_ions(i,2))
         end do
      end do
      !$acc end parallel loop 

      ! Setup for kz
      !$acc parallel loop collapse(2)
      do k = 0, this%kmax_z
         do i = 1, num_ions
            this%cos_kz_ions(i,k) = cos(this%zpoint(k)*xyz_ions(i,3))
            this%sin_kz_ions(i,k) = sin(this%zpoint(k)*xyz_ions(i,3))
         end do
      end do
      !$acc end parallel loop 

      !$acc update host(this%cos_kx_ions(:,0:), this%sin_kx_ions(:,0:), &
      !$acc             this%cos_ky_ions(:,0:), this%sin_ky_ions(:,0:), &
      !$acc             this%cos_kz_ions(:,0:), this%sin_kz_ions(:,0:))      

      !$acc end data 

   end subroutine setup_cossin_ions

   !================================================================================
   ! Setup cos and sin for Ewald summation
   subroutine setup_cossin_elec(this, box, xyz_elec)
      use MW_constants, only: twopi
      use MW_box, only: MW_box_t
      implicit none
      ! Parameters
      ! ----------
      type(MW_ewald_t), intent(inout) :: this
      type(MW_box_t), intent(in) :: box
      real(wp), intent(in) :: xyz_elec(:,:)

      ! Locals
      ! ------
      integer :: i, k
      integer :: num_elec
      real(wp) :: knorm

      num_elec = size(xyz_elec,1)

 
      !$acc data &
      !$acc present(xyz_elec(:,:), this, this%zpoint(0:), &
      !$acc         this%cos_kx_elec(:,0:), this%sin_kx_elec(:,0:), &
      !$acc         this%cos_ky_elec(:,0:), this%sin_ky_elec(:,0:), &
      !$acc         this%cos_kz_elec(:,0:), this%sin_kz_elec(:,0:))      

 
      knorm = twopi / box%length(1)
      !$acc parallel loop collapse(2)
      do k = 0, this%kmax_x
         do i = 1, num_elec
            this%cos_kx_elec(i,k) = cos(real(k,wp)*knorm*xyz_elec(i,1))
            this%sin_kx_elec(i,k) = sin(real(k,wp)*knorm*xyz_elec(i,1))
         end do
      end do
      !$acc end parallel loop 
 
      knorm = twopi / box%length(2)
      !$acc parallel loop collapse(2)
      do k = 0, this%kmax_y
         do i = 1, num_elec
            this%cos_ky_elec(i,k) = cos(real(k,wp)*knorm*xyz_elec(i,2))
            this%sin_ky_elec(i,k) = sin(real(k,wp)*knorm*xyz_elec(i,2))
         end do
      end do
      !$acc end parallel loop 
 
      !$acc parallel loop collapse(2)
      do k = 0, this%kmax_z
         do i = 1, num_elec
            this%cos_kz_elec(i,k) = cos(this%zpoint(k)*xyz_elec(i,3))
            this%sin_kz_elec(i,k) = sin(this%zpoint(k)*xyz_elec(i,3))
         end do
      end do
      !$acc end parallel loop 

      !$acc update host(this%cos_kx_elec(:,0:), this%sin_kx_elec(:,0:), &
      !$acc             this%cos_ky_elec(:,0:), this%sin_ky_elec(:,0:), &
      !$acc             this%cos_kz_elec(:,0:), this%sin_kz_elec(:,0:))      

      !$acc end data

   end subroutine setup_cossin_elec

   !================================================================================
   ! Set num pbc in the Ewald data structure
   subroutine set_num_pbc(this, num_pbc)
      implicit none
      type(MW_ewald_t), intent(inout) :: this
      integer, intent(in) :: num_pbc
      this%num_pbc = num_pbc
   end subroutine set_num_pbc

   !================================================================================
   ! Determine alpha given rcut and a tolerance parameter
   !
   ! In the short-range part of electrostatic interactions, terms of the order
   ! erfc(alpha*|r|)/|r| appear in the potential calculation
   ! terms of the order erfc(alpha*|r|)/|r|^3 and
   !                    (2*alpha/sqrt(pi))*exp(-alpha^2*|r|^2)/|r|^2 appear in the force
   !
   ! we want to choose alpha such that
   !    erfc(alpha*|rcut|)/|rcut| < epsilon
   !    (2*alpha/sqrt(pi))*exp(-alpha^2*|rcut|^2)/|rcut| < epsilon
   !
   ! For rcut large enough, erfc(alpha*|rcut|)/|rcut| < exp(-alpha^2*|rcut|^2)/|rcut|
   ! hence we compute alpha as
   !
   ! alpha = sqrt(ln(-epsilon*rcut))/rcut
   !
   subroutine compute_alpha(rcut, epsilon, alpha)
      use MW_kinds, only: wp
      implicit none
      ! Parameters
      ! ----------
      real(wp), intent(in) :: rcut
      real(wp), intent(in) :: epsilon
      real(wp), intent(out) :: alpha

      alpha = sqrt(-log(epsilon*rcut)) / rcut
   end subroutine compute_alpha

   !================================================================================
   include 'update_kmode_index.inc'

end module MW_ewald
