!--------------------------------------------------------------------------------------------------!
!   CP2K: A general program to perform molecular dynamics simulations                              !
!   Copyright 2000-2025 CP2K developers group <https://cp2k.org>                                   !
!                                                                                                  !
!   SPDX-License-Identifier: GPL-2.0-or-later                                                      !
!--------------------------------------------------------------------------------------------------!

! **************************************************************************************************
!> \brief Types and basic routines needed for a kpoint calculation
!> \par History
!>       2014.07 created [JGH]
!>       2014.11 unified k-point and gamma-point code [Ole Schuett]
!> \author JGH
! **************************************************************************************************
MODULE kpoint_types
   USE cp_blacs_env,                    ONLY: cp_blacs_env_release,&
                                              cp_blacs_env_type
   USE cp_fm_types,                     ONLY: cp_fm_release,&
                                              cp_fm_type
   USE cp_log_handling,                 ONLY: cp_get_default_logger,&
                                              cp_logger_type
   USE cp_output_handling,              ONLY: cp_print_key_finished_output,&
                                              cp_print_key_unit_nr
   USE input_cp2k_kpoints,              ONLY: use_complex_wfn,&
                                              use_real_wfn
   USE input_section_types,             ONLY: section_vals_get,&
                                              section_vals_type,&
                                              section_vals_val_get
   USE kinds,                           ONLY: default_string_length,&
                                              dp
   USE mathconstants,                   ONLY: twopi
   USE message_passing,                 ONLY: mp_para_env_release,&
                                              mp_para_env_type
   USE physcon,                         ONLY: angstrom
   USE qs_diis_types,                   ONLY: qs_diis_b_release_kp,&
                                              qs_diis_buffer_type_kp
   USE qs_matrix_pools,                 ONLY: mpools_release,&
                                              qs_matrix_pools_type
   USE qs_mo_types,                     ONLY: deallocate_mo_set,&
                                              mo_set_type
   USE qs_neighbor_list_types,          ONLY: neighbor_list_set_p_type
   USE string_utilities,                ONLY: uppercase
#include "./base/base_uses.f90"

   IMPLICIT NONE

   PRIVATE

   CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'kpoint_types'

   PUBLIC :: kpoint_type
   PUBLIC :: kpoint_create, kpoint_release, get_kpoint_info, set_kpoint_info
   PUBLIC :: read_kpoint_section, write_kpoint_info
   PUBLIC :: kpoint_env_type, kpoint_env_p_type
   PUBLIC :: kpoint_env_create, get_kpoint_env
   PUBLIC :: kind_rotmat_type
   PUBLIC :: kpoint_sym_type
   PUBLIC :: kpoint_sym_create

! **************************************************************************************************
!> \brief Keeps information about a specific k-point
!> \param nkpoint   the kpoint index
!> \param wkp       weight of this kpoint
!> \param xkp       kpoint coordinates in units of b-vector
!> \param is_local  if this kpoint is calculated on a single thread
!> \param mos       associated MOs (r/i,spin)
!> \param pmat      associated density matrix (r/i,spin)
!> \param wmat      associated energy weighted density matrix (r/i,spin)
!> \param smat      associated overlap matrix (for ADMM) (r/i,spin)
!> \param amat      associated ADMM basis projection matrix (r/i,spin)
!> \author JGH
! **************************************************************************************************
   TYPE kpoint_env_type
      INTEGER                                           :: nkpoint = -1
      REAL(KIND=dp)                                     :: wkp = 0.0_dp
      REAL(KIND=dp), DIMENSION(3)                       :: xkp = 0.0_dp
      LOGICAL                                           :: is_local = .FALSE.
      TYPE(mo_set_type), DIMENSION(:, :), POINTER     :: mos => NULL()
      TYPE(cp_fm_type), DIMENSION(:, :), POINTER      :: pmat => NULL()
      TYPE(cp_fm_type), DIMENSION(:, :), POINTER      :: wmat => NULL()
      TYPE(cp_fm_type), DIMENSION(:, :), POINTER      :: smat => NULL()
      TYPE(cp_fm_type), DIMENSION(:, :), POINTER      :: amat => NULL()
   END TYPE kpoint_env_type

   TYPE kpoint_env_p_type
      TYPE(kpoint_env_type), POINTER                    :: kpoint_env => NULL()
   END TYPE kpoint_env_p_type

! **************************************************************************************************
!> \brief Rotation matrices for basis sets
!> \param rmat      atom basis function rotation matrix
!> \author JGH
! **************************************************************************************************
   TYPE kind_rotmat_type
      REAL(KIND=dp), DIMENSION(:, :), POINTER           :: rmat => NULL()
   END TYPE kind_rotmat_type

! **************************************************************************************************
!> \brief Keeps symmetry information about a specific k-point
!> \param apply_symmetry ...
!> \param nwght     kpoint multiplicity
!> \param xkp       kpoint coordinates
!> \param rot       rotation matrices
!> \param f0        atom permutation
!> \author JGH
! **************************************************************************************************
   TYPE kpoint_sym_type
      LOGICAL                                           :: apply_symmetry = .FALSE.
      INTEGER                                           :: nwght = -1
      INTEGER                                           :: nwred = -1
      REAL(KIND=dp), DIMENSION(:, :), POINTER           :: xkp => NULL()
      REAL(KIND=dp), DIMENSION(:, :, :), POINTER        :: rot => NULL()
      INTEGER, DIMENSION(:), POINTER                    :: rotp => NULL()
      INTEGER, DIMENSION(:, :), POINTER                 :: f0 => NULL()
   END TYPE kpoint_sym_type

   TYPE kpoint_sym_p_type
      TYPE(kpoint_sym_type), POINTER                    :: kpoint_sym => NULL()
   END TYPE kpoint_sym_p_type

