!-*- mode: F90 -*-!
!------------------------------------------------------------!
! Copyright (C) 2026 Wannier Developer Group                 !
!                                                            !
! This library is free software; you can redistribute it     !
! and/or modify it under the terms of the GNU Lesser General !
! Public License as published by the Free Software           !
! Foundation; either version 2.1 of the License, or (at your !
! option) any later version.                                 !
!                                                            !
! This library is distributed in the hope that it will be    !
! useful,but WITHOUT ANY WARRANTY; without even the implied  !
! warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR    !
! PURPOSE.  See the GNU Lesser General Public License for    !
! more details.                                              !
!                                                            !
! You should have received a copy of the GNU Lesser General  !
! Public License along with this library; if not, see        !
! <https://www.gnu.org/licenses/>.                           !
!                                                            !
! The webpage of the Wannier90 code is                       !
! <https://www.wannier.org>.                                 !
!                                                            !
! The Wannier90 code is hosted on GitHub                     !
! <https://github.com/wannier-developers/wannier90>          !
!------------------------------------------------------------!
!                                                            !
!  w90_postw90_common: routines used throughout postw90      !
!                                                            !
!------------------------------------------------------------!

module w90_postw90_common

  !! This contains the common variables and procedures needed to set up a Wannier
  !! interpolatation calculation for any physical property

  use w90_constants, only: dp
  use w90_error, only: w90_error_type, set_error_alloc, set_error_dealloc, set_error_fatal, &
                       set_error_input, set_error_fatal, set_error_file

  implicit none

  private

  public :: pw90common_fourier_R_to_k
  public :: pw90common_fourier_R_to_k_new
  public :: pw90common_fourier_R_to_k_new_second_d
  public :: pw90common_fourier_R_to_k_new_second_d_TB_conv
  public :: pw90common_fourier_R_to_k_vec
  public :: pw90common_fourier_R_to_k_vec_dadb
  public :: pw90common_fourier_R_to_k_vec_dadb_TB_conv
  public :: pw90common_get_occ
  public :: pw90common_kmesh_spacing
  public :: pw90common_wanint_data_dist
  public :: pw90common_wanint_get_kpoint_file
  public :: pw90common_wanint_setup
  public :: pw90common_wanint_w90_wannier90_readwrite_dist

  interface pw90common_kmesh_spacing
    module procedure kmesh_spacing_singleinteger
    module procedure kmesh_spacing_mesh
  end interface pw90common_kmesh_spacing