! **************************************************************************************************
!> \brief Contains information about kpoints
!> \par History
!>       2014.07 created [JGH]
!> \param kp_scheme           [input] Type of kpoint grid
!> \param nkp_grid            [input] Grid points
!> \param kp_shift            [input] Shift of the grid
!> \param use_real_wfn        [input] real/complex wfn
!> \param symmetry            [input] use symmetry (atoms) to reduce kpoints
!> \param full_grid           [input] don't reduce kpoints at all
!> \param verbose             [input] more output information
!> \param eps_geo             [input] accuracy of atom symmetry detection
!> \param parallel_group_size [input] kpoint group size
!> \param nkp     number of kpoints
!> \param xkp     kpoint coordinates
!> \param wkp     kpoint weights
!> \param para_env 'global' parallel environment
!> \param para_env_kp parallel environment of the kpoint calculation
!> \param para_env_inter_kp parallel environment between kpoints
!> \param iogrp  this kpoint group has the IO processor
!> \param nkp_groups   number of kpoint groups
!> \param kp_dist      kpoints distribution on groups
!> \param kp_range     kpoints distribution for local processor
!> \param blacs_env    BLACS env for the kpoint group
!> \param opmats       Operator matrices
!> \param kp_env       Information for each kpoint
!> \param mpools       FM matrix pools for kpoint groups
!> \author JGH
! **************************************************************************************************
   TYPE kpoint_type
      CHARACTER(LEN=default_string_length)    :: kp_scheme = ""
      INTEGER, DIMENSION(3)                   :: nkp_grid = -1
      REAL(KIND=dp), DIMENSION(3)             :: kp_shift = 0.0_dp
      LOGICAL                                 :: use_real_wfn = .FALSE.
      LOGICAL                                 :: symmetry = .FALSE.
      LOGICAL                                 :: full_grid = .FALSE.
      LOGICAL                                 :: verbose = .FALSE.
      REAL(KIND=dp)                           :: eps_geo = 0.0_dp
      INTEGER                                 :: parallel_group_size = -1
      INTEGER                                 :: nkp = -1
      REAL(KIND=dp), DIMENSION(:, :), POINTER :: xkp => Null()
      REAL(KIND=dp), DIMENSION(:), POINTER    :: wkp => Null()
      ! parallel environment
      TYPE(mp_para_env_type), POINTER         :: para_env => Null()
      TYPE(cp_blacs_env_type), POINTER        :: blacs_env_all => Null()
      TYPE(mp_para_env_type), POINTER         :: para_env_kp => Null(), &
                                                 para_env_inter_kp => Null()
      LOGICAL                                 :: iogrp = .FALSE.
      INTEGER                                 :: nkp_groups = -1
      INTEGER, DIMENSION(:, :), POINTER       :: kp_dist => Null()
      INTEGER, DIMENSION(2)                   :: kp_range = -1
      TYPE(cp_blacs_env_type), POINTER        :: blacs_env => Null()
      INTEGER, DIMENSION(:, :, :), POINTER    :: cell_to_index => Null()
      INTEGER, DIMENSION(:, :), POINTER       :: index_to_cell => Null()
      TYPE(neighbor_list_set_p_type), &
         DIMENSION(:), POINTER                :: sab_nl => Null(), &
                                                 sab_nl_nosym => Null()
      ! environment
      TYPE(kpoint_env_p_type), DIMENSION(:), &
         POINTER                              :: kp_env => Null()
      TYPE(kpoint_env_p_type), DIMENSION(:), &
         POINTER                              :: kp_aux_env => Null()
      ! symmetry
      TYPE(kpoint_sym_p_type), DIMENSION(:), &
         POINTER                              :: kp_sym => Null()
      INTEGER, DIMENSION(:), POINTER          :: atype => Null()
      INTEGER, DIMENSION(:), POINTER          :: ibrot => Null()
      TYPE(kind_rotmat_type), DIMENSION(:, :), &
         POINTER                              :: kind_rotmat => Null()
      ! pools
      TYPE(qs_matrix_pools_type), POINTER     :: mpools => Null()
      TYPE(qs_diis_buffer_type_kp), POINTER   :: scf_diis_buffer => Null()
      TYPE(qs_matrix_pools_type), POINTER     :: mpools_aux_fit => Null()
   END TYPE kpoint_type

! **************************************************************************************************

CONTAINS

! **************************************************************************************************
!> \brief Create a kpoint environment
!> \param kpoint  All the kpoint information
!> \author JGH
! **************************************************************************************************
   SUBROUTINE kpoint_create(kpoint)
      TYPE(kpoint_type), POINTER                         :: kpoint

      CPASSERT(.NOT. ASSOCIATED(kpoint))

      ALLOCATE (kpoint)

      kpoint%kp_scheme = ""
      kpoint%nkp_grid = 0
      kpoint%kp_shift = 0.0_dp
      kpoint%symmetry = .FALSE.
      kpoint%verbose = .FALSE.
      kpoint%full_grid = .FALSE.
      kpoint%use_real_wfn = .FALSE.
      kpoint%eps_geo = 1.0e-6_dp
      kpoint%parallel_group_size = -1

      kpoint%nkp = 0

      NULLIFY (kpoint%xkp, kpoint%wkp)
      NULLIFY (kpoint%kp_dist)

      NULLIFY (kpoint%para_env)
      NULLIFY (kpoint%blacs_env_all)
      NULLIFY (kpoint%para_env_kp, kpoint%para_env_inter_kp)
      NULLIFY (kpoint%blacs_env)
      kpoint%nkp_groups = 0
      kpoint%iogrp = .FALSE.
      kpoint%kp_range = 0

      NULLIFY (kpoint%kp_env)
      NULLIFY (kpoint%mpools)

      ALLOCATE (kpoint%cell_to_index(0:0, 0:0, 0:0))
      kpoint%cell_to_index(:, :, :) = 1

      ALLOCATE (kpoint%index_to_cell(0:0, 0:0))
      kpoint%index_to_cell(:, :) = 0

   END SUBROUTINE kpoint_create

! **************************************************************************************************
!> \brief  Release a kpoint environment, deallocate all data
!> \param kpoint  The kpoint environment
!> \author JGH
! **************************************************************************************************
   SUBROUTINE kpoint_release(kpoint)
      TYPE(kpoint_type), POINTER                         :: kpoint

      INTEGER                                            :: i, ik, j

      IF (ASSOCIATED(kpoint)) THEN

         IF (ASSOCIATED(kpoint%xkp)) THEN
            DEALLOCATE (kpoint%xkp)
         END IF
         IF (ASSOCIATED(kpoint%wkp)) THEN
            DEALLOCATE (kpoint%wkp)
         END IF
         IF (ASSOCIATED(kpoint%kp_dist)) THEN
            DEALLOCATE (kpoint%kp_dist)
         END IF

         CALL mpools_release(kpoint%mpools)
         CALL mpools_release(kpoint%mpools_aux_fit)

         CALL cp_blacs_env_release(kpoint%blacs_env)
         CALL cp_blacs_env_release(kpoint%blacs_env_all)

         CALL mp_para_env_release(kpoint%para_env)
         CALL mp_para_env_release(kpoint%para_env_kp)
         CALL mp_para_env_release(kpoint%para_env_inter_kp)

         IF (ASSOCIATED(kpoint%cell_to_index)) DEALLOCATE (kpoint%cell_to_index)
         IF (ASSOCIATED(kpoint%index_to_cell)) DEALLOCATE (kpoint%index_to_cell)

         IF (ASSOCIATED(kpoint%kp_env)) THEN
            DO ik = 1, SIZE(kpoint%kp_env)
               CALL kpoint_env_release(kpoint%kp_env(ik)%kpoint_env)
            END DO
            DEALLOCATE (kpoint%kp_env)
         END IF

         IF (ASSOCIATED(kpoint%kp_aux_env)) THEN
            DO ik = 1, SIZE(kpoint%kp_aux_env)
               CALL kpoint_env_release(kpoint%kp_aux_env(ik)%kpoint_env)
            END DO
            DEALLOCATE (kpoint%kp_aux_env)
         END IF

         IF (ASSOCIATED(kpoint%kp_sym)) THEN
            DO ik = 1, SIZE(kpoint%kp_sym)
               CALL kpoint_sym_release(kpoint%kp_sym(ik)%kpoint_sym)
            END DO
            DEALLOCATE (kpoint%kp_sym)
         END IF

         IF (ASSOCIATED(kpoint%atype)) DEALLOCATE (kpoint%atype)
         IF (ASSOCIATED(kpoint%ibrot)) DEALLOCATE (kpoint%ibrot)

         IF (ASSOCIATED(kpoint%kind_rotmat)) THEN
            DO i = 1, SIZE(kpoint%kind_rotmat, 1)
               DO j = 1, SIZE(kpoint%kind_rotmat, 2)
                  IF (ASSOCIATED(kpoint%kind_rotmat(i, j)%rmat)) THEN
                     DEALLOCATE (kpoint%kind_rotmat(i, j)%rmat)
                  END IF
               END DO
            END DO
            DEALLOCATE (kpoint%kind_rotmat)
         END IF

         IF (ASSOCIATED(kpoint%scf_diis_buffer)) THEN
            CALL qs_diis_b_release_kp(kpoint%scf_diis_buffer)
            DEALLOCATE (kpoint%scf_diis_buffer)
         END IF

         DEALLOCATE (kpoint)

      END IF

   END SUBROUTINE kpoint_release