contains

  !================================================!
  !                   PUBLIC PROCEDURES
  ! Public procedures have names starting with wanint_
  !================================================!

  subroutine pw90common_wanint_setup(num_wann, print_output, real_lattice, mp_grid, ws_region, &
                                     ws_distance, effective_model, wigner_seitz, wannier_data, &
                                     stdout, seedname, timer, error, comm)
    !================================================!
    !
    !! Setup data ready for interpolation
    !
    !================================================!

    use w90_constants, only: dp
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_types, only: ws_region_type, ws_distance_type, wannier_data_type, &
                         print_output_type, timer_list_type
    use w90_comms, only: mpirank, w90_comm_type, comms_bcast
    use w90_postw90_types, only: wigner_seitz_type
    use w90_ws_distance, only: ws_translate_dist

    type(print_output_type), intent(in) :: print_output
    type(wigner_seitz_type), intent(inout) :: wigner_seitz
    type(ws_distance_type), intent(inout) :: ws_distance
    type(ws_region_type), intent(in) :: ws_region
    type(wannier_data_type), intent(in) :: wannier_data
    type(timer_list_type), intent(inout) :: timer
    type(w90_comm_type), intent(in) :: comm
    type(w90_error_type), allocatable, intent(out) :: error

    real(kind=dp), intent(in) :: real_lattice(3, 3)
    integer, intent(in) :: num_wann
    integer, intent(in) :: stdout
    integer, intent(in) :: mp_grid(3)
    logical, intent(in) :: effective_model
    character(len=50), intent(in)  :: seedname

    integer :: ierr, ir, file_unit, num_wann_loc
    logical :: on_root = .false.

    if (mpirank(comm) == 0) on_root = .true.

    ! Find nrpts, the number of points in the Wigner-Seitz cell
    if (effective_model) then
      if (on_root) then
        ! nrpts is read from file, together with num_wann
        open (newunit=file_unit, file=trim(seedname)//'_HH_R.dat', form='formatted', &
              status='old', err=101)
        read (file_unit, *) !header
        read (file_unit, *) num_wann_loc
        if (num_wann_loc /= num_wann) then
          call set_error_fatal(error, 'Inconsistent values of num_wann in '//trim(seedname) &
                               //'_HH_R.dat and '//trim(seedname)//'.win', comm)
          return
        end if
        read (file_unit, *) wigner_seitz%nrpts
        close (file_unit)
      end if
      call comms_bcast(wigner_seitz%nrpts, 1, error, comm)
      if (allocated(error)) return
    else
      call wignerseitz(print_output, real_lattice, mp_grid, ws_region, wigner_seitz, stdout, &
                       .true., timer, error, comm)
      if (allocated(error)) return
    end if

    ! Now can allocate several arrays
    allocate (wigner_seitz%irvec(3, wigner_seitz%nrpts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating irvec in pw90common_wanint_setup', comm)
      return
    end if
    wigner_seitz%irvec = 0
    allocate (wigner_seitz%crvec(3, wigner_seitz%nrpts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating crvec in pw90common_wanint_setup', comm)
      return
    end if
    wigner_seitz%crvec = 0.0_dp
    allocate (wigner_seitz%ndegen(wigner_seitz%nrpts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating ndegen in pw90common_wanint_setup', comm)
      return
    end if
    wigner_seitz%ndegen = 0

    ! Also rpt_origin, so that when effective_model=.true it is not
    ! passed to get_HH_R without being initialized.
    wigner_seitz%rpt_origin = 0

    ! If effective_model, this is done in get_HH_R
    if (.not. effective_model) then
      ! Set up the lattice vectors on the Wigner-Seitz supercell
      ! where the Wannier functions live

      call wignerseitz(print_output, real_lattice, mp_grid, ws_region, wigner_seitz, stdout, &
                       .false., timer, error, comm)
      if (allocated(error)) return

      ! Convert from reduced to Cartesian coordinates

      do ir = 1, wigner_seitz%nrpts
        ! Note that 'real_lattice' stores the lattice vectors as *rows*
        wigner_seitz%crvec(:, ir) = matmul(transpose(real_lattice), wigner_seitz%irvec(:, ir))
      end do

      if (ws_region%use_ws_distance) then
        call ws_translate_dist(ws_distance, ws_region, num_wann, wannier_data%centres, real_lattice, &
                               mp_grid, wigner_seitz%nrpts, wigner_seitz%irvec, error, comm)
        if (allocated(error)) return
      end if

      call wigner_seitz_opt_setup(ws_distance, ws_region, wigner_seitz, num_wann, real_lattice, &
                                  wigner_seitz%nrpts, error, comm)

    end if

    return

101 call set_error_file(error, 'Error in pw90common_wanint_setup: problem opening file '// &
                        trim(seedname)//'_HH_R.dat', comm)
    return

  end subroutine pw90common_wanint_setup

  !================================================!
  subroutine pw90common_wanint_get_kpoint_file(kpoint_dist, error, comm)
    !================================================!
    !
    !! read kpoints from kpoint.dat and distribute
    !
    !================================================!

    use w90_constants, only: dp
    use w90_io, only: io_date, io_time
    use w90_comms, only: mpirank, mpisize, w90_comm_type, comms_bcast
    use w90_postw90_types, only: kpoint_dist_type

    ! arguments
    type(kpoint_dist_type), intent(inout) :: kpoint_dist
    type(w90_comm_type), intent(in) :: comm
    type(w90_error_type), allocatable, intent(out) :: error

    ! local variables
    integer :: i, ierr, my_node_id, num_nodes, k_unit
    !real(kind=dp) :: sum
    logical :: on_root = .false.
    real(kind=dp), allocatable :: kt(:, :), wt(:) !temp for read/dist
    integer :: ik, nk, ir, nkloc, off

    my_node_id = mpirank(comm)
    num_nodes = mpisize(comm)

    if (my_node_id == 0) on_root = .true.

    if (on_root) then
      open (newunit=k_unit, file='kpoint.dat', status='old', form='formatted', err=106)
      read (k_unit, *) kpoint_dist%num_int_kpts
    end if
    call comms_bcast(kpoint_dist%num_int_kpts, 1, error, comm)
    if (allocated(error)) return

    allocate (kpoint_dist%num_int_kpts_on_node(0:num_nodes - 1))
    kpoint_dist%num_int_kpts_on_node(:) = kpoint_dist%num_int_kpts/num_nodes
    kpoint_dist%max_int_kpts_on_node = kpoint_dist%num_int_kpts &
                                       - (num_nodes - 1)*(kpoint_dist%num_int_kpts/num_nodes)
    kpoint_dist%num_int_kpts_on_node(0) = kpoint_dist%max_int_kpts_on_node
!   if(my_node_id < num_int_kpts- num_int_kpts_on_node*num_nodes)  num_int_kpts_on_node= num_int_kpts_on_node+1

    allocate (kpoint_dist%int_kpts(3, kpoint_dist%max_int_kpts_on_node), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating max_int_kpts_on_node in w90_wannier90_readwrite_read_um', comm)
      return
    end if
    kpoint_dist%int_kpts = 0.0_dp
    allocate (kpoint_dist%weight(kpoint_dist%max_int_kpts_on_node), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating weight in w90_wannier90_readwrite_read_um', comm)
      return
    end if
    kpoint_dist%weight = 0.0_dp

    !sum = 0.0_dp

    nk = kpoint_dist%num_int_kpts
    allocate (kt(3, nk))
    allocate (wt(nk))
    if (on_root) then
      do ik = 1, nk
        read (k_unit, *) (kt(i, ik), i=1, 3), wt(ik)
      end do
    end if
    call comms_bcast(kt(1, 1), 3*nk, error, comm)
    if (allocated(error)) return
    call comms_bcast(wt(1), nk, error, comm)
    if (allocated(error)) return

    nkloc = kpoint_dist%num_int_kpts_on_node(my_node_id)
    off = 1
    do ir = 0, my_node_id - 1
      off = off + kpoint_dist%num_int_kpts_on_node(ir)
    end do
    call dcopy(3*nkloc, kt(1, off), 1, kpoint_dist%int_kpts, 1)
    call dcopy(nkloc, wt(off), 1, kpoint_dist%weight, 1)
    deallocate (kt)
    deallocate (wt)

!    if (on_root) then
!      do loop_nodes = 1, num_nodes - 1
!        do loop_kpt = 1, kpoint_dist%num_int_kpts_on_node(loop_nodes)
!          read (k_unit, *) (kpoint_dist%int_kpts(i, loop_kpt), i=1, 3), kpoint_dist%weight(loop_kpt)
!          sum = sum + kpoint_dist%weight(loop_kpt)
!        end do
!
!        call comms_no_sync_send(kpoint_dist%int_kpts(1, 1), 3*kpoint_dist%num_int_kpts_on_node(loop_nodes), loop_nodes, error, comm)
!        if (allocated(error)) return
!        call comms_no_sync_send(kpoint_dist%weight(1), kpoint_dist%num_int_kpts_on_node(loop_nodes), loop_nodes, error, comm)
!        if (allocated(error)) return
!      end do
!      do loop_kpt = 1, kpoint_dist%num_int_kpts_on_node(0)
!        read (k_unit, *) (kpoint_dist%int_kpts(i, loop_kpt), i=1, 3), kpoint_dist%weight(loop_kpt)
!        sum = sum + kpoint_dist%weight(loop_kpt)
!      end do
!!       print*,'rsum',sum
!    end if
!
!    if (.not. on_root) then
!      call comms_no_sync_recv(kpoint_dist%int_kpts(1, 1), 3*kpoint_dist%num_int_kpts_on_node(my_node_id), 0, error, comm)
!      if (allocated(error)) return
!      call comms_no_sync_recv(kpoint_dist%weight(1), kpoint_dist%num_int_kpts_on_node(my_node_id), 0, error, comm)
!      if (allocated(error)) return
!    end if

    return

106 call set_error_file(error, 'Error: Problem opening file kpoint.dat in pw90common_wanint_get_kpoint_file', comm)
    return

  end subroutine pw90common_wanint_get_kpoint_file

  !================================================!
  subroutine pw90common_wanint_w90_wannier90_readwrite_dist(print_output, ws_region, kmesh_info, kpt_latt, num_kpts, &
                                                            dis_manifold, w90_system, fermi_energy_list, num_bands, &
                                                            num_wann, eigval, mp_grid, real_lattice, &
                                                            pw90_calculation, scissors_shift, effective_model, &
                                                            pw90_spin, pw90_band_deriv_degen, pw90_kpath, &
                                                            pw90_kslice, pw90_dos, pw90_berry, pw90_spin_hall, &
                                                            pw90_gyrotropic, pw90_geninterp, pw90_boltzwann, &
                                                            eig_found, error, comm)
    !================================================!
    !
    !! distribute the parameters across processors
    !! NOTE: we only send the ones postw90 uses, not all in w90
    !
    !================================================!

    use w90_constants, only: dp
    use w90_io, only: io_date, io_time
    use w90_comms, only: mpirank, w90_comm_type, comms_bcast
    use w90_types
    use w90_postw90_types, only: pw90_calculation_type, pw90_spin_mod_type, &
                                 pw90_band_deriv_degen_type, pw90_kpath_mod_type, pw90_kslice_mod_type, pw90_dos_mod_type, &
                                 pw90_berry_mod_type, pw90_spin_hall_type, pw90_gyrotropic_type, pw90_geninterp_mod_type, &
                                 pw90_boltzwann_type

    type(print_output_type), intent(inout) :: print_output
    type(ws_region_type), intent(inout) :: ws_region
    type(w90_system_type), intent(inout) :: w90_system
    type(kmesh_info_type), intent(inout) :: kmesh_info
    type(dis_manifold_type), intent(inout) :: dis_manifold
    type(pw90_calculation_type), intent(inout) :: pw90_calculation
    type(pw90_spin_mod_type), intent(inout) :: pw90_spin
    type(pw90_band_deriv_degen_type), intent(inout) :: pw90_band_deriv_degen
    type(pw90_kpath_mod_type), intent(inout) :: pw90_kpath
    type(pw90_kslice_mod_type), intent(inout) :: pw90_kslice
    type(pw90_dos_mod_type), intent(inout) :: pw90_dos
    type(pw90_berry_mod_type), intent(inout) :: pw90_berry
    type(pw90_spin_hall_type), intent(inout) :: pw90_spin_hall
    type(pw90_gyrotropic_type), intent(inout) :: pw90_gyrotropic
    type(pw90_geninterp_mod_type), intent(inout) :: pw90_geninterp
    type(pw90_boltzwann_type), intent(inout) :: pw90_boltzwann
    type(w90_comm_type), intent(in) :: comm
    type(w90_error_type), allocatable, intent(out) :: error

    real(kind=dp), allocatable, intent(inout) :: kpt_latt(:, :)
    real(kind=dp), allocatable, intent(inout) :: fermi_energy_list(:)
    real(kind=dp), pointer, intent(inout) :: eigval(:, :)
    real(kind=dp), intent(inout) :: real_lattice(3, 3)
    real(kind=dp), intent(inout) :: scissors_shift
    integer, intent(inout) :: num_kpts
    integer, intent(inout) :: num_bands
    integer, intent(inout) :: num_wann
    integer, intent(inout) :: mp_grid(3)
    logical, intent(inout) :: eig_found
    logical, intent(inout) :: effective_model

    integer :: ierr
    integer :: iprintroot
    integer :: fermi_n, kdotp_nbands
    logical :: on_root = .false.

    if (mpirank(comm) == 0) on_root = .true.

    call comms_bcast(effective_model, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(eig_found, 1, error, comm)
    if (allocated(error)) return

    if (.not. effective_model) then
      call comms_bcast(mp_grid(1), 3, error, comm)
      if (allocated(error)) return
      call comms_bcast(num_kpts, 1, error, comm)
      if (allocated(error)) return
      call comms_bcast(num_bands, 1, error, comm)
      if (allocated(error)) return
    end if
    call comms_bcast(num_wann, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(print_output%timing_level, 1, error, comm)
    if (allocated(error)) return

    !______________________________________
    ! not so pretty solution to setting iprint to zero on non-root processes
    iprintroot = print_output%iprint
    print_output%iprint = 0
    call comms_bcast(print_output%iprint, 1, error, comm)
    if (allocated(error)) return
    if (on_root) print_output%iprint = iprintroot
    !______________________________________

    call comms_bcast(ws_region%ws_distance_tol, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(ws_region%ws_search_size(1), 3, error, comm)
    if (allocated(error)) return
!    call comms_bcast(num_atoms,1)   ! Ivo: not used in postw90, right?
!    call comms_bcast(num_species,1) ! Ivo: not used in postw90, right?
    call comms_bcast(real_lattice(1, 1), 9, error, comm)
    if (allocated(error)) return
    !call comms_bcast(recip_lattice(1, 1), 9, error, comm)
    !call comms_bcast(real_metric(1, 1), 9)
    !call comms_bcast(recip_metric(1, 1), 9)
    !call comms_bcast(cell_volume, 1, error, comm)
    call comms_bcast(pw90_dos%energy_step, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_dos%smearing%use_adaptive, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_dos%smearing%type_index, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_dos%kmesh%spacing, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_dos%kmesh%mesh(1), 3, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_dos%smearing%adaptive_max_width, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_dos%smearing%fixed_width, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_dos%smearing%adaptive_prefactor, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_dos%num_project, 1, error, comm)
    if (allocated(error)) return

    call comms_bcast(pw90_calculation%berry, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%task, len(pw90_berry%task), error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%kmesh%spacing, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%kmesh%mesh(1), 3, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%curv_adpt_kmesh, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%curv_adpt_kmesh_thresh, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%curv_unit, len(pw90_berry%curv_unit), error, comm)
    if (allocated(error)) return

! Tsirkin
    call comms_bcast(pw90_calculation%gyrotropic, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%task, len(pw90_gyrotropic%task), error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%kmesh%spacing, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%kmesh%mesh(1), 3, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%eigval_max, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%nfreq, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%degen_thresh, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%num_bands, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%box(1, 1), 9, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%box_corner(1), 3, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%smearing%use_adaptive, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%smearing%fixed_width, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%smearing%type_index, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%smearing%max_arg, 1, error, comm)
    if (allocated(error)) return

    call comms_bcast(w90_system%spinors, 1, error, comm)
    if (allocated(error)) return

    call comms_bcast(pw90_spin_hall%freq_scan, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_spin_hall%alpha, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_spin_hall%beta, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_spin_hall%gamma, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_spin_hall%bandshift, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_spin_hall%bandshift_firstband, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_spin_hall%bandshift_energyshift, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_spin_hall%method, len(pw90_spin_hall%method), error, comm)
    if (allocated(error)) return

    call comms_bcast(pw90_berry%kubo_smearing%use_adaptive, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%kubo_smearing%adaptive_prefactor, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%kubo_smearing%adaptive_max_width, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%kubo_smearing%fixed_width, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%kubo_smearing%type_index, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%kubo_eigval_max, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%kubo_nfreq, 1, error, comm)
    if (allocated(error)) return
    fermi_n = 0
    if (on_root) then
      if (allocated(fermi_energy_list)) fermi_n = size(fermi_energy_list)
    end if
    call comms_bcast(fermi_n, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_dos%energy_min, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_dos%energy_max, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_spin%kmesh%spacing, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_spin%kmesh%mesh(1), 3, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%wanint_kpoint_file, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(dis_manifold%win_min, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(dis_manifold%win_max, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%sc_eta, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%sc_w_thr, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%sc_phase_conv, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%sc_use_eta_corr, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%tetrahedron_method, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%tetrahedron_higher_correction, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%tetrahedron_cutoff, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%tetrahedron_avoid_degeneracy, 1, error, comm)
    if (allocated(error)) return
! ----------------------------------------------
!
! New input variables in development
!
    !call comms_bcast(print_output%devel_flag, len(print_output%devel_flag), error, comm)
    call comms_bcast(pw90_calculation%spin_moment, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_spin%axis_polar, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_spin%axis_azimuth, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_calculation%spin_decomp, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_band_deriv_degen%use_degen_pert, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_band_deriv_degen%degen_thr, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(w90_system%num_valence_bands, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_calculation%dos, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_dos%task, len(pw90_dos%task), error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_calculation%kpath, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_kpath%task, len(pw90_kpath%task), error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_kpath%bands_colour, len(pw90_kpath%bands_colour), error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_calculation%kslice, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_kslice%task, len(pw90_kslice%task), error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_kslice%corner(1), 3, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_kslice%b1(1), 3, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_kslice%b2(1), 3, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_kslice%kmesh2d(1), 2, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_kslice%fermi_lines_colour, len(pw90_kslice%fermi_lines_colour), error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%transl_inv, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(w90_system%num_elec_per_state, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(scissors_shift, 1, error, comm)
    if (allocated(error)) return

    ! Do these have to be broadcasted? (Plots done on root node only)
    !
!    call comms_bcast(bands_num_points,1)
!    call comms_bcast(bands_num_spec_points,1)
!    if(allocated(bands_spec_points)) &
!         call comms_bcast(bands_spec_points(1,1),3*bands_num_spec_points)
!    if(allocated(bands_label)) &
!         call comms_bcast(bands_label(:),len(bands_label(1))*bands_num_spec_points)
! ----------------------------------------------
    call comms_bcast(pw90_calculation%geninterp, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_geninterp%alsofirstder, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_geninterp%single_file, 1, error, comm)
    if (allocated(error)) return
    ! BoltzWann variables
    call comms_bcast(pw90_calculation%boltzwann, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%calc_also_dos, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%dir_num_2d, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%dos_energy_step, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%dos_energy_min, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%dos_energy_max, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%dos_smearing%use_adaptive, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%dos_smearing%fixed_width, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%dos_smearing%adaptive_prefactor, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%dos_smearing%adaptive_max_width, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%mu_min, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%mu_max, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%mu_step, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%temp_min, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%temp_max, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%temp_step, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%kmesh%spacing, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%kmesh%mesh(1), 3, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%tdf_energy_step, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%relax_time, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%tdf_smearing%use_adaptive, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%tdf_smearing%fixed_width, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%tdf_smearing%type_index, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%dos_smearing%type_index, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%bandshift, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%bandshift_firstband, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_boltzwann%bandshift_energyshift, 1, error, comm)
    if (allocated(error)) return
    call comms_bcast(ws_region%use_ws_distance, 1, error, comm)
    if (allocated(error)) return

    ! These variables are different from the ones above in that they are
    ! allocatable, and in w90_wannier90_readwrite_read they were allocated on the root node only

    if (.not. on_root) then
      allocate (fermi_energy_list(fermi_n), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error allocating fermi_energy_read in postw90_w90_wannier90_readwrite_dist', comm)
        return
      end if
      allocate (pw90_berry%kubo_freq_list(pw90_berry%kubo_nfreq), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error allocating kubo_freq_list in postw90_w90_wannier90_readwrite_dist', comm)
        return
      end if
      allocate (pw90_gyrotropic%band_list(pw90_gyrotropic%num_bands), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error allocating gyrotropic_band_list in postw90_w90_wannier90_readwrite_dist', comm)
        return
      end if
      allocate (pw90_gyrotropic%freq_list(pw90_gyrotropic%nfreq), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error allocating gyrotropic_freq_list in postw90_w90_wannier90_readwrite_dist', comm)
        return
      end if
      allocate (pw90_dos%project(pw90_dos%num_project), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error allocating dos_project in postw90_w90_wannier90_readwrite_dist', comm)
        return
      end if
      if (.not. effective_model) then
        if (eig_found) then
          allocate (eigval(num_bands, num_kpts), stat=ierr)
          if (ierr /= 0) then
            call set_error_alloc(error, 'Error allocating eigval in postw90_w90_wannier90_readwrite_dist', comm)
            return
          end if
        end if
        allocate (kpt_latt(3, num_kpts), stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error allocating kpt_latt in postw90_w90_wannier90_readwrite_dist', comm)
          return
        end if
      end if
    end if

    kdotp_nbands = 0
    if (on_root) then
      if (allocated(pw90_berry%kdotp_bands)) kdotp_nbands = size(pw90_berry%kdotp_bands)
    end if
    call comms_bcast(kdotp_nbands, 1, error, comm)
    if (kdotp_nbands > 0) then
      if (.not. on_root) then
        allocate (pw90_berry%kdotp_bands(kdotp_nbands), stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error allocating kdotp_bands in postw90_param_dist', comm)
          return
        end if
      end if
      call comms_bcast(pw90_berry%kdotp_bands(1), kdotp_nbands, error, comm)
    end if

    if (fermi_n > 0) then
      call comms_bcast(fermi_energy_list(1), fermi_n, error, comm)
      if (allocated(error)) return
    end if
    call comms_bcast(pw90_gyrotropic%freq_list(1), pw90_gyrotropic%nfreq, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_gyrotropic%band_list(1), pw90_gyrotropic%num_bands, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_berry%kubo_freq_list(1), pw90_berry%kubo_nfreq, error, comm)
    if (allocated(error)) return
    call comms_bcast(pw90_dos%project(1), pw90_dos%num_project, error, comm)
    if (allocated(error)) return
    if (.not. effective_model) then
      if (eig_found) then
        call comms_bcast(eigval(1, 1), num_bands*num_kpts, error, comm)
        if (allocated(error)) return
      end if
      call comms_bcast(kpt_latt(1, 1), 3*num_kpts, error, comm)
      if (allocated(error)) return
    end if

    ! kmesh: only nntot,wb, and bk are needed to evaluate the WF matrix
    ! elements of the position operator in reciprocal space. For the
    ! extra matrix elements entering the orbital magnetization, also
    ! need nnlist. In principle could only broadcast those four variables

    if (.not. effective_model) then

      call comms_bcast(kmesh_info%nnh, 1, error, comm)
      if (allocated(error)) return
      call comms_bcast(kmesh_info%nntot, 1, error, comm)
      if (allocated(error)) return

      if (.not. on_root) then
        allocate (kmesh_info%nnlist(num_kpts, kmesh_info%nntot), stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error in allocating nnlist in pw90common_wanint_w90_wannier90_readwrite_dist', comm)
          return
        end if
        allocate (kmesh_info%neigh(num_kpts, kmesh_info%nntot/2), stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error in allocating neigh in pw90common_wanint_w90_wannier90_readwrite_dist', comm)
          return
        end if
        allocate (kmesh_info%nncell(3, num_kpts, kmesh_info%nntot), stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error in allocating nncell in pw90common_wanint_w90_wannier90_readwrite_dist', comm)
          return
        end if
        allocate (kmesh_info%wb(kmesh_info%nntot), stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error in allocating wb in pw90common_wanint_w90_wannier90_readwrite_dist', comm)
          return
        end if
        allocate (kmesh_info%bka(3, kmesh_info%nntot/2), stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error in allocating bka in pw90common_wanint_w90_wannier90_readwrite_dist', comm)
          return
        end if
        allocate (kmesh_info%bk(3, kmesh_info%nntot, num_kpts), stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error in allocating bk in pw90common_wanint_w90_wannier90_readwrite_dist', comm)
          return
        end if
      end if

      call comms_bcast(kmesh_info%nnlist(1, 1), num_kpts*kmesh_info%nntot, error, comm)
      if (allocated(error)) return
      call comms_bcast(kmesh_info%neigh(1, 1), num_kpts*kmesh_info%nntot/2, error, comm)
      if (allocated(error)) return
      call comms_bcast(kmesh_info%nncell(1, 1, 1), 3*num_kpts*kmesh_info%nntot, error, comm)
      if (allocated(error)) return
      call comms_bcast(kmesh_info%wb(1), kmesh_info%nntot, error, comm)
      if (allocated(error)) return
      call comms_bcast(kmesh_info%bka(1, 1), 3*kmesh_info%nntot/2, error, comm)
      if (allocated(error)) return
      call comms_bcast(kmesh_info%bk(1, 1, 1), 3*kmesh_info%nntot*num_kpts, error, comm)
      if (allocated(error)) return

    end if

  end subroutine pw90common_wanint_w90_wannier90_readwrite_dist

  !================================================!
  subroutine pw90common_wanint_data_dist(num_wann, num_kpts, num_bands, u_matrix_opt, u_matrix, &
                                         dis_manifold, wannier_data, scissors_shift, v_matrix, &
                                         num_valence_bands, have_disentangled, error, comm)
    !================================================!
    !
    !! Distribute the um and chk files
    !
    !================================================!

    use w90_constants, only: dp, cmplx_0
    use w90_io, only: io_date, io_time
    use w90_types, only: dis_manifold_type, wannier_data_type
    use w90_comms, only: w90_comm_type, mpirank, comms_bcast

    implicit none

    type(dis_manifold_type), intent(inout) :: dis_manifold
    type(wannier_data_type), intent(inout) :: wannier_data
    type(w90_comm_type), intent(in) :: comm
    type(w90_error_type), allocatable, intent(out) :: error

    integer, intent(in) :: num_valence_bands
    integer, intent(in) :: num_wann, num_kpts, num_bands
    real(kind=dp), intent(in) :: scissors_shift
    complex(kind=dp), allocatable :: v_matrix(:, :, :)
    complex(kind=dp), allocatable, intent(inout) :: u_matrix_opt(:, :, :), u_matrix(:, :, :)
    logical, intent(inout) :: have_disentangled

    integer :: ierr, loop_kpt, m, i, j
    logical :: on_root = .false.

    if (mpirank(comm) == 0) on_root = .true.

!    if (.not. on_root) then
!      ! wannier_centres is allocated in w90_wannier90_readwrite_read, so only on root node
!      ! It is then read in w90_wannier90_readwrite_read_chpkt
!      ! Therefore, now we need to allocate it on all nodes, and then broadcast it
!      allocate (wannier_data%centres(3, num_wann), stat=ierr)
!      if (ierr /= 0) then
!        call set_error_alloc(error, 'Error allocating wannier_centres in pw90common_wanint_data_dist', comm)
!        return
!      endif
!    end if
!    call comms_bcast(wannier_data%centres(1, 1), 3*num_wann, error, comm)
!    if (allocated(error)) return

    ! -------------------
    ! Ivo: added 8april11
    ! -------------------
    !
    ! Calculate the matrix that describes the combined effect of
    ! disentanglement and maximal localization. This is the combination
    ! that is most often needed for interpolation purposes
    !
    ! Allocate on all nodes
    allocate (v_matrix(num_bands, num_wann, num_kpts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating v_matrix in pw90common_wanint_data_dist', comm)
      return
    end if
    ! u_matrix and u_matrix_opt are stored on root only
    if (on_root) then
      if (.not. have_disentangled) then
        v_matrix(1:num_wann, :, :) = u_matrix(1:num_wann, :, :)
      else
        v_matrix = cmplx_0
        do loop_kpt = 1, num_kpts
          do j = 1, num_wann
            do m = 1, dis_manifold%ndimwin(loop_kpt)
              do i = 1, num_wann
                v_matrix(m, j, loop_kpt) = v_matrix(m, j, loop_kpt) &
                                           + u_matrix_opt(m, i, loop_kpt)*u_matrix(i, j, loop_kpt)
              end do
            end do
          end do
        end do
      end if
      if (allocated(u_matrix_opt)) deallocate (u_matrix_opt)
      if (.not. (num_valence_bands > 0 .and. abs(scissors_shift) > 1.0e-7_dp)) then
        if (allocated(u_matrix)) deallocate (u_matrix)
      end if
    end if
    call comms_bcast(v_matrix(1, 1, 1), num_bands*num_wann*num_kpts, error, comm)
    if (allocated(error)) return

    if (num_valence_bands > 0 .and. abs(scissors_shift) > 1.0e-7_dp) then
    if (.not. on_root .and. .not. allocated(u_matrix)) then
      allocate (u_matrix(num_wann, num_wann, num_kpts), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error allocating u_matrix in pw90common_wanint_data_dist', comm)
        return
      end if
    end if
    call comms_bcast(u_matrix(1, 1, 1), num_wann*num_wann*num_kpts, error, comm)
    end if

    call comms_bcast(have_disentangled, 1, error, comm)
    if (allocated(error)) return

    if (have_disentangled) then
      if (.not. on_root) then

        if (.not. allocated(dis_manifold%lwindow)) then
          allocate (dis_manifold%lwindow(num_bands, num_kpts), stat=ierr)
          if (ierr /= 0) then
            call set_error_alloc(error, 'Error allocating lwindow in pw90common_wanint_data_dist', comm)
            return
          end if
        end if

        if (.not. allocated(dis_manifold%ndimwin)) then
          allocate (dis_manifold%ndimwin(num_kpts), stat=ierr)
          if (ierr /= 0) then
            call set_error_alloc(error, 'Error allocating ndimwin in pw90common_wanint_data_dist', comm)
            return
          end if
        end if

      end if

!       call comms_bcast(u_matrix_opt(1,1,1),num_bands*num_wann*num_kpts)
      call comms_bcast(dis_manifold%lwindow(1, 1), num_bands*num_kpts, error, comm)
      if (allocated(error)) return
      call comms_bcast(dis_manifold%ndimwin(1), num_kpts, error, comm)
      if (allocated(error)) return
    end if

  end subroutine pw90common_wanint_data_dist

!================================================

  subroutine pw90common_get_occ(ef, eig, occ, num_wann)
    !================================================!
    !
    !! Compute the electronic occupancy
    !
    !================================================!

    use w90_constants, only: dp

    ! arguments
    integer, intent(in) :: num_wann

    real(kind=dp), intent(in)  :: eig(num_wann)
    !! Eigenvalues
    real(kind=dp), intent(in)  :: ef
    !! Fermi level
    real(kind=dp), intent(out) :: occ(num_wann)
    !! Occupancy of states

    ! local variables
    integer :: i

    ! State occupancies
!    if(smear_temp < eps7) then
    !
    ! Use a step function occupancy (T=0)
    !
    occ(:) = 0.0_dp
    do i = 1, num_wann
      if (eig(i) < ef) occ(i) = 1.0_dp
    end do
!    else
    !
    ! Use a Fermi-Dirac occupancy (T=smear_temp, in Kelvin)
    !
    ! k_B.T in electron-volts
    !
!       kt=k_B_SI*smear_temp/elem_charge_SI
!       do i=1,num_wann
!          occ(i)=1.0_dp/(exp((eig(i)-ef)/kt)+1.0_dp)
!       end do
!    end if

  end subroutine pw90common_get_occ

  !================================================
  function kmesh_spacing_singleinteger(num_points, recip_lattice)
    !================================================
    !! Set up the value of the interpolation mesh spacing, needed for
    !! adaptive smearing [see Eqs. (34-35) YWVS07]. Choose it as the largest of
    !! the three Delta_k's for each of the primitive translations b1, b2, and b3
    !================================================

    integer, intent(in) :: num_points
    real(kind=dp), intent(in) :: recip_lattice(3, 3)
    real(kind=dp)       :: kmesh_spacing_singleinteger

    integer        :: i
    real(kind=dp) :: Delta_k_i(3)

    ! NOTE: The vectors b_i are stored as *rows* in recip_lattice (argh!).
    ! Hence I believe Jonathan's original code confused rows with columns
    ! when computing Delta_k, which he called 'rspace'
    ! (See my e-mail of 20Sept07)
    !
    do i = 1, 3
      Delta_k_i(i) = sqrt(dot_product(recip_lattice(i, :), recip_lattice(i, :)))/num_points
    end do
    kmesh_spacing_singleinteger = maxval(Delta_k_i)

  end function kmesh_spacing_singleinteger
  function kmesh_spacing_mesh(mesh, recip_lattice)
    !! Same as kmesh_spacing_singleinteger, but for a kmesh with three
    !! different mesh samplings along the three directions

    integer, dimension(3), intent(in) :: mesh
    real(kind=dp), intent(in) :: recip_lattice(3, 3)
    real(kind=dp) :: kmesh_spacing_mesh

    integer        :: i
    real(kind=dp) :: Delta_k_i(3)

    do i = 1, 3
      Delta_k_i(i) = sqrt(dot_product(recip_lattice(i, :), recip_lattice(i, :)))/mesh(i)
    end do
    kmesh_spacing_mesh = maxval(Delta_k_i)

  end function kmesh_spacing_mesh

  !================================================!
  subroutine pw90common_fourier_R_to_k(ws_region, wannier_data, ws_distance, wigner_seitz, OO, &
                                       OO_R, kpt, real_lattice, mp_grid, alpha, num_wann, &
                                       error, comm)
    !================================================!
    !
    !! For alpha=0:
    !! O_ij(R) --> O_ij(k) = sum_R e^{+ik.R}*O_ij(R)
    !!
    !! For alpha=1,2,3:
    !! sum_R [cmplx_i*R_alpha*e^{+ik.R}*O_ij(R)]
    !! where R_alpha is a Cartesian component of R
    !! ***REMOVE EVENTUALLY*** (replace with pw90common_fourier_R_to_k_new)
    !
    !================================================!

    use w90_constants, only: dp, cmplx_0, cmplx_i, twopi
    use w90_types, only: wannier_data_type, ws_region_type, ws_distance_type
    use w90_ws_distance, only: ws_translate_dist
    use w90_postw90_types, only: wigner_seitz_type
    use w90_comms, only: w90_comm_type

    implicit none

    ! arguments
    type(ws_region_type), intent(in) :: ws_region
    type(wannier_data_type), intent(in) :: wannier_data
    type(wigner_seitz_type), intent(in) :: wigner_seitz
    type(ws_distance_type), intent(inout) :: ws_distance
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: num_wann
    integer, intent(in) :: mp_grid(3)
    integer, intent(in) :: alpha

    real(kind=dp), intent(in) :: kpt(3), real_lattice(3, 3)

    complex(kind=dp), intent(in) :: OO_R(:, :, :)
    complex(kind=dp), intent(out) :: OO(:, :)

    ! local variables
    integer          :: ir, i, j, ideg
    real(kind=dp)    :: rdotk
    complex(kind=dp) :: phase_fac

    OO(:, :) = cmplx_0
    do ir = 1, wigner_seitz%nrpts_pw90
      ! [lp] Original code, without IJ-dependent shift:
      rdotk = twopi*dot_product(kpt(:), wigner_seitz%irvec_pw90(:, ir))
      phase_fac = cmplx(cos(rdotk), sin(rdotk), dp)
      if (alpha == 0) then
        OO(:, :) = OO(:, :) + phase_fac*OO_R(:, :, ir)
      elseif (alpha == 1 .or. alpha == 2 .or. alpha == 3) then
        OO(:, :) = OO(:, :) + &
                   cmplx_i*wigner_seitz%crvec_pw90(alpha, ir)*phase_fac*OO_R(:, :, ir)
      else
        stop 'wrong value of alpha in pw90common_fourier_R_to_k'
      end if

    end do

  end subroutine pw90common_fourier_R_to_k

  !================================================!
  subroutine pw90common_fourier_R_to_k_new(ws_region, wannier_data, ws_distance, wigner_seitz, &
                                           OO_R, kpt, real_lattice, mp_grid, num_wann, error, &
                                           comm, OO, OO_dx, OO_dy, OO_dz)
    !================================================!
    !
    !! For OO:
    !! $$O_{ij}(k) = \sum_R e^{+ik.R}.O_{ij}(R)$$
    !! For $$OO_{dx,dy,dz}$$:
    !! $$\sum_R [i.R_{dx,dy,dz}.e^{+ik.R}.O_{ij}(R)]$$
    !! where R_{x,y,z} are the Cartesian components of R
    !
    !================================================!

    use w90_constants, only: dp, cmplx_0, cmplx_i, twopi
    use w90_types, only: ws_region_type, wannier_data_type, ws_distance_type
    use w90_ws_distance, only: ws_translate_dist
    use w90_postw90_types, only: wigner_seitz_type
    use w90_comms, only: w90_comm_type

    implicit none

    ! arguments
    type(ws_region_type), intent(in) :: ws_region
    type(wannier_data_type), intent(in) :: wannier_data
    type(wigner_seitz_type), intent(in) :: wigner_seitz
    type(ws_distance_type), intent(inout) :: ws_distance
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: num_wann
    integer, intent(in) :: mp_grid(3)

    real(kind=dp), intent(in) :: kpt(3), real_lattice(3, 3)

    complex(kind=dp), intent(in) :: OO_R(:, :, :)
    complex(kind=dp), optional, intent(out) :: OO(:, :)
    complex(kind=dp), optional, intent(out) :: OO_dx(:, :)
    complex(kind=dp), optional, intent(out) :: OO_dy(:, :)
    complex(kind=dp), optional, intent(out) :: OO_dz(:, :)

    ! local variables
    integer          :: ir, i, j, ideg
    real(kind=dp)    :: rdotk
    complex(kind=dp) :: phase_fac

    if (present(OO)) OO = cmplx_0
    if (present(OO_dx)) OO_dx = cmplx_0
    if (present(OO_dy)) OO_dy = cmplx_0
    if (present(OO_dz)) OO_dz = cmplx_0
    do ir = 1, wigner_seitz%nrpts_pw90
! [lp] Original code, without IJ-dependent shift:
      rdotk = twopi*dot_product(kpt(:), wigner_seitz%irvec_pw90(:, ir))
      phase_fac = cmplx(cos(rdotk), sin(rdotk), dp)
      if (present(OO)) OO(:, :) = OO(:, :) + phase_fac*OO_R(:, :, ir)
      if (present(OO_dx)) OO_dx(:, :) = OO_dx(:, :) + &
                                        cmplx_i*wigner_seitz%crvec_pw90(1, ir)*phase_fac*OO_R(:, :, ir)
      if (present(OO_dy)) OO_dy(:, :) = OO_dy(:, :) + &
                                        cmplx_i*wigner_seitz%crvec_pw90(2, ir)*phase_fac*OO_R(:, :, ir)
      if (present(OO_dz)) OO_dz(:, :) = OO_dz(:, :) + &
                                        cmplx_i*wigner_seitz%crvec_pw90(3, ir)*phase_fac*OO_R(:, :, ir)
    end do

  end subroutine pw90common_fourier_R_to_k_new

  !================================================!
  subroutine pw90common_fourier_R_to_k_new_second_d(kpt, OO_R, num_wann, ws_region, wannier_data, &
                                                    real_lattice, mp_grid, ws_distance, &
                                                    wigner_seitz, error, comm, OO, OO_da, OO_dadb)
    !================================================!
    !
    !! For OO:
    !! $$O_{ij}(k) = \sum_R e^{+ik.R}.O_{ij}(R)$$
    !! For $$OO_{dx,dy,dz}$$:
    !! $$\sum_R [i.R_{dx,dy,dz}.e^{+ik.R}.O_{ij}(R)]$$
    !! where R_{x,y,z} are the Cartesian components of R
    !! For $$OO_{dx1,dy1,dz1;dx2,dy2,dz2}$$:
    !! $$-\sum_R [R_{dx1,dy1,dz1}.R_{dx2,dy2,dz2}.e^{+ik.R}.O_{ij}(R)]$$
    !! where R_{xi,yi,zi} are the Cartesian components of R
    !
    !================================================!

    use w90_constants, only: dp, cmplx_0, cmplx_i, twopi
    use w90_types, only: ws_region_type, wannier_data_type, ws_distance_type
    use w90_ws_distance, only: ws_translate_dist
    use w90_postw90_types, only: wigner_seitz_type
    use w90_comms, only: w90_comm_type

    implicit none

    ! arguments
    type(ws_region_type), intent(in) :: ws_region
    type(wannier_data_type), intent(in) :: wannier_data
    type(wigner_seitz_type), intent(in) :: wigner_seitz
    type(ws_distance_type), intent(inout) :: ws_distance
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: mp_grid(3)
    integer, intent(in) :: num_wann

    real(kind=dp), intent(in) :: kpt(3), real_lattice(3, 3)

    complex(kind=dp), intent(in) :: OO_R(:, :, :)
    complex(kind=dp), optional, intent(out) :: OO(:, :)
    complex(kind=dp), optional, intent(out) :: OO_da(:, :, :)
    complex(kind=dp), optional, intent(out) :: OO_dadb(:, :, :, :)

    ! local variables
    integer          :: ir, i, j, ideg, a, b
    real(kind=dp)    :: rdotk
    complex(kind=dp) :: phase_fac
    if (present(OO)) OO = cmplx_0
    if (present(OO_da)) OO_da = cmplx_0
    if (present(OO_dadb)) OO_dadb = cmplx_0
    do ir = 1, wigner_seitz%nrpts_pw90
! [lp] Original code, without IJ-dependent shift:
      rdotk = twopi*dot_product(kpt(:), wigner_seitz%irvec_pw90(:, ir))
      phase_fac = cmplx(cos(rdotk), sin(rdotk), dp)
      if (present(OO)) OO(:, :) = OO(:, :) + phase_fac*OO_R(:, :, ir)
      if (present(OO_da)) then
        do a = 1, 3
          OO_da(:, :, a) = OO_da(:, :, a) + cmplx_i*wigner_seitz%crvec_pw90(a, ir)*phase_fac &
                           *OO_R(:, :, ir)
        end do
      end if
      if (present(OO_dadb)) then
        do a = 1, 3
          do b = 1, 3
            OO_dadb(:, :, a, b) = OO_dadb(:, :, a, b) - &
                                  wigner_seitz%crvec_pw90(a, ir)*wigner_seitz%crvec_pw90(b, ir)*phase_fac &
                                  *OO_R(:, :, ir)
          end do
        end do
      end if
    end do

  end subroutine pw90common_fourier_R_to_k_new_second_d

  !================================================!
  subroutine pw90common_fourier_R_to_k_new_second_d_TB_conv(kpt, OO_R, oo_a_R, num_wann, &
                                                            ws_region, wannier_data, real_lattice, &
                                                            mp_grid, ws_distance, wigner_seitz, &
                                                            error, comm, OO, OO_da, OO_dadb)
    !================================================!
    ! modified version of pw90common_fourier_R_to_k_new_second_d, includes wannier centres in
    ! the exponential inside the sum (so called TB convention)
    !
    !! For OO:
    !! $$O_{ij}(k) = \sum_R e^{+ik.(R+tau_ij)}.O_{ij}(R)$$
    !! For $$OO_{dx,dy,dz}$$:
    !! $$\sum_R [i.(R+tau_ij)_{dx,dy,dz}.e^{+ik.(R+tau_ij)}.O_{ij}(R)]$$
    !! where R_{x,y,z} are the Cartesian components of R
    !! For $$OO_{dx1,dy1,dz1;dx2,dy2,dz2}$$:
    !! $$-\sum_R [(R+tau_ij)_{dx1,dy1,dz1}.(R+tau_ij)_{dx2,dy2,dz2}.e^{+ik.(R+tau_ij)}.O_{ij}(R)]$$
    !! where {xi,yi,zi} denote the Cartesian components and
    !  tau_ij = tau_j - tau_i, being tau_i=<0i|r|0i> the individual wannier centres
    !================================================!

    use w90_constants, only: dp, cmplx_0, cmplx_i, twopi
    use w90_types, only: ws_region_type, wannier_data_type, ws_distance_type
    use w90_ws_distance, only: ws_translate_dist
    use w90_utility, only: utility_cart_to_frac, utility_inverse_mat
    use w90_postw90_types, only: wigner_seitz_type
    use w90_comms, only: w90_comm_type

    implicit none

    ! arguments
    type(ws_region_type), intent(in) :: ws_region
    type(wannier_data_type), intent(in) :: wannier_data
    type(wigner_seitz_type), intent(in) :: wigner_seitz
    type(ws_distance_type), intent(inout) :: ws_distance
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: mp_grid(3)
    integer, intent(in) :: num_wann

    real(kind=dp), intent(in) :: kpt(3), real_lattice(3, 3)

    complex(kind=dp), intent(in) :: oo_a_R(:, :, :, :)
    complex(kind=dp), intent(in) :: OO_R(:, :, :)
    complex(kind=dp), optional, intent(out) :: OO(:, :)
    complex(kind=dp), optional, intent(out) :: OO_da(:, :, :)
    complex(kind=dp), optional, intent(out) :: OO_dadb(:, :, :, :)

    ! local variables
    real(kind=dp) :: inv_lattice(3, 3)
    integer :: ir, i, j, ideg, a, b
    real(kind=dp) :: rdotk
    real(kind=dp) :: wannier_centres_frac(3, num_wann)
    real(kind=dp) :: r_sum(3)
    complex(kind=dp) :: phase_fac

    r_sum = 0.d0

    ! rotate wannier centres from cartesian to fractional coordinates
    wannier_centres_frac(:, :) = 0.d0
    call utility_inverse_mat(real_lattice, inv_lattice)
    do ir = 1, num_wann
      call utility_cart_to_frac(wigner_seitz%wannier_centres_from_AA_R(:, ir), &
                                wannier_centres_frac(:, ir), inv_lattice)
    end do

    if (present(OO)) OO = cmplx_0
    if (present(OO_da)) OO_da = cmplx_0
    if (present(OO_dadb)) OO_dadb = cmplx_0
    do ir = 1, wigner_seitz%nrpts_pw90
! [lp] Original code, without IJ-dependent shift:
      do j = 1, num_wann
        do i = 1, num_wann
          r_sum(:) = real(wigner_seitz%irvec_pw90(:, ir)) &
                     + wannier_centres_frac(:, j) - wannier_centres_frac(:, i)
          rdotk = twopi*dot_product(kpt(:), r_sum(:))
          phase_fac = cmplx(cos(rdotk), sin(rdotk), dp)
          if (present(OO)) OO(i, j) = OO(i, j) + phase_fac*OO_R(i, j, ir)
          if (present(OO_da)) then
            do a = 1, 3
              OO_da(i, j, a) = OO_da(i, j, a) + cmplx_i* &
                               (wigner_seitz%crvec_pw90(a, ir) + wigner_seitz%wannier_centres_from_AA_R(a, j) - &
                                wigner_seitz%wannier_centres_from_AA_R(a, i))*phase_fac*OO_R(i, j, ir)
            end do
          end if
          if (present(OO_dadb)) then
            do a = 1, 3
              do b = 1, 3
                OO_dadb(i, j, a, b) = &
                  OO_dadb(i, j, a, b) - &
                  (wigner_seitz%crvec_pw90(a, ir) + wigner_seitz%wannier_centres_from_AA_R(a, j) - &
                   wigner_seitz%wannier_centres_from_AA_R(a, i))*(wigner_seitz%crvec_pw90(b, ir) + &
                                                                  wigner_seitz%wannier_centres_from_AA_R(b, j) - &
                                                                  wigner_seitz%wannier_centres_from_AA_R(b, i))* &
                  phase_fac*OO_R(i, j, ir)
              end do
            end do
          end if
        end do
      end do
    end do

  end subroutine pw90common_fourier_R_to_k_new_second_d_TB_conv

  !================================================!
  subroutine pw90common_fourier_R_to_k_vec(ws_region, wannier_data, ws_distance, wigner_seitz, &
                                           OO_R, kpt, real_lattice, mp_grid, num_wann, error, &
                                           comm, OO_true, OO_pseudo)
    !================================================!
    !
    !! For OO_true (true vector):
    !! $${\vec O}_{ij}(k) = \sum_R e^{+ik.R} {\vec O}_{ij}(R)$$
    !
    !================================================!

    use w90_constants, only: dp, cmplx_0, cmplx_i, twopi
    use w90_types, only: ws_region_type, wannier_data_type, ws_distance_type
    use w90_ws_distance, only: ws_translate_dist
    use w90_postw90_types, only: wigner_seitz_type
    use w90_comms, only: w90_comm_type

    implicit none

    ! arguments
    type(ws_region_type), intent(in) :: ws_region
    type(wannier_data_type), intent(in) :: wannier_data
    type(ws_distance_type), intent(inout) :: ws_distance
    type(wigner_seitz_type), intent(in) :: wigner_seitz
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: num_wann
    integer, intent(in) :: mp_grid(3)

    real(kind=dp), intent(in) :: kpt(3), real_lattice(3, 3)

    complex(kind=dp), intent(in)  :: OO_R(:, :, :, :)
    complex(kind=dp), optional, intent(out) :: OO_true(:, :, :)
    complex(kind=dp), optional, intent(out) :: OO_pseudo(:, :, :)

    ! local variables
    integer          :: ir, i, j, ideg
    real(kind=dp)    :: rdotk
    complex(kind=dp) :: phase_fac

    if (present(OO_true)) OO_true = cmplx_0
    if (present(OO_pseudo)) OO_pseudo = cmplx_0
    do ir = 1, wigner_seitz%nrpts_pw90
! [lp] Original code, without IJ-dependent shift:
      rdotk = twopi*dot_product(kpt(:), wigner_seitz%irvec_pw90(:, ir))
      phase_fac = cmplx(cos(rdotk), sin(rdotk), dp)
      if (present(OO_true)) then
        OO_true(:, :, 1) = OO_true(:, :, 1) + phase_fac*OO_R(:, :, ir, 1)
        OO_true(:, :, 2) = OO_true(:, :, 2) + phase_fac*OO_R(:, :, ir, 2)
        OO_true(:, :, 3) = OO_true(:, :, 3) + phase_fac*OO_R(:, :, ir, 3)
      end if
      if (present(OO_pseudo)) then
        OO_pseudo(:, :, 1) = OO_pseudo(:, :, 1) &
                             + cmplx_i*wigner_seitz%crvec_pw90(2, ir)*phase_fac*OO_R(:, :, ir, 3) &
                             - cmplx_i*wigner_seitz%crvec_pw90(3, ir)*phase_fac*OO_R(:, :, ir, 2)
        OO_pseudo(:, :, 2) = OO_pseudo(:, :, 2) &
                             + cmplx_i*wigner_seitz%crvec_pw90(3, ir)*phase_fac*OO_R(:, :, ir, 1) &
                             - cmplx_i*wigner_seitz%crvec_pw90(1, ir)*phase_fac*OO_R(:, :, ir, 3)
        OO_pseudo(:, :, 3) = OO_pseudo(:, :, 3) &
                             + cmplx_i*wigner_seitz%crvec_pw90(1, ir)*phase_fac*OO_R(:, :, ir, 2) &
                             - cmplx_i*wigner_seitz%crvec_pw90(2, ir)*phase_fac*OO_R(:, :, ir, 1)
      end if
    end do

  end subroutine pw90common_fourier_R_to_k_vec

  !================================================!
  subroutine pw90common_fourier_R_to_k_vec_dadb(ws_region, wannier_data, ws_distance, &
                                                wigner_seitz, OO_R, kpt, real_lattice, mp_grid, &
                                                num_wann, error, comm, OO_da, OO_dadb)
    !================================================!
    !
    !! For $$OO_{ij;dx,dy,dz}$$:
    !! $$O_{ij;dx,dy,dz}(k) = \sum_R e^{+ik.R} O_{ij;dx,dy,dz}(R)$$
    !! For $$OO_{ij;dx1,dy1,dz1;dx2,dy2,dz2}$$:
    !! $$O_{ij;dx1,dy1,dz1;dx2,dy2,dz2}(k) = \sum_R e^{+ik.R} i.R_{dx2,dy2,dz2}
    !!                                       .O_{ij;dx1,dy1,dz1}(R)$$
    !
    !================================================!

    use w90_constants, only: dp, cmplx_0, cmplx_i, twopi
    use w90_types, only: ws_region_type, wannier_data_type, ws_distance_type
    use w90_ws_distance, only: ws_translate_dist
    use w90_postw90_types, only: wigner_seitz_type
    use w90_comms, only: w90_comm_type

    implicit none

    ! arguments
    type(ws_region_type), intent(in) :: ws_region
    type(wannier_data_type), intent(in) :: wannier_data
    type(ws_distance_type), intent(inout) :: ws_distance
    type(wigner_seitz_type), intent(in) :: wigner_seitz
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: num_wann
    integer, intent(in) :: mp_grid(3)

    real(kind=dp), intent(in) :: kpt(3), real_lattice(3, 3)

    complex(kind=dp), intent(in)  :: OO_R(:, :, :, :)
    complex(kind=dp), optional, intent(out) :: OO_da(:, :, :)
    complex(kind=dp), optional, intent(out) :: OO_dadb(:, :, :, :)

    ! local variables
    integer          :: ir, i, j, ideg, a, b
    real(kind=dp)    :: rdotk
    complex(kind=dp) :: phase_fac

    if (present(OO_da)) OO_da = cmplx_0
    if (present(OO_dadb)) OO_dadb = cmplx_0
    do ir = 1, wigner_seitz%nrpts_pw90
! [lp] Original code, without IJ-dependent shift:
      rdotk = twopi*dot_product(kpt(:), wigner_seitz%irvec_pw90(:, ir))
      phase_fac = cmplx(cos(rdotk), sin(rdotk), dp)
      if (present(OO_da)) then
        OO_da(:, :, 1) = OO_da(:, :, 1) + phase_fac*OO_R(:, :, ir, 1)
        OO_da(:, :, 2) = OO_da(:, :, 2) + phase_fac*OO_R(:, :, ir, 2)
        OO_da(:, :, 3) = OO_da(:, :, 3) + phase_fac*OO_R(:, :, ir, 3)
      end if
      if (present(OO_dadb)) then
        do a = 1, 3
          do b = 1, 3
            OO_dadb(:, :, a, b) = OO_dadb(:, :, a, b) &
                                  + cmplx_i*wigner_seitz%crvec_pw90(b, ir)*phase_fac*OO_R(:, :, ir, a)
          end do
        end do
      end if
    end do

  end subroutine pw90common_fourier_R_to_k_vec_dadb

  !================================================!
  subroutine pw90common_fourier_R_to_k_vec_dadb_TB_conv(ws_region, wannier_data, ws_distance, &
                                                        wigner_seitz, OO_R, kpt, real_lattice, &
                                                        mp_grid, num_wann, error, comm, OO_da, &
                                                        OO_dadb)
    !================================================!
    !
    ! modified version of pw90common_fourier_R_to_k_vec_dadb, includes wannier centres in
    ! the exponential inside the sum (so called TB convention)
    !
    !! For $$OO_{ij;dx,dy,dz}$$:
    !! $$O_{ij;dx,dy,dz}(k) = \sum_R e^{+ik.(R+tau_ij)} O_{ij;dx,dy,dz}(R)$$
    !! For $$OO_{ij;dx1,dy1,dz1;dx2,dy2,dz2}$$:
    !! $$O_{ij;dx1,dy1,dz1;dx2,dy2,dz2}(k) = \sum_R e^{+ik.(R+tau_ij)} i.(R+tau_ij)_{dx2,dy2,dz2}
    !!                                       .O_{ij;dx1,dy1,dz1}(R)$$
    ! with tau_ij = tau_j - tau_i, being tau_i=<0i|r|0i> the individual wannier centres
    !
    !================================================!

    use w90_constants, only: dp, cmplx_0, cmplx_i, twopi
    use w90_types, only: ws_region_type, wannier_data_type, ws_distance_type
    use w90_ws_distance, only: ws_translate_dist
    use w90_utility, only: utility_cart_to_frac, utility_inverse_mat
    use w90_postw90_types, only: wigner_seitz_type
    use w90_comms, only: w90_comm_type

    implicit none

    ! arguments
    type(ws_region_type), intent(in) :: ws_region
    type(wannier_data_type), intent(in) :: wannier_data
    type(ws_distance_type), intent(inout) :: ws_distance
    type(wigner_seitz_type), intent(in) :: wigner_seitz
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: num_wann
    integer, intent(in) :: mp_grid(3)

    real(kind=dp), intent(in) :: kpt(3), real_lattice(3, 3)

    complex(kind=dp), intent(in) :: OO_R(:, :, :, :)
    complex(kind=dp), optional, intent(out) :: OO_da(:, :, :)
    complex(kind=dp), optional, intent(out)   :: OO_dadb(:, :, :, :)

    ! local variables
    real(kind=dp)    :: inv_lattice(3, 3)
    integer          :: ir, i, j, ideg, a, b
    real(kind=dp)    :: rdotk
    complex(kind=dp) :: phase_fac
    real(kind=dp)    :: wannier_centres_frac(3, num_wann)
    real(kind=dp)    :: r_sum(3)

    r_sum = 0.d0

    if (present(OO_da)) OO_da = cmplx_0
    if (present(OO_dadb)) OO_dadb = cmplx_0

    ! rotate wannier centres from cartesian to fractional coordinates
    wannier_centres_frac(:, :) = 0.d0
    call utility_inverse_mat(real_lattice, inv_lattice)
    do ir = 1, num_wann
      call utility_cart_to_frac(wigner_seitz%wannier_centres_from_AA_R(:, ir), &
                                wannier_centres_frac(:, ir), inv_lattice)
    end do

!    print *, 'wannier_centres_frac'
!    do ir = 1,num_wann
!     print *, wannier_centres_frac(:,ir)
!    enddo
!    stop
!
!    print *, 'crvec'
!    do ir = 1,nrpts
!     print *, crvec(:,ir)
!    enddo
!    stop
!    print *, 'wannier_centres'
!    do ir = 1,num_wann
!     print *, wannier_centres(:,ir)
!    enddo
!    stop

    do ir = 1, wigner_seitz%nrpts_pw90
! [lp] Original code, without IJ-dependent shift:
      do j = 1, num_wann
      do i = 1, num_wann
        r_sum(:) = real(wigner_seitz%irvec_pw90(:, ir)) &
                   + wannier_centres_frac(:, j) - wannier_centres_frac(:, i)
        rdotk = twopi*dot_product(kpt(:), r_sum(:))
        phase_fac = cmplx(cos(rdotk), sin(rdotk), dp)
        if (present(OO_da)) then
          ! if we are at the origin and at the same band, then the
          ! matrix element is zero in this convention
          if ((wigner_seitz%irvec_pw90(1, ir) .eq. 0) .and. (wigner_seitz%irvec_pw90(2, ir) .eq. 0) .and. &
              (wigner_seitz%irvec_pw90(3, ir) .eq. 0) .and. (i .eq. j)) then
            OO_da(i, j, 1) = OO_da(i, j, 1) + phase_fac*(OO_R(i, j, ir, 1) &
                                                         - wigner_seitz%wannier_centres_from_AA_R(1, j))
            OO_da(i, j, 2) = OO_da(i, j, 2) + phase_fac*(OO_R(i, j, ir, 2) &
                                                         - wigner_seitz%wannier_centres_from_AA_R(2, j))
            OO_da(i, j, 3) = OO_da(i, j, 3) + phase_fac*(OO_R(i, j, ir, 3) &
                                                         - wigner_seitz%wannier_centres_from_AA_R(3, j))
!            print *, 'OO_R(i,j,ir,1)', OO_R(i,j,ir,1)
!            print *, 'local_wannier_centres(1,j)', local_wannier_centres(1,j)
!            print *, 'OO_R(i,j,ir,2)', OO_R(i,j,ir,2)
!            print *, 'local_wannier_centres(2,j)', local_wannier_centres(2,j)
!            cycle
          else
            OO_da(i, j, 1) = OO_da(i, j, 1) + phase_fac*OO_R(i, j, ir, 1)
            OO_da(i, j, 2) = OO_da(i, j, 2) + phase_fac*OO_R(i, j, ir, 2)
            OO_da(i, j, 3) = OO_da(i, j, 3) + phase_fac*OO_R(i, j, ir, 3)
          end if
        end if
        if (present(OO_dadb)) then
          ! same skip as before
          if ((wigner_seitz%irvec_pw90(1, ir) .eq. 0) .and. (wigner_seitz%irvec_pw90(2, ir) .eq. 0) .and. &
              (wigner_seitz%irvec_pw90(3, ir) .eq. 0) .and. (i .eq. j)) then
            do a = 1, 3
              do b = 1, 3
                OO_dadb(i, j, a, b) = OO_dadb(i, j, a, b) + &
                                      cmplx_i*(wigner_seitz%crvec_pw90(b, ir) + &
                                               wigner_seitz%wannier_centres_from_AA_R(b, j) &
                                               - wigner_seitz%wannier_centres_from_AA_R(b, i))*phase_fac* &
                                      (OO_R(i, j, ir, a) - wigner_seitz%wannier_centres_from_AA_R(a, j))
              end do
            end do
!           cycle
          else
            do a = 1, 3
              do b = 1, 3
                OO_dadb(i, j, a, b) = OO_dadb(i, j, a, b) + &
                                      cmplx_i*(wigner_seitz%crvec_pw90(b, ir) + &
                                               wigner_seitz%wannier_centres_from_AA_R(b, j) &
                                               - wigner_seitz%wannier_centres_from_AA_R(b, i))* &
                                      phase_fac*OO_R(i, j, ir, a)
              end do
            end do
          end if
        end if
      end do
      end do
    end do

  end subroutine pw90common_fourier_R_to_k_vec_dadb_TB_conv

  !================================================!
  !                   PRIVATE PROCEDURES
  !================================================!

  !================================================!
  subroutine wignerseitz(print_output, real_lattice, mp_grid, ws_region, wigner_seitz, stdout, &
                         count_pts, timer, error, comm)
    !================================================!
    !! Calculates a grid of lattice vectors r that fall inside (and eventually
    !! on the surface of) the Wigner-Seitz supercell centered on the
    !! origin of the Bravais superlattice with primitive translations
    !! mp_grid(1)*a_1, mp_grid(2)*a_2, and mp_grid(3)*a_3
    !================================================!

    use w90_constants, only: eps8, dp
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_types, only: print_output_type, timer_list_type, ws_region_type
    use w90_utility, only: utility_metric
    use w90_comms, only: w90_comm_type, mpirank
    use w90_postw90_types, only: wigner_seitz_type

    ! irvec(i,irpt)     The irpt-th Wigner-Seitz grid point has components
    !                   irvec(1:3,irpt) in the basis of the lattice vectors
    ! ndegen(irpt)      Weight of the irpt-th point is 1/ndegen(irpt)
    ! nrpts             number of Wigner-Seitz grid points

    implicit none
    ! arguments
    type(ws_region_type), intent(in)    :: ws_region
    type(print_output_type), intent(in) :: print_output
    type(timer_list_type), intent(inout) :: timer
    type(w90_comm_type), intent(in) :: comm
    type(wigner_seitz_type), intent(inout) :: wigner_seitz
    type(w90_error_type), allocatable, intent(out) :: error

    integer, intent(in) :: mp_grid(3)
    integer, intent(in) :: stdout
    logical, intent(in) :: count_pts
    real(kind=dp), intent(in) :: real_lattice(3, 3)

    ! local variables
    integer       :: ierr, dist_dim
    integer       :: n1, n2, n3, i1, i2, i3, icnt, i, j, ir
    integer       :: ndiff(3)
    logical :: on_root = .false.
    real(kind=dp), allocatable :: dist(:)
    real(kind=dp) :: real_metric(3, 3)
    real(kind=dp) :: tot, dist_min

    if (mpirank(comm) == 0) on_root = .true.

    if (print_output%timing_level > 1 .and. on_root) &
      call io_stopwatch_start('postw90_common: wigner_seitz', timer)

    call utility_metric(real_lattice, real_metric)

    dist_dim = 1
    do i = 1, 3
      dist_dim = dist_dim*((ws_region%ws_search_size(i) + 1)*2 + 1)
    end do
    allocate (dist(dist_dim), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating dist in wigner_seitz', comm)
      return
    end if

    ! The Wannier functions live in a periodic supercell of the real space unit
    ! cell. This supercell is mp_grid(i) unit cells long along each primitive
    ! translation vector a_i of the unit cell
    !
    ! We loop over grid points r on a unit cell that is (2*ws_search_size+1)**3 times
    ! larger than this primitive supercell.
    !
    ! One of these points is in the W-S supercell if it is closer to R=0 than any
    ! of the other points R (where R are the translation vectors of the
    ! supercell).

    ! In the end, nrpts contains the total number of grid points that have been
    ! found in the Wigner-Seitz cell

    wigner_seitz%nrpts = 0
    ! Loop over the lattice vectors of the primitive cell
    ! that live in a supercell which is (2*ws_search_size+1)**2
    ! larger than the Born-von Karman supercell.
    ! We need to find which among these live in the Wigner-Seitz cell
    do n1 = -ws_region%ws_search_size(1)*mp_grid(1), ws_region%ws_search_size(1)*mp_grid(1)
      do n2 = -ws_region%ws_search_size(2)*mp_grid(2), ws_region%ws_search_size(2)*mp_grid(2)
        do n3 = -ws_region%ws_search_size(3)*mp_grid(3), ws_region%ws_search_size(3)*mp_grid(3)
          ! Loop over the lattice vectors R of the Born-von Karman supercell
          ! that contains all the points of the previous loop.
          ! There are (2*(ws_search_size+1)+1)**3 points R. R=0 corresponds to
          ! i1=i2=i3=0, or icnt=((2*(ws_search_size+1)+1)**3 + 1)/2
          icnt = 0
          do i1 = -ws_region%ws_search_size(1) - 1, ws_region%ws_search_size(1) + 1
            do i2 = -ws_region%ws_search_size(2) - 1, ws_region%ws_search_size(2) + 1
              do i3 = -ws_region%ws_search_size(3) - 1, ws_region%ws_search_size(3) + 1
                icnt = icnt + 1
                ! Calculate distance squared |r-R|^2
                ndiff(1) = n1 - i1*mp_grid(1)
                ndiff(2) = n2 - i2*mp_grid(2)
                ndiff(3) = n3 - i3*mp_grid(3)
                dist(icnt) = 0.0_dp
                do i = 1, 3
                  do j = 1, 3
                    dist(icnt) = dist(icnt) + &
                                 real(ndiff(i), dp)*real_metric(i, j)*real(ndiff(j), dp)
                  end do
                end do
              end do
            end do
          end do
          dist_min = minval(dist)
          if (abs(dist((dist_dim + 1)/2) - dist_min) .lt. ws_region%ws_distance_tol**2) then
            wigner_seitz%nrpts = wigner_seitz%nrpts + 1
            if (.not. count_pts) then
              wigner_seitz%ndegen(wigner_seitz%nrpts) = 0
              do i = 1, dist_dim
                if (abs(dist(i) - dist_min) .lt. ws_region%ws_distance_tol**2) &
                  wigner_seitz%ndegen(wigner_seitz%nrpts) = &
                  wigner_seitz%ndegen(wigner_seitz%nrpts) + 1
              end do
              wigner_seitz%irvec(1, wigner_seitz%nrpts) = n1
              wigner_seitz%irvec(2, wigner_seitz%nrpts) = n2
              wigner_seitz%irvec(3, wigner_seitz%nrpts) = n3

              ! Remember which grid point r is at the origin

              if (n1 == 0 .and. n2 == 0 .and. n3 == 0) wigner_seitz%rpt_origin = wigner_seitz%nrpts
            end if
          end if

          !n3
        end do
        !n2
      end do
      !n1
    end do
    !
    deallocate (dist, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating dist wigner_seitz', comm)
      return
    end if

    if (count_pts) then
      if (print_output%timing_level > 1 .and. on_root) &
        call io_stopwatch_stop('postw90_common: wigner_seitz', timer)
      return
    end if

    if (print_output%iprint >= 3 .and. on_root) then
      write (stdout, '(1x,i4,a,/)') wigner_seitz%nrpts, &
        ' lattice points in Wigner-Seitz supercell:'
      do ir = 1, wigner_seitz%nrpts
        write (stdout, '(4x,a,3(i3,1x),a,i2)') '  vector ', wigner_seitz%irvec(1, ir), &
          wigner_seitz%irvec(2, ir), wigner_seitz%irvec(3, ir), '  degeneracy: ', &
          wigner_seitz%ndegen(ir)
      end do
      !write (stdout, '(1x,a,f12.3)') ' tot = ', tot !not yet computed
      write (stdout, '(1x,a,i12)') ' mp_grid product = ', mp_grid(1)*mp_grid(2)*mp_grid(3)
    end if
    ! Check the "sum rule"
    tot = 0.0_dp
    do ir = 1, wigner_seitz%nrpts
      !
      ! Corrects weights in Fourier sums for R-vectors on the boundary of the
      ! W-S supercell
      !
      tot = tot + 1.0_dp/real(wigner_seitz%ndegen(ir), dp)
    end do
    if (abs(tot - real(mp_grid(1)*mp_grid(2)*mp_grid(3), dp)) > eps8) then
      call set_error_fatal(error, 'ERROR in wigner_seitz: error in finding Wigner-Seitz points', comm)
      return
    end if
    if (print_output%timing_level > 1 .and. on_root) &
      call io_stopwatch_stop('postw90_common: wigner_seitz', timer)
    return
  end subroutine wignerseitz

  !=========================================================!
  subroutine wigner_seitz_opt_setup(ws_distance, ws_region, wigner_seitz, num_wann, real_lattice, &
                                    nrpts, error, comm)
    !==========================================================================
    !
    !! If use_ws_distance = true, get list of all R vectors that appear in
    !! irdist_ws and write them to irvec_pw90. Set the index map ir_ind_ws_to_pw90
    !!
    !! If use_ws_distance = false, copy irvec to irvec_pw90.
    !
    !==========================================================================

    use w90_constants, only: dp
    use w90_types, only: ws_region_type, ws_distance_type
    use w90_comms, only: w90_comm_type
    use w90_postw90_types, only: wigner_seitz_type

    implicit none

    type(ws_distance_type), intent(inout) :: ws_distance
    type(ws_region_type), intent(in) :: ws_region
    type(wigner_seitz_type), intent(inout) :: wigner_seitz
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    real(kind=dp), intent(in) :: real_lattice(3, 3)
    integer, intent(in) :: num_wann
    integer, intent(in) :: nrpts
    logical :: found
    integer :: i, j, ideg, ir, jr, ierr, nrpts_found, max_ndeg, ivdum(3)
    integer, allocatable :: irvec_found(:, :), irvec_temp(:, :)

    if (ws_region%use_ws_distance) then

      max_ndeg = maxval(ws_distance%ndeg)
      allocate (wigner_seitz%ir_ind_ws_to_pw90(max_ndeg, num_wann, num_wann, nrpts), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating ir_ind_ws_to_pw90 in wigner_seitz_opt_setup', comm)
        return
      end if
      wigner_seitz%ir_ind_ws_to_pw90 = -1

      ! find the set of R vectors from irdist_ws, removing duplicates
      nrpts_found = 0
      do ir = 1, nrpts
        do j = 1, num_wann
          do i = 1, num_wann
            do ideg = 1, ws_distance%ndeg(i, j, ir)
              ivdum = ws_distance%irdist(:, ideg, i, j, ir)

              if (nrpts_found == 0) then
                nrpts_found = 1
                allocate (irvec_found(3, 1), stat=ierr)
                if (ierr /= 0) then
                  call set_error_alloc(error, 'Error in allocating irvec_found in wigner_seitz_opt_setup', comm)
                  return
                end if
                irvec_found(:, 1) = ivdum

                wigner_seitz%ir_ind_ws_to_pw90(ideg, i, j, ir) = 1

              else
                ! find if ivdum is already found
                found = .false.
                do jr = 1, nrpts_found
                  if (all(ivdum == irvec_found(:, jr))) then
                    wigner_seitz%ir_ind_ws_to_pw90(ideg, i, j, ir) = jr
                    found = .true.
                    exit
                  end if
                end do

                ! if not found, add ivdum to irvec_found
                if (.not. found) then

                  ! copy irvec_found to irvec_temp
                  allocate (irvec_temp(3, nrpts_found), stat=ierr)
                  if (ierr /= 0) then
                    call set_error_alloc(error, 'Error in allocating irvec_temp in wigner_seitz_opt_setup', comm)
                    return
                  end if
                  irvec_temp = irvec_found
                  deallocate (irvec_found, stat=ierr)
                  if (ierr /= 0) then
                    call set_error_dealloc(error, 'Error in deallocating irvec_found in wigner_seitz_opt_setup', comm)
                    return
                  end if

                  allocate (irvec_found(3, nrpts_found + 1), stat=ierr)
                  if (ierr /= 0) then
                    call set_error_alloc(error, 'Error in allocating irvec_found in wigner_seitz_opt_setup', comm)
                    return
                  end if
                  irvec_found(:, 1:nrpts_found) = irvec_temp
                  irvec_found(:, nrpts_found + 1) = ivdum
                  deallocate (irvec_temp, stat=ierr)
                  if (ierr /= 0) then
                    call set_error_dealloc(error, 'Error in deallocating irvec_temp in wigner_seitz_opt_setup', comm)
                    return
                  end if

                  nrpts_found = nrpts_found + 1

                  wigner_seitz%ir_ind_ws_to_pw90(ideg, i, j, ir) = nrpts_found

                end if ! found
              end if ! nrpts_found == 0

            end do
          end do
        end do
      end do

      wigner_seitz%nrpts_pw90 = nrpts_found

      allocate (wigner_seitz%irvec_pw90(3, wigner_seitz%nrpts_pw90), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating irvec_pw90 in wigner_seitz_opt_setup', comm)
        return
      end if
      allocate (wigner_seitz%crvec_pw90(3, wigner_seitz%nrpts_pw90), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating crvec_pw90 in wigner_seitz_opt_setup', comm)
        return
      end if

      wigner_seitz%irvec_pw90 = irvec_found
      do ir = 1, wigner_seitz%nrpts_pw90
        wigner_seitz%crvec_pw90(:, ir) = matmul(transpose(real_lattice), real(wigner_seitz%irvec_pw90(:, ir), dp))
      end do

    else ! .not. use_ws_distance
      allocate (wigner_seitz%irvec_pw90(3, wigner_seitz%nrpts_pw90), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating irvec_pw90 in wigner_seitz_opt_setup', comm)
        return
      end if
      allocate (wigner_seitz%crvec_pw90(3, wigner_seitz%nrpts_pw90), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating crvec_pw90 in wigner_seitz_opt_setup', comm)
        return
      end if

      wigner_seitz%irvec_pw90 = wigner_seitz%irvec
      wigner_seitz%crvec_pw90 = wigner_seitz%crvec
      wigner_seitz%nrpts_pw90 = wigner_seitz%nrpts
    end if ! use_ws_distance

    ! Check degeneracy factor ndegen for R = 0 is 1
    if (wigner_seitz%ndegen(wigner_seitz%rpt_origin) /= 1) then
      call set_error_fatal(error, 'ndegen for R=0 is not 1.', comm)
      return
    end if

    ! Check degeneracy factor ws_distance%ndeg for a Wannier function with itself,
    ! i.e. R = 0 and i = j, is 1.
    if (ws_region%use_ws_distance) then
      do ir = 1, nrpts
        do i = 1, num_wann
          do ideg = 1, ws_distance%ndeg(i, i, ir)
            ivdum = ws_distance%irdist(:, ideg, i, i, ir)
            if (all(ivdum == 0)) then
              if (ws_distance%ndeg(i, i, ir) /= 1) then
                call set_error_fatal(error, 'ws_distance%ndeg for R=0 and i=j is not 1.', comm)
                return
              end if
            end if
          end do
        end do
      end do
    end if

  end subroutine wigner_seitz_opt_setup

end module w90_postw90_common