! **************************************************************************************************
!> \brief Retrieve information from a kpoint environment
!> \param kpoint        The kpoint environment
!> \param kp_scheme     Type of kpoint grid
!> \param nkp_grid      Grid points
!> \param kp_shift      Shift of the grid
!> \param symmetry      use symmetry (atoms) to reduce kpoints
!> \param verbose       more output information
!> \param full_grid     don't reduce kpoints at all
!> \param use_real_wfn  real/complex wfn
!> \param eps_geo       accuracy of atom symmetry detection
!> \param parallel_group_size kpoint group size
!> \param kp_range      kpoints distribution for local processor
!> \param nkp           number of kpoints
!> \param xkp           kpoint coordinates in units of b-vector
!> \param wkp           kpoint weights
!> \param para_env      'global' parallel environment
!> \param blacs_env_all BLACS env for the total environment
!> \param para_env_kp   parallel environment of the kpoint calculation
!> \param para_env_inter_kp   parallel environment between kpoints
!> \param blacs_env     BLACS env for the kpoint group
!> \param kp_env        Information for each kpoint
!> \param kp_aux_env ...
!> \param mpools        FM matrix pools for kpoint groups
!> \param iogrp         this kpoint group has the IO processor
!> \param nkp_groups    number of kpoint groups
!> \param kp_dist       kpoints distribution on groups
!> \param cell_to_index given a cell triple, returns the real space index
!> \param index_to_cell ...
!> \param sab_nl        neighbourlist that defines real space matrices
!> \param sab_nl_nosym neighbourlist that defines real space matrices, non-symmetric
!> \author JGH
! **************************************************************************************************
   SUBROUTINE get_kpoint_info(kpoint, kp_scheme, nkp_grid, kp_shift, symmetry, verbose, &
                              full_grid, use_real_wfn, eps_geo, parallel_group_size, kp_range, nkp, xkp, wkp, &
                              para_env, blacs_env_all, para_env_kp, para_env_inter_kp, blacs_env, &
                              kp_env, kp_aux_env, mpools, iogrp, nkp_groups, kp_dist, cell_to_index, index_to_cell, &
                              sab_nl, sab_nl_nosym)
      TYPE(kpoint_type), INTENT(IN)                      :: kpoint
      CHARACTER(LEN=*), OPTIONAL                         :: kp_scheme
      INTEGER, DIMENSION(3), OPTIONAL                    :: nkp_grid
      REAL(KIND=dp), DIMENSION(3), OPTIONAL              :: kp_shift
      LOGICAL, OPTIONAL                                  :: symmetry, verbose, full_grid, &
                                                            use_real_wfn
      REAL(KIND=dp), OPTIONAL                            :: eps_geo
      INTEGER, OPTIONAL                                  :: parallel_group_size
      INTEGER, DIMENSION(2), OPTIONAL                    :: kp_range
      INTEGER, OPTIONAL                                  :: nkp
      REAL(KIND=dp), DIMENSION(:, :), OPTIONAL, POINTER  :: xkp
      REAL(KIND=dp), DIMENSION(:), OPTIONAL, POINTER     :: wkp
      TYPE(mp_para_env_type), OPTIONAL, POINTER          :: para_env
      TYPE(cp_blacs_env_type), OPTIONAL, POINTER         :: blacs_env_all
      TYPE(mp_para_env_type), OPTIONAL, POINTER          :: para_env_kp, para_env_inter_kp
      TYPE(cp_blacs_env_type), OPTIONAL, POINTER         :: blacs_env
      TYPE(kpoint_env_p_type), DIMENSION(:), OPTIONAL, &
         POINTER                                         :: kp_env, kp_aux_env
      TYPE(qs_matrix_pools_type), OPTIONAL, POINTER      :: mpools
      LOGICAL, OPTIONAL                                  :: iogrp
      INTEGER, OPTIONAL                                  :: nkp_groups
      INTEGER, DIMENSION(:, :), OPTIONAL, POINTER        :: kp_dist
      INTEGER, DIMENSION(:, :, :), OPTIONAL, POINTER     :: cell_to_index
      INTEGER, DIMENSION(:, :), OPTIONAL, POINTER        :: index_to_cell
      TYPE(neighbor_list_set_p_type), DIMENSION(:), &
         OPTIONAL, POINTER                               :: sab_nl, sab_nl_nosym

      IF (PRESENT(kp_scheme)) kp_scheme = kpoint%kp_scheme
      IF (PRESENT(nkp_grid)) nkp_grid = kpoint%nkp_grid
      IF (PRESENT(kp_shift)) kp_shift = kpoint%kp_shift
      IF (PRESENT(symmetry)) symmetry = kpoint%symmetry
      IF (PRESENT(verbose)) verbose = kpoint%verbose
      IF (PRESENT(full_grid)) full_grid = kpoint%full_grid
      IF (PRESENT(use_real_wfn)) use_real_wfn = kpoint%use_real_wfn
      IF (PRESENT(eps_geo)) eps_geo = kpoint%eps_geo
      IF (PRESENT(parallel_group_size)) parallel_group_size = kpoint%parallel_group_size

      IF (PRESENT(nkp)) nkp = kpoint%nkp
      IF (PRESENT(wkp)) wkp => kpoint%wkp
      IF (PRESENT(xkp)) xkp => kpoint%xkp

      IF (PRESENT(para_env)) para_env => kpoint%para_env
      IF (PRESENT(para_env_kp)) para_env_kp => kpoint%para_env_kp
      IF (PRESENT(para_env_inter_kp)) para_env_inter_kp => kpoint%para_env_inter_kp
      IF (PRESENT(blacs_env_all)) blacs_env_all => kpoint%blacs_env_all
      IF (PRESENT(blacs_env)) blacs_env => kpoint%blacs_env

      IF (PRESENT(iogrp)) iogrp = kpoint%iogrp
      IF (PRESENT(kp_range)) kp_range = kpoint%kp_range
      IF (PRESENT(nkp_groups)) nkp_groups = kpoint%nkp_groups
      IF (PRESENT(kp_dist)) kp_dist => kpoint%kp_dist

      IF (PRESENT(kp_env)) kp_env => kpoint%kp_env
      IF (PRESENT(kp_aux_env)) kp_aux_env => kpoint%kp_aux_env
      IF (PRESENT(mpools)) mpools => kpoint%mpools

      IF (PRESENT(cell_to_index)) cell_to_index => kpoint%cell_to_index
      IF (PRESENT(index_to_cell)) index_to_cell => kpoint%index_to_cell
      IF (PRESENT(sab_nl)) sab_nl => kpoint%sab_nl
      IF (PRESENT(sab_nl_nosym)) sab_nl_nosym => kpoint%sab_nl_nosym

   END SUBROUTINE get_kpoint_info

! **************************************************************************************************
!> \brief Set information in a kpoint environment
!> \param kpoint        The kpoint environment
!> \param kp_scheme     Type of kpoint grid
!> \param nkp_grid      Grid points
!> \param kp_shift      Shift of the grid
!> \param symmetry      use symmetry (atoms) to reduce kpoints
!> \param verbose       more output information
!> \param full_grid     don't reduce kpoints at all
!> \param use_real_wfn  real/complex wfn
!> \param eps_geo       accuracy of atom symmetry detection
!> \param parallel_group_size kpoint group size
!> \param kp_range      kpoints distribution for local processor
!> \param nkp           number of kpoints
!> \param xkp           kpoint coordinates
!> \param wkp           kpoint weights
!> \param para_env      'global' parallel environment
!> \param blacs_env_all BLACS env for the total environment
!> \param para_env_kp   parallel environment of the kpoint calculation
!> \param para_env_inter_kp   parallel environment between kpoints
!> \param blacs_env     BLACS env for the kpoint group
!> \param kp_env        Information for each kpoint
!> \param kp_aux_env ...
!> \param mpools        FM matrix pools for kpoint groups
!> \param iogrp         this kpoint group has the IO processor
!> \param nkp_groups    number of kpoint groups
!> \param kp_dist       kpoints distribution on groups
!> \param cell_to_index given a cell triple, returns the real space index
!> \param index_to_cell ...
!> \param sab_nl        neighbourlist that defines real space matrices
!> \param sab_nl_nosym  neighbourlist that defines real space matrices
!> \author JGH
! **************************************************************************************************
   SUBROUTINE set_kpoint_info(kpoint, kp_scheme, nkp_grid, kp_shift, symmetry, verbose, &
                              full_grid, use_real_wfn, eps_geo, parallel_group_size, kp_range, nkp, xkp, wkp, &
                              para_env, blacs_env_all, para_env_kp, para_env_inter_kp, blacs_env, &
                              kp_env, kp_aux_env, mpools, iogrp, nkp_groups, kp_dist, cell_to_index, index_to_cell, &
                              sab_nl, sab_nl_nosym)
      TYPE(kpoint_type), INTENT(INOUT)                   :: kpoint
      CHARACTER(LEN=*), OPTIONAL                         :: kp_scheme
      INTEGER, DIMENSION(3), OPTIONAL                    :: nkp_grid
      REAL(KIND=dp), DIMENSION(3), OPTIONAL              :: kp_shift
      LOGICAL, OPTIONAL                                  :: symmetry, verbose, full_grid, &
                                                            use_real_wfn
      REAL(KIND=dp), OPTIONAL                            :: eps_geo
      INTEGER, OPTIONAL                                  :: parallel_group_size
      INTEGER, DIMENSION(2), OPTIONAL                    :: kp_range
      INTEGER, OPTIONAL                                  :: nkp
      REAL(KIND=dp), DIMENSION(:, :), OPTIONAL, POINTER  :: xkp
      REAL(KIND=dp), DIMENSION(:), OPTIONAL, POINTER     :: wkp
      TYPE(mp_para_env_type), OPTIONAL, POINTER          :: para_env
      TYPE(cp_blacs_env_type), OPTIONAL, POINTER         :: blacs_env_all
      TYPE(mp_para_env_type), OPTIONAL, POINTER          :: para_env_kp, para_env_inter_kp
      TYPE(cp_blacs_env_type), OPTIONAL, POINTER         :: blacs_env
      TYPE(kpoint_env_p_type), DIMENSION(:), OPTIONAL, &
         POINTER                                         :: kp_env, kp_aux_env
      TYPE(qs_matrix_pools_type), OPTIONAL, POINTER      :: mpools
      LOGICAL, OPTIONAL                                  :: iogrp
      INTEGER, OPTIONAL                                  :: nkp_groups
      INTEGER, DIMENSION(:, :), OPTIONAL, POINTER        :: kp_dist
      INTEGER, DIMENSION(:, :, :), OPTIONAL, POINTER     :: cell_to_index
      INTEGER, DIMENSION(:, :), OPTIONAL, POINTER        :: index_to_cell
      TYPE(neighbor_list_set_p_type), DIMENSION(:), &
         OPTIONAL, POINTER                               :: sab_nl, sab_nl_nosym

      IF (PRESENT(kp_scheme)) kpoint%kp_scheme = kp_scheme
      IF (PRESENT(nkp_grid)) kpoint%nkp_grid = nkp_grid
      IF (PRESENT(kp_shift)) kpoint%kp_shift = kp_shift
      IF (PRESENT(symmetry)) kpoint%symmetry = symmetry
      IF (PRESENT(verbose)) kpoint%verbose = verbose
      IF (PRESENT(full_grid)) kpoint%full_grid = full_grid
      IF (PRESENT(use_real_wfn)) kpoint%use_real_wfn = use_real_wfn
      IF (PRESENT(eps_geo)) kpoint%eps_geo = eps_geo
      IF (PRESENT(parallel_group_size)) kpoint%parallel_group_size = parallel_group_size

      IF (PRESENT(nkp)) kpoint%nkp = nkp
      IF (PRESENT(wkp)) kpoint%wkp => wkp
      IF (PRESENT(xkp)) kpoint%xkp => xkp

      IF (PRESENT(para_env)) kpoint%para_env => para_env
      IF (PRESENT(para_env_kp)) kpoint%para_env_kp => para_env_kp
      IF (PRESENT(para_env_inter_kp)) kpoint%para_env_inter_kp => para_env_inter_kp
      IF (PRESENT(blacs_env_all)) kpoint%blacs_env_all => blacs_env_all
      IF (PRESENT(blacs_env)) kpoint%blacs_env => blacs_env

      IF (PRESENT(iogrp)) kpoint%iogrp = iogrp
      IF (PRESENT(kp_range)) kpoint%kp_range = kp_range
      IF (PRESENT(nkp_groups)) kpoint%nkp_groups = nkp_groups
      IF (PRESENT(kp_dist)) kpoint%kp_dist => kp_dist

      IF (PRESENT(kp_env)) kpoint%kp_env => kp_env
      IF (PRESENT(kp_env)) kpoint%kp_aux_env => kp_aux_env
      IF (PRESENT(mpools)) kpoint%mpools => mpools
      IF (PRESENT(sab_nl)) kpoint%sab_nl => sab_nl
      IF (PRESENT(sab_nl_nosym)) kpoint%sab_nl_nosym => sab_nl_nosym

      IF (PRESENT(cell_to_index)) THEN
         IF (ASSOCIATED(kpoint%cell_to_index)) DEALLOCATE (kpoint%cell_to_index)
         kpoint%cell_to_index => cell_to_index
      END IF

      IF (PRESENT(index_to_cell)) THEN
         IF (ASSOCIATED(kpoint%index_to_cell)) DEALLOCATE (kpoint%index_to_cell)
         kpoint%index_to_cell => index_to_cell
      END IF

   END SUBROUTINE set_kpoint_info

! **************************************************************************************************
!> \brief Read the kpoint input section
!> \param kpoint  The kpoint environment
!> \param kpoint_section The input section
!> \param a_vec ...
!> \author JGH
! **************************************************************************************************
   SUBROUTINE read_kpoint_section(kpoint, kpoint_section, a_vec)
      TYPE(kpoint_type), INTENT(INOUT)                   :: kpoint
      TYPE(section_vals_type), POINTER                   :: kpoint_section
      REAL(KIND=dp), DIMENSION(3, 3), INTENT(IN)         :: a_vec

      CHARACTER(LEN=default_string_length)               :: ustr
      CHARACTER(LEN=default_string_length), &
         DIMENSION(:), POINTER                           :: tmpstringlist
      INTEGER                                            :: i, n_rep, nval, wfntype
      LOGICAL                                            :: available
      REAL(KIND=dp)                                      :: ff
      REAL(KIND=dp), DIMENSION(:), POINTER               :: reallist

      CALL section_vals_get(kpoint_section, explicit=available)

      IF (available) THEN
         CALL section_vals_val_get(kpoint_section, "SCHEME", c_vals=tmpstringlist)
         nval = SIZE(tmpstringlist)
         CPASSERT(nval >= 1)
         kpoint%kp_scheme = tmpstringlist(1)
         CALL uppercase(kpoint%kp_scheme)

         ! SCHEME [None, Gamma, Monkhorst-Pack, MacDonald, General]
         SELECT CASE (kpoint%kp_scheme)
         CASE ("NONE")
            ! do nothing
         CASE ("GAMMA")
            ! do nothing
         CASE ("MONKHORST-PACK")
            CPASSERT(nval >= 4)
            DO i = 2, 4
               READ (tmpstringlist(i), *) kpoint%nkp_grid(i - 1)
            END DO
         CASE ("MACDONALD")
            CPASSERT(nval >= 7)
            DO i = 2, 4
               READ (tmpstringlist(i), *) kpoint%nkp_grid(i - 1)
            END DO
            DO i = 5, 7
               READ (tmpstringlist(i), *) kpoint%kp_shift(i - 4)
            END DO
         CASE ("GENERAL")
            CALL section_vals_val_get(kpoint_section, "UNITS", c_val=ustr)
            CALL uppercase(ustr)
            CALL section_vals_val_get(kpoint_section, "KPOINT", n_rep_val=n_rep)
            kpoint%nkp = n_rep
            ALLOCATE (kpoint%xkp(3, n_rep), kpoint%wkp(n_rep))
            DO i = 1, n_rep
               CALL section_vals_val_get(kpoint_section, "KPOINT", i_rep_val=i, &
                                         r_vals=reallist)
               nval = SIZE(reallist)
               CPASSERT(nval >= 4)
               SELECT CASE (ustr)
               CASE ("B_VECTOR")
                  kpoint%xkp(1:3, i) = reallist(1:3)
               CASE ("CART_ANGSTROM")
                  kpoint%xkp(1:3, i) = (reallist(1)*a_vec(1, 1:3) + &
                                        reallist(2)*a_vec(2, 1:3) + &
                                        reallist(3)*a_vec(3, 1:3))/twopi*angstrom
               CASE ("CART_BOHR")
                  kpoint%xkp(1:3, i) = (reallist(1)*a_vec(1, 1:3) + &
                                        reallist(2)*a_vec(2, 1:3) + &
                                        reallist(3)*a_vec(3, 1:3))/twopi
               CASE DEFAULT
                  CPABORT("Unknown Unit for kpoint definition")
               END SELECT
               kpoint%wkp(i) = reallist(4)
            END DO
            ff = 1.0_dp/SUM(kpoint%wkp(:))
            kpoint%wkp(:) = ff*kpoint%wkp(:)
         CASE DEFAULT
            CPABORT("")
         END SELECT

         CALL section_vals_val_get(kpoint_section, "SYMMETRY", l_val=kpoint%symmetry)
         CALL section_vals_val_get(kpoint_section, "WAVEFUNCTIONS", i_val=wfntype)
         CALL section_vals_val_get(kpoint_section, "VERBOSE", l_val=kpoint%verbose)
         CALL section_vals_val_get(kpoint_section, "FULL_GRID", l_val=kpoint%full_grid)
         CALL section_vals_val_get(kpoint_section, "EPS_GEO", r_val=kpoint%eps_geo)
         CALL section_vals_val_get(kpoint_section, "PARALLEL_GROUP_SIZE", &
                                   i_val=kpoint%parallel_group_size)
         SELECT CASE (wfntype)
         CASE (use_real_wfn)
            kpoint%use_real_wfn = .TRUE.
         CASE (use_complex_wfn)
            kpoint%use_real_wfn = .FALSE.
         CASE DEFAULT
            CPABORT("")
         END SELECT

      ELSE
         kpoint%kp_scheme = "NONE"
      END IF

   END SUBROUTINE read_kpoint_section

! **************************************************************************************************
!> \brief Write information on the kpoints to output
!> \param kpoint  The kpoint environment
!> \param dft_section  DFT section information
!> \author JGH
! **************************************************************************************************
   SUBROUTINE write_kpoint_info(kpoint, dft_section)
      TYPE(kpoint_type), INTENT(IN)                      :: kpoint
      TYPE(section_vals_type), INTENT(IN)                :: dft_section

      INTEGER                                            :: i, punit
      TYPE(cp_logger_type), POINTER                      :: logger

      NULLIFY (logger)
      logger => cp_get_default_logger()

      punit = cp_print_key_unit_nr(logger, dft_section, "PRINT%KPOINTS", extension=".Log")
      IF (punit > 0) THEN

         IF (kpoint%kp_scheme /= "NONE") THEN
            WRITE (punit, '(/," ",79("*"),/,T37,A,/," ",79("*"))') "Kpoints"
         END IF
         SELECT CASE (kpoint%kp_scheme)
         CASE ("NONE")
            ! be silent
         CASE ("GAMMA")
            WRITE (punit, '(A,T57,A)') ' BRILLOUIN|', ' Gamma-point calculation'
         CASE ("MONKHORST-PACK")
            WRITE (punit, '(A,T61,A20)') ' BRILLOUIN| K-point scheme ', '      Monkhorst-Pack'
            WRITE (punit, '(A,T66,3I5)') ' BRILLOUIN| K-Point grid', kpoint%nkp_grid
            WRITE (punit, '(A,T66,G15.6)') ' BRILLOUIN| Accuracy in Symmetry determination', kpoint%eps_geo
         CASE ("MACDONALD")
            WRITE (punit, '(A,T71,A10)') ' BRILLOUIN| K-point scheme ', ' MacDonald'
            WRITE (punit, '(A,T66,3I5)') ' BRILLOUIN| K-Point grid', kpoint%nkp_grid
            WRITE (punit, '(A,T51,3F10.4)') ' BRILLOUIN| K-Point shift', kpoint%kp_shift
            WRITE (punit, '(A,T66,G15.6)') ' BRILLOUIN| Accuracy in Symmetry determination', kpoint%eps_geo
         CASE ("GENERAL")
            WRITE (punit, '(A,T71,A10)') ' BRILLOUIN| K-point scheme ', '   General'
         CASE DEFAULT
            CPABORT("")
         END SELECT
         IF (kpoint%kp_scheme /= "NONE") THEN
            IF (kpoint%symmetry) THEN
               WRITE (punit, '(A,T76,A)') ' BRILLOUIN| K-Point point group symmetrization', '   ON'
            ELSE
               WRITE (punit, '(A,T76,A)') ' BRILLOUIN| K-Point point group symmetrization', '  OFF'
            END IF
            IF (kpoint%use_real_wfn) THEN
               WRITE (punit, '(A,T76,A)') ' BRILLOUIN| Wavefunction type', ' REAL'
            ELSE
               WRITE (punit, '(A,T73,A)') ' BRILLOUIN| Wavefunction type', ' COMPLEX'
            END IF
            IF (kpoint%full_grid) THEN
               WRITE (punit, '(A,T76,A)') ' BRILLOUIN| Use full k-point grid     '
            END IF
            IF (kpoint%kp_scheme /= "GAMMA") THEN
               WRITE (punit, '(A,T71,I10)') ' BRILLOUIN| List of Kpoints [2 Pi/Bohr]', kpoint%nkp
               WRITE (punit, '(A,T30,A,T48,A,T63,A,T78,A)') &
                  ' BRILLOUIN| Number ', 'Weight', 'X', 'Y', 'Z'
               DO i = 1, kpoint%nkp
                  WRITE (punit, '(A,I5,3X,4F15.5)') ' BRILLOUIN| ', i, kpoint%wkp(i), &
                     kpoint%xkp(1, i), kpoint%xkp(2, i), kpoint%xkp(3, i)
               END DO
            END IF
            WRITE (punit, '(" ",79("*"))')
         END IF

      END IF
      CALL cp_print_key_finished_output(punit, logger, dft_section, "PRINT%KPOINTS")

   END SUBROUTINE write_kpoint_info

! **************************************************************************************************
!> \brief Create a single kpoint environment
!> \param kp_env  Single kpoint environment
!> \author JGH
! **************************************************************************************************
   SUBROUTINE kpoint_env_create(kp_env)
      TYPE(kpoint_env_type), POINTER                     :: kp_env

      CPASSERT(.NOT. ASSOCIATED(kp_env))

      ALLOCATE (kp_env)

      kp_env%nkpoint = 0
      kp_env%wkp = 0.0_dp
      kp_env%xkp = 0.0_dp
      kp_env%is_local = .FALSE.

      NULLIFY (kp_env%mos)
      NULLIFY (kp_env%pmat)
      NULLIFY (kp_env%wmat)
      NULLIFY (kp_env%smat)
      NULLIFY (kp_env%amat)

   END SUBROUTINE kpoint_env_create

! **************************************************************************************************
!> \brief Release a single kpoint environment
!> \param kp_env  Single kpoint environment
!> \author JGH
! **************************************************************************************************
   SUBROUTINE kpoint_env_release(kp_env)
      TYPE(kpoint_env_type), POINTER                     :: kp_env

      INTEGER                                            :: ic, is

      IF (ASSOCIATED(kp_env)) THEN

         IF (ASSOCIATED(kp_env%mos)) THEN
            DO is = 1, SIZE(kp_env%mos, 2)
               DO ic = 1, SIZE(kp_env%mos, 1)
                  CALL deallocate_mo_set(kp_env%mos(ic, is))
               END DO
            END DO
            DEALLOCATE (kp_env%mos)
         END IF

         CALL cp_fm_release(kp_env%pmat)
         CALL cp_fm_release(kp_env%wmat)
         CALL cp_fm_release(kp_env%smat)
         CALL cp_fm_release(kp_env%amat)

         DEALLOCATE (kp_env)

      END IF

   END SUBROUTINE kpoint_env_release

! **************************************************************************************************
!> \brief Get information from a single kpoint environment
!> \param kpoint_env Single kpoint environment
!> \param nkpoint    Index of kpoint
!> \param wkp        Weight of kpoint
!> \param xkp        Coordinates of kpoint
!> \param is_local   Is this kpoint local (single cpu group)
!> \param mos        MOs of this kpoint
!> \author JGH
! **************************************************************************************************
   SUBROUTINE get_kpoint_env(kpoint_env, nkpoint, wkp, xkp, is_local, mos)
      TYPE(kpoint_env_type), INTENT(IN)                  :: kpoint_env
      INTEGER, OPTIONAL                                  :: nkpoint
      REAL(KIND=dp), OPTIONAL                            :: wkp
      REAL(KIND=dp), DIMENSION(3), OPTIONAL              :: xkp
      LOGICAL, OPTIONAL                                  :: is_local
      TYPE(mo_set_type), DIMENSION(:, :), OPTIONAL, &
         POINTER                                         :: mos

      IF (PRESENT(nkpoint)) nkpoint = kpoint_env%nkpoint
      IF (PRESENT(wkp)) wkp = kpoint_env%wkp
      IF (PRESENT(xkp)) xkp = kpoint_env%xkp
      IF (PRESENT(is_local)) is_local = kpoint_env%is_local
      IF (PRESENT(mos)) mos => kpoint_env%mos

   END SUBROUTINE get_kpoint_env

! **************************************************************************************************
!> \brief Create a single kpoint symmetry environment
!> \param kp_sym  ...
!> \author JGH
! **************************************************************************************************
   SUBROUTINE kpoint_sym_create(kp_sym)
      TYPE(kpoint_sym_type), POINTER                     :: kp_sym

      CPASSERT(.NOT. ASSOCIATED(kp_sym))

      ALLOCATE (kp_sym)

      kp_sym%nwght = 0
      kp_sym%nwred = 0
      kp_sym%apply_symmetry = .FALSE.

      NULLIFY (kp_sym%rot)
      NULLIFY (kp_sym%xkp)
      NULLIFY (kp_sym%rotp)
      NULLIFY (kp_sym%f0)

   END SUBROUTINE kpoint_sym_create

! **************************************************************************************************
!> \brief Release a single kpoint symmetry environment
!> \param kp_sym  ...
!> \author JGH
! **************************************************************************************************
   SUBROUTINE kpoint_sym_release(kp_sym)
      TYPE(kpoint_sym_type), POINTER                     :: kp_sym

      IF (ASSOCIATED(kp_sym)) THEN

         IF (ASSOCIATED(kp_sym%rot)) THEN
            DEALLOCATE (kp_sym%rot)
         END IF
         IF (ASSOCIATED(kp_sym%xkp)) THEN
            DEALLOCATE (kp_sym%xkp)
         END IF
         IF (ASSOCIATED(kp_sym%f0)) THEN
            DEALLOCATE (kp_sym%f0)
         END IF
         IF (ASSOCIATED(kp_sym%rotp)) THEN
            DEALLOCATE (kp_sym%rotp)
         END IF

         DEALLOCATE (kp_sym)

      END IF

   END SUBROUTINE kpoint_sym_release

! **************************************************************************************************

END MODULE kpoint_types
