!-*- 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_plot: plot various data                               !
!                                                            !
!------------------------------------------------------------!

module w90_plot_mod

  !! This module handles various plots

  use w90_comms, only: comms_reduce, w90_comm_type, mpisize, mpirank, comms_array_split

  implicit none

  private

  public :: plot_main

contains

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

  subroutine plot_main(atom_data, band_plot, dis_manifold, fermi_energy_list, fermi_surface_plot, &
                       ham_logical, kmesh_info, kpt_latt, output_file, wvfn_read, real_space_ham, &
                       kpoint_path, print_output, wannier_data, wannier_plot, ws_region, &
                       w90_calculation, ham_k, ham_r, m_matrix, u_matrix, u_matrix_opt, eigval, &
                       real_lattice, wannier_centres_translated, bohr, irvec, mp_grid, ndegen, &
                       shift_vec, nrpts, num_bands, num_kpts, num_wann, rpt_origin, &
                       transport_mode, have_disentangled, lsitesymmetry, w90_system, seedname, &
                       stdout, timer, dist_k, error, comm)
    !================================================!
    !
    !! Main plotting routine
    !
    !================================================!

    use w90_constants, only: eps6, dp
    use w90_hamiltonian, only: hamiltonian_get_hr, hamiltonian_write_hr, hamiltonian_setup, &
                               hamiltonian_write_tb
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_types, only: kmesh_info_type, wannier_data_type, atom_data_type, dis_manifold_type, &
                         kpoint_path_type, print_output_type, ws_region_type, ws_distance_type, timer_list_type, &
                         w90_system_type
    use w90_utility, only: utility_recip_lattice_base
    use w90_wannier90_types, only: w90_calculation_type, wvfn_read_type, output_file_type, &
                                   fermi_surface_plot_type, band_plot_type, wannier_plot_type, real_space_ham_type, &
                                   ham_logical_type
    use w90_ws_distance, only: ws_translate_dist, ws_write_vec
    use w90_error, only: w90_error_type

    implicit none

    ! arguments
    type(real_space_ham_type), intent(inout) :: real_space_ham
    type(w90_error_type), allocatable, intent(out) :: error
    type(timer_list_type), intent(inout) :: timer
    type(ham_logical_type), intent(inout) :: ham_logical

    complex(kind=dp), intent(inout), allocatable :: ham_r(:, :, :)
    complex(kind=dp), intent(inout), allocatable :: ham_k(:, :, :)

    real(kind=dp), intent(inout), allocatable :: wannier_centres_translated(:, :)

    integer, intent(inout), allocatable :: irvec(:, :)
    integer, intent(inout), allocatable :: ndegen(:)
    integer, intent(inout), allocatable :: shift_vec(:, :)
    integer, intent(inout)              :: nrpts
    integer, intent(inout)              :: rpt_origin

    type(atom_data_type), intent(in) :: atom_data
    type(band_plot_type), intent(in) :: band_plot
    type(dis_manifold_type), intent(in) :: dis_manifold
    type(fermi_surface_plot_type), intent(in) :: fermi_surface_plot
    type(kmesh_info_type), intent(in) :: kmesh_info
    type(kpoint_path_type), intent(in) :: kpoint_path
    type(output_file_type), intent(in) :: output_file
    type(print_output_type), intent(in) :: print_output
    type(w90_calculation_type), intent(in) :: w90_calculation
    type(w90_comm_type), intent(in) :: comm
    type(w90_system_type), intent(in) :: w90_system
    type(wannier_data_type), intent(in) :: wannier_data
    type(wannier_plot_type), intent(in) :: wannier_plot
    type(ws_region_type), intent(in) :: ws_region
    type(wvfn_read_type), intent(in) :: wvfn_read

    complex(kind=dp), intent(in) :: m_matrix(:, :, :, :)
    complex(kind=dp), intent(in) :: u_matrix_opt(:, :, :)
    complex(kind=dp), intent(in) :: u_matrix(:, :, :)

    real(kind=dp), intent(in), allocatable :: fermi_energy_list(:)
    real(kind=dp), intent(in) :: bohr
    real(kind=dp), intent(in) :: eigval(:, :)
    real(kind=dp), intent(in) :: kpt_latt(:, :)
    real(kind=dp), intent(in) :: real_lattice(3, 3)

    integer, intent(in) :: mp_grid(3)
    integer, intent(in) :: num_bands
    integer, intent(in) :: num_kpts
    integer, intent(in) :: num_wann
    integer, intent(in) :: stdout
    integer, intent(in) :: dist_k(:)

    character(len=20), intent(in) :: transport_mode
    character(len=50), intent(in) :: seedname

    logical, intent(in) :: have_disentangled
    logical, intent(in) :: lsitesymmetry

    ! local variables
    type(ws_distance_type) :: ws_distance
    real(kind=dp) :: recip_lattice(3, 3), volume
    integer :: nkp, bands_num_spec_points, my_node_id, i
    logical :: have_gamma
    logical :: on_root = .false.

    my_node_id = mpirank(comm)

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

    call utility_recip_lattice_base(real_lattice, recip_lattice, volume)

    ! setup RS cluster for calculations that need it
    if (output_file%write_hr .or. &
        output_file%write_hr_diag .or. &
        output_file%write_r2mn .or. &
        output_file%write_rmn .or. &
        output_file%write_tb .or. &
        w90_calculation%bands_plot .or. &
        w90_calculation%fermi_surface_plot) then

      call hamiltonian_setup(ham_logical, print_output, ws_region, w90_calculation, ham_k, ham_r, &
                             real_lattice, wannier_centres_translated, irvec, mp_grid, ndegen, &
                             num_kpts, num_wann, nrpts, rpt_origin, band_plot%mode, stdout, &
                             timer, error, transport_mode, comm)
      if (allocated(error)) return
    end if

    ! setup RS Hamilton eqn for calculations that need it
    if (output_file%write_hr .or. &
        output_file%write_hr_diag .or. &
        output_file%write_tb .or. &
        w90_calculation%bands_plot .or. &
        w90_calculation%fermi_surface_plot) then

      ! Check if the kmesh includes the gamma point
      have_gamma = .false.
      do nkp = 1, num_kpts
        if (all(abs(kpt_latt(:, nkp)) < eps6)) have_gamma = .true.
      end do
      if (.not. have_gamma) then
        write (stdout, '(1x,a)') '!!!! Kpoint grid does not include Gamma. '// &
          ' Interpolation may be incorrect. !!!!'
      end if

      ! Transform Hamiltonian to WF basis
      call hamiltonian_get_hr(atom_data, dis_manifold, ham_logical, real_space_ham, print_output, &
                              ham_k, ham_r, u_matrix, u_matrix_opt, eigval, kpt_latt, &
                              real_lattice, wannier_data%centres, wannier_centres_translated, &
                              irvec, shift_vec, nrpts, num_bands, num_kpts, num_wann, &
                              have_disentangled, stdout, timer, error, lsitesymmetry, comm)

      if (allocated(error)) return
    end if

    if (on_root) then
      if (print_output%timing_level > 0) call io_stopwatch_start('plot: main', timer)

      if (print_output%iprint > 0) then
        ! Print the header only if there is something to plot
        if (output_file%write_hr .or. &
            output_file%write_r2mn .or. &
            output_file%write_rmn .or. &
            output_file%write_tb .or. &
            output_file%write_u_matrices .or. &
            w90_calculation%bands_plot .or. &
            w90_calculation%fermi_surface_plot .or. &
            w90_calculation%wannier_plot) then
          write (stdout, '(1x,a)') '*---------------------------------------------------------------------------*'
          write (stdout, '(1x,a)') '|                               PLOTTING                                    |'
          write (stdout, '(1x,a)') '*---------------------------------------------------------------------------*'
          write (stdout, *)
        end if
      end if

      if (w90_calculation%fermi_surface_plot) then
        call plot_fermi_surface(fermi_energy_list, recip_lattice, fermi_surface_plot, num_wann, &
                                ham_r, irvec, ndegen, nrpts, print_output%timing_level, stdout, &
                                seedname, timer, error, comm)
        if (allocated(error)) return
      end if

      if (output_file%write_hr .or. output_file%write_tb) then
        call ws_translate_dist(ws_distance, ws_region, num_wann, &
                               wannier_data%centres, real_lattice, mp_grid, nrpts, irvec, &
                               error, comm, force_recompute=.false.)
        if (allocated(error)) return

        call ws_write_vec(ws_distance, nrpts, irvec, num_wann, ws_region%use_ws_distance, &
                          seedname, error, comm)
        if (allocated(error)) return
      end if

      ! calculate and write projection of WFs on original bands in outer window
      ! only meaningful in disentanglement case (and otherwise lwindow is not available)
      if (output_file%write_proj .and. num_bands > num_wann) then
        call plot_calc_projection(num_bands, num_wann, num_kpts, u_matrix_opt, eigval, &
                                  dis_manifold%lwindow, print_output%timing_level, &
                                  print_output%iprint, stdout, timer)
        if (allocated(error)) return
      end if

      if (output_file%write_bvec) then
        call plot_bvec(kmesh_info, num_kpts, seedname, error, comm)
        if (allocated(error)) return
      end if

      if (output_file%write_hr) then
        ! this is a trivial matrix write; no need to parallelize
        call hamiltonian_write_hr(ham_r, irvec, ndegen, nrpts, num_wann, &
                                  print_output%timing_level, seedname, timer, error, comm)
        if (allocated(error)) return
      end if

      if (output_file%write_hr_diag) then
        if (print_output%iprint > 0) then
          write (stdout, *)
          write (stdout, '(1x,a)') 'On-site Hamiltonian matrix elements'
          write (stdout, '(3x,a)') '  n        <0n|H|0n> (eV)'
          write (stdout, '(3x,a)') '-------------------------'
          do i = 1, num_wann
            write (stdout, '(3x,i3,5x,f12.6)') i, real(ham_r(i, i, rpt_origin), kind=dp)
          end do
          write (stdout, *)
        end if
      end if

      if (output_file%write_u_matrices) then
        call plot_u_matrices(u_matrix_opt, u_matrix, kpt_latt, dis_manifold, have_disentangled, &
                             num_wann, num_kpts, num_bands, seedname, error, comm)
        if (allocated(error)) return
      end if

      if (output_file%write_vdw_data) then
        ! aam: write data required for vdW utility
        call plot_write_vdw_data(num_wann, wannier_data, real_lattice, u_matrix, u_matrix_opt, &
                                 have_disentangled, w90_system, error, comm, stdout, seedname)
        if (allocated(error)) return
      end if

      if (output_file%write_xyz) then
        call plot_write_xyz(real_space_ham%translate_home_cell, num_wann, wannier_data%centres, &
                            real_lattice, atom_data, print_output, error, comm, stdout, seedname)
        if (allocated(error)) return
      end if
    end if !on_root

    if (w90_calculation%bands_plot) then
      bands_num_spec_points = 0
      if (allocated(kpoint_path%labels)) bands_num_spec_points = size(kpoint_path%labels)

      call plot_interpolate_bands(mp_grid, real_lattice, band_plot, kpoint_path, real_space_ham, &
                                  ws_region, print_output, recip_lattice, num_wann, wannier_data, &
                                  ham_r, irvec, ndegen, nrpts, wannier_centres_translated, &
                                  ws_distance, bands_num_spec_points, stdout, seedname, timer, &
                                  error, comm)
      if (allocated(error)) return
    end if

    if (output_file%svd_omega) then
      call plot_svd_omega_i(num_wann, num_kpts, kmesh_info, m_matrix, print_output, timer, dist_k, &
                            error, comm, stdout)
      if (allocated(error)) return
    end if

    if (output_file%write_rmn) then
      ! parallel write_rmn
      call plot_write_rmn(kmesh_info, m_matrix, kpt_latt, irvec, nrpts, num_kpts, num_wann, &
                          seedname, dist_k, error, comm)
      if (allocated(error)) return
    end if

    if (output_file%write_r2mn) then
      ! write matrix elements <m|r^2|n> to file
      call plot_write_r2mn(num_kpts, num_wann, kmesh_info, m_matrix, seedname, dist_k, error, comm)
      if (allocated(error)) return
    end if

    if (output_file%write_tb) then
      call hamiltonian_write_tb(kmesh_info, ham_r, m_matrix, kpt_latt, real_lattice, irvec, &
                                ndegen, nrpts, num_kpts, num_wann, print_output%timing_level, &
                                seedname, timer, dist_k, error, comm)
      if (allocated(error)) return
    end if

    if (w90_calculation%wannier_plot) then
      call plot_wannier(wannier_plot, wvfn_read, wannier_data, print_output, u_matrix_opt, &
                        dis_manifold, real_lattice, atom_data, kpt_latt, u_matrix, num_kpts, &
                        num_bands, num_wann, have_disentangled, w90_system%spinors, bohr, stdout, &
                        seedname, timer, dist_k, error, comm)
      if (allocated(error)) return
    end if

    if (on_root .and. print_output%timing_level > 0) call io_stopwatch_stop('plot: main', timer)
  end subroutine plot_main

  !-----------------------------------------------------------------
  !----------------- Private Routines ------------------------------
  !-----------------------------------------------------------------

  !================================================!
  subroutine plot_interpolate_bands(mp_grid, real_lattice, band_plot, kpoint_path, real_space_ham, &
                                    ws_region, print_output, recip_lattice, num_wann, &
                                    wannier_data, ham_r, irvec, ndegen, nrpts, &
                                    wannier_centres_translated, ws_distance, &
                                    bands_num_spec_points, stdout, seedname, timer, error, comm)
    !================================================!
    !                                            !
    !! Plots the interpolated band structure
    !                                            !
    !================================================!

    use w90_constants, only: dp, cmplx_0, twopi
    use w90_io, only: io_time, io_stopwatch_start, io_stopwatch_stop
    use w90_ws_distance, only: ws_translate_dist
    use w90_utility, only: utility_metric
    use w90_types, only: wannier_data_type, kpoint_path_type, print_output_type, ws_region_type, &
                         ws_distance_type, timer_list_type
    use w90_wannier90_types, only: band_plot_type, real_space_ham_type
    use w90_error, only: w90_error_type, set_error_alloc, set_error_dealloc, set_error_fatal, &
                         set_error_warn

    implicit none

    ! arguments
    type(band_plot_type), intent(in) :: band_plot
    type(kpoint_path_type), intent(in) :: kpoint_path
    type(print_output_type), intent(in) :: print_output
    type(real_space_ham_type), intent(in) :: real_space_ham
    type(wannier_data_type), intent(in) :: wannier_data
    type(ws_distance_type), intent(inout) :: ws_distance
    type(ws_region_type), intent(in) :: ws_region
    type(timer_list_type), intent(inout) :: timer
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(inout) :: nrpts
    integer, intent(in) :: ndegen(:)
    integer, intent(in) :: irvec(:, :)
    integer, intent(in) :: mp_grid(3)
    integer, intent(in) :: num_wann
    integer, intent(in) :: bands_num_spec_points
    integer, intent(in) :: stdout

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

    complex(kind=dp), intent(in) :: ham_r(:, :, :)
    character(len=50), intent(in)  :: seedname

    ! local variables
    integer, allocatable :: irvec_cut(:, :)
    integer              :: irvec_max(3)
    integer              :: nrpts_cut
    integer, allocatable :: iwork(:), ifail(:)
    integer              :: info, i, j
    integer              :: irpt, nfound, loop_kpt, counter
    integer              :: loop_spts, total_pts, loop_i, nkp, ideg
    integer              :: num_paths, num_spts, ierr
    integer              :: bndunit, gnuunit, loop_w, loop_p
    integer, allocatable :: kpath_pts(:)
    integer, allocatable :: idx_special_points(:)
    integer, allocatable :: label_idx_special_points(:)

    real(kind=dp)              :: rdotk, vec(3), emin, emax, time0
    real(kind=dp), allocatable :: kpath_len(:)
    real(kind=dp), allocatable :: rwork(:)
    real(kind=dp), allocatable :: xval(:)
    real(kind=dp), allocatable :: eig_int(:, :), plot_kpoint(:, :)
    real(kind=dp), allocatable :: bands_proj(:, :)
    real(kind=dp), allocatable :: xval_special_points(:)
    real(kind=dp)              :: recip_metric(3, 3)

    complex(kind=dp)              :: fac
    complex(kind=dp), allocatable :: ham_r_cut(:, :, :)
    complex(kind=dp), allocatable :: ham_pack(:)
    complex(kind=dp), allocatable :: ham_kprm(:, :)
    complex(kind=dp), allocatable :: U_int(:, :)
    complex(kind=dp), allocatable :: cwork(:)

    logical, allocatable :: kpath_print_first_point(:)

    character(len=20), allocatable :: glabel(:)
    character(len=20), allocatable :: xlabel(:)
    character(len=20), allocatable :: ctemp(:)

    ! mpi variables
    integer ::  my_node_id, num_nodes
    logical ::  on_root
    integer, allocatable :: counts(:)
    integer, allocatable :: displs(:)

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

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

    allocate (counts(0:num_nodes - 1), stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating counts in plot_interpolate_bands', comm)
      return
    end if
    allocate (displs(0:num_nodes - 1), stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating displs in plot_interpolate_bands', comm)
      return
    end if
    !
    if (on_root) then
      if (print_output%timing_level > 1) then
        call io_stopwatch_start('plot: interpolate_bands', timer)
      end if
      !
      time0 = io_time()

      write (stdout, *)
      write (stdout, '(1x,a)') 'Calculating interpolated band-structure'
      write (stdout, *)
    end if ! on_root
    call utility_metric(recip_lattice, recip_metric)
    !
    allocate (ham_pack((num_wann*(num_wann + 1))/2), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating ham_pack in plot_interpolate_bands', comm)
      return
    end if
    allocate (ham_kprm(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating ham_kprm in plot_interpolate_bands', comm)
      return
    end if
    allocate (U_int(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating U_int in plot_interpolate_bands', comm)
      return
    end if
    allocate (cwork(2*num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cwork in plot_interpolate_bands', comm)
      return
    end if
    allocate (rwork(7*num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating rwork in plot_interpolate_bands', comm)
      return
    end if
    allocate (iwork(5*num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating iwork in plot_interpolate_bands', comm)
      return
    end if
    allocate (ifail(num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating ifail in plot_interpolate_bands', comm)
      return
    end if

    if (.not. kpoint_path%bands_kpt_explicit) then
      allocate (kpath_len(bands_num_spec_points/2), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating kpath_len in plot_interpolate_bands', comm)
        return
      end if
      allocate (kpath_pts(bands_num_spec_points/2), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating kpath_pts in plot_interpolate_bands', comm)
        return
      end if
      allocate (kpath_print_first_point(bands_num_spec_points/2), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating kpath_print_first_point in plot_interpolate_bands', comm)
        return
      end if
      allocate (idx_special_points(bands_num_spec_points), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating idx_special_points in plot_interpolate_bands', comm)
        return
      end if
      allocate (xval_special_points(bands_num_spec_points), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating xval_special_points in plot_interpolate_bands', comm)
        return
      end if
      idx_special_points = -1
      xval_special_points = -1._dp
    end if
    !
    ! Work out how many points in the total path and the positions of the special points
    !
    if (kpoint_path%bands_kpt_explicit) then
      total_pts = size(kpoint_path%bands_kpt_frac, 2)
      ! Count the total number of special points
      num_spts = 0
      do i = 1, total_pts
        do j = 1, bands_num_spec_points
          if (sum((kpoint_path%bands_kpt_frac(:, i) - kpoint_path%points(:, j))**2) <= 1.e-6) then
            num_spts = num_spts + 1
            exit
          end if
        end do
      end do
    else
      num_paths = bands_num_spec_points/2

      kpath_print_first_point = .false.

      ! Loop over paths, set to False print_first_point if the starting point
      ! is the same as the ending point of the previous path.
      ! I skip the first path for which I always want to print the first point.
      kpath_print_first_point(1) = .true.
      do i = 2, num_paths
        ! If either the coordinates are different or the label is different, compute again the point
        ! (it will end up at the same x coordinate)
        if ((SUM((kpoint_path%points(:, (i - 1)*2) - &
                  kpoint_path%points(:, (i - 1)*2 + 1))**2) > 1.e-6) .or. &
            (TRIM(kpoint_path%labels((i - 1)*2)) .ne. &
             TRIM(kpoint_path%labels((i - 1)*2 + 1)))) then
          kpath_print_first_point(i) = .true.
        end if
      end do

      ! Count the total number of special points
      num_spts = num_paths
      do i = 1, num_paths
        if (kpath_print_first_point(i)) num_spts = num_spts + 1
      end do

      do loop_spts = 1, num_paths
        vec = kpoint_path%points(:, 2*loop_spts) - kpoint_path%points(:, 2*loop_spts - 1)
        kpath_len(loop_spts) = sqrt(dot_product(vec, (matmul(recip_metric, vec))))
        if (loop_spts == 1) then
          kpath_pts(loop_spts) = kpoint_path%num_points_first_segment
        else
          kpath_pts(loop_spts) = nint(real(kpoint_path%num_points_first_segment, dp) &
                                      *kpath_len(loop_spts)/kpath_len(1))
          ! At least 1 point
          !if (kpath_pts(loop_spts) .eq. 0) kpath_pts(loop_spts) = 1
        end if
      end do
      total_pts = sum(kpath_pts)
      do i = 1, num_paths
        if (kpath_print_first_point(i)) total_pts = total_pts + 1
      end do
    end if

    allocate (plot_kpoint(3, total_pts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating plot_kpoint in plot_interpolate_bands', comm)
      return
    end if
    allocate (xval(total_pts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating xval in plot_interpolate_bands', comm)
      return
    end if
    allocate (eig_int(num_wann, total_pts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating eig_int in plot_interpolate_bands', comm)
      return
    end if
    allocate (bands_proj(num_wann, total_pts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating bands_proj in plot_interpolate_bands', comm)
      return
    end if
    allocate (glabel(num_spts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating glabel in plot_interpolate_bands', comm)
      return
    end if
    allocate (xlabel(num_spts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating xlabel in plot_interpolate_bands', comm)
      return
    end if
    allocate (ctemp(bands_num_spec_points), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating ctemp in plot_interpolate_bands', comm)
      return
    end if
    eig_int = 0.0_dp; bands_proj = 0.0_dp
    !
    ! Find the position of each kpoint in the path
    !
    if (kpoint_path%bands_kpt_explicit) then
      allocate (idx_special_points(num_spts), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating idx_special_points in plot_interpolate_bands', comm)
        return
      end if
      allocate (xval_special_points(num_spts), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating xval_special_points in plot_interpolate_bands', comm)
        return
      end if
      idx_special_points = -1
      xval_special_points = -1._dp
      allocate (label_idx_special_points(num_spts), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating label_idx_special_points in plot_interpolate_bands', comm)
        return
      end if
      plot_kpoint(:, :) = kpoint_path%bands_kpt_frac(:, :)
      xval = 0.0_dp
      counter = 0
      do i = 1, total_pts
        if (i > 1) then
          vec = plot_kpoint(:, i) - plot_kpoint(:, i - 1)
          xval(i) = xval(i - 1) + sqrt(dot_product(vec, (matmul(recip_metric, vec))))
        end if
        do j = 1, bands_num_spec_points
          if (sum((kpoint_path%bands_kpt_frac(:, i) - kpoint_path%points(:, j))**2) <= 1.e-6) then
            counter = counter + 1
            idx_special_points(counter) = i
            label_idx_special_points(counter) = j
            xval_special_points(counter) = xval(i)
            if (counter > 1) then
              if (idx_special_points(counter) == idx_special_points(counter - 1) + 1) then
                ! If the two points are consecutive, it means that the x coordinate should be the same
                xval(i) = xval(i - 1)
                xval_special_points(counter) = xval(i)
              end if
            end if
            exit
          end if
        end do
      end do

    else
      counter = 0
      do loop_spts = 1, num_paths
        if (kpath_print_first_point(loop_spts)) then
          counter = counter + 1
          if (counter == 1) then
            xval(counter) = 0.0_dp
          else
            ! If we are printing the first point in a path,
            ! It means that the coordinate did not change (otherwise
            ! we would not be printing it). Therefore I do not move
            ! on the x axis, there was a jump in the path here.
            xval(counter) = xval(counter - 1)
          end if
          plot_kpoint(:, counter) = kpoint_path%points(:, 2*loop_spts - 1)

          idx_special_points(2*loop_spts - 1) = counter
          xval_special_points(2*loop_spts - 1) = xval(counter)
        end if

        ! This is looping on all points but the first (1 is the first point
        ! after the first in the path)
        do loop_i = 1, kpath_pts(loop_spts)
          counter = counter + 1
          ! Set xval, the x position on the path of the current path
          if (counter == 1) then
            ! This case should never happen but I keep it in for "safety"
            xval(counter) = 0.0_dp
          else
            xval(counter) = xval(counter - 1) + kpath_len(loop_spts)/real(kpath_pts(loop_spts), dp)
          end if
          plot_kpoint(:, counter) = kpoint_path%points(:, 2*loop_spts - 1) + &
                                    (kpoint_path%points(:, 2*loop_spts) &
                                     - kpoint_path%points(:, 2*loop_spts - 1))* &
                                    (real(loop_i, dp)/real(kpath_pts(loop_spts), dp))
        end do
        idx_special_points(2*loop_spts) = counter
        xval_special_points(2*loop_spts) = xval(counter)
      end do
      !xval(total_pts)=sum(kpath_len)
      plot_kpoint(:, total_pts) = kpoint_path%points(:, bands_num_spec_points)
    end if
    !
    ! Write out the kpoints in the path
    !
    if (on_root) then
      open (newunit=bndunit, file=trim(seedname)//'_band.kpt', form='formatted')
      write (bndunit, *) total_pts
      do loop_spts = 1, total_pts
        write (bndunit, '(3f12.6,3x,a)') (plot_kpoint(loop_i, loop_spts), loop_i=1, 3), "1.0"
      end do
      close (bndunit)
      !
      ! Write out information on high-symmetry points in the path
      !
      open (newunit=bndunit, file=trim(seedname)//'_band.labelinfo.dat', form='formatted')
      if (kpoint_path%bands_kpt_explicit) then
        do loop_spts = 1, num_spts
          write (bndunit, '(a,3x,I10,3x,4f18.10)') &
            kpoint_path%labels(label_idx_special_points(loop_spts)), &
            idx_special_points(loop_spts), &
            xval_special_points(loop_spts), &
            (plot_kpoint(loop_i, idx_special_points(loop_spts)), loop_i=1, 3)
        end do
      else
        do loop_spts = 1, bands_num_spec_points
          if ((MOD(loop_spts, 2) .eq. 1) .and. &
              (kpath_print_first_point((loop_spts + 1)/2) .eqv. .false.)) cycle
          write (bndunit, '(a,3x,I10,3x,4f18.10)') &
            kpoint_path%labels(loop_spts), &
            idx_special_points(loop_spts), &
            xval_special_points(loop_spts), &
            (plot_kpoint(loop_i, idx_special_points(loop_spts)), loop_i=1, 3)
        end do
      end if
      close (bndunit)
    end if ! on_root
    !
    ! Cut H matrix in real-space
    !
    if (index(band_plot%mode, 'cut') .ne. 0) then
      call plot_cut_hr(real_space_ham, real_lattice, mp_grid, num_wann, &
                       wannier_centres_translated, stdout, error)
      if (allocated(error)) return
    end if
    !
    ! Interpolate the Hamiltonian at each kpoint
    !
    if (ws_region%use_ws_distance) then
      if (index(band_plot%mode, 's-k') .ne. 0) then
        call ws_translate_dist(ws_distance, ws_region, num_wann, &
                               wannier_data%centres, real_lattice, mp_grid, nrpts, &
                               irvec, error, comm, force_recompute=.true.)
        if (allocated(error)) return

      elseif (index(band_plot%mode, 'cut') .ne. 0) then
        call ws_translate_dist(ws_distance, ws_region, num_wann, &
                               wannier_data%centres, real_lattice, mp_grid, nrpts_cut, &
                               irvec_cut, error, comm, force_recompute=.true.)
        if (allocated(error)) return

      else
        call set_error_warn(error, 'Error in plot_interpolate bands: value of bands_plot_mode not recognised', comm)
        return
      end if
    end if
    if (on_root .and. print_output%timing_level > 2) then
      call io_stopwatch_start('plot: interpolate_bands: loop_kpoints', timer)
    end if
    ! [lp] the s-k and cut codes are very similar when use_ws_distance is used, a complete
    !      merge after this point is not impossible
    call comms_array_split(total_pts, counts, displs, comm)
    ! Don't worry about serial run, it is OK to call comms_array_split!
    do loop_kpt = displs(my_node_id) + 1, displs(my_node_id) + counts(my_node_id)
      ! do loop_kpt = 1, total_pts
      ham_kprm = cmplx_0
      !
      if (index(band_plot%mode, 's-k') .ne. 0) then
        do irpt = 1, nrpts
          ! [lp] Shift the WF to have the minimum distance IJ, see also ws_distance.F90
          if (ws_region%use_ws_distance) then
            do j = 1, num_wann
            do i = 1, num_wann
              do ideg = 1, ws_distance%ndeg(i, j, irpt)
                rdotk = twopi*dot_product(plot_kpoint(:, loop_kpt), &
                                          real(ws_distance%irdist(:, ideg, i, j, irpt), dp))
                fac = cmplx(cos(rdotk), sin(rdotk), dp) &
                      /real(ndegen(irpt)*ws_distance%ndeg(i, j, irpt), dp)
                ham_kprm(i, j) = ham_kprm(i, j) + fac*ham_r(i, j, irpt)
              end do
            end do
            end do
          else
            ! [lp] Original code, without IJ-dependent shift:
            rdotk = twopi*dot_product(plot_kpoint(:, loop_kpt), irvec(:, irpt))
            fac = cmplx(cos(rdotk), sin(rdotk), dp)/real(ndegen(irpt), dp)
            ham_kprm = ham_kprm + fac*ham_r(:, :, irpt)
          end if
        end do
        ! end of s-k mode
      elseif (index(band_plot%mode, 'cut') .ne. 0) then
        do irpt = 1, nrpts_cut
          ! [lp] Shift the WF to have the minimum distance IJ, see also ws_distance.F90
          if (ws_region%use_ws_distance) then
            do j = 1, num_wann
            do i = 1, num_wann
              do ideg = 1, ws_distance%ndeg(i, j, irpt)
                rdotk = twopi*dot_product(plot_kpoint(:, loop_kpt), &
                                          real(ws_distance%irdist(:, ideg, i, j, irpt), dp))
                fac = cmplx(cos(rdotk), sin(rdotk), dp)/real(ws_distance%ndeg(i, j, irpt), dp)
                ham_kprm(i, j) = ham_kprm(i, j) + fac*ham_r_cut(i, j, irpt)
              end do
            end do
            end do
            ! [lp] Original code, without IJ-dependent shift:
          else
            rdotk = twopi*dot_product(plot_kpoint(:, loop_kpt), irvec_cut(:, irpt))
            !~[aam] check divide by ndegen?
            fac = cmplx(cos(rdotk), sin(rdotk), dp)
            ham_kprm = ham_kprm + fac*ham_r_cut(:, :, irpt)
          end if ! end of use_ws_distance
        end do
      end if ! end of "cut" mode
      !
      ! Diagonalise H_k (->basis of eigenstates)
      do j = 1, num_wann
        do i = 1, j
          ham_pack(i + ((j - 1)*j)/2) = ham_kprm(i, j)
        end do
      end do
      call ZHPEVX('V', 'A', 'U', num_wann, ham_pack, 0.0_dp, 0.0_dp, 0, 0, -1.0_dp, &
                  nfound, eig_int(1, loop_kpt), U_int, num_wann, cwork, rwork, iwork, ifail, info)
      if (info < 0) then
        write (stdout, '(a,i3,a)') 'THE ', -info, ' ARGUMENT OF ZHPEVX HAD AN ILLEGAL VALUE'
        call set_error_fatal(error, 'Error in plot_interpolate_bands', comm)
        return
      end if
      if (info > 0) then
        write (stdout, '(i3,a)') info, ' EIGENVECTORS FAILED TO CONVERGE'
        call set_error_warn(error, 'Error in plot_interpolate_bands', comm)
        return
      end if
      ! Compute projection onto WF if requested
      if (allocated(band_plot%project)) then
      do loop_w = 1, num_wann
        do loop_p = 1, num_wann
          if (any(band_plot%project == loop_p)) then
            bands_proj(loop_w, loop_kpt) = bands_proj(loop_w, loop_kpt) + &
                                           abs(U_int(loop_p, loop_w))**2
          end if
        end do
      end do
      end if
      !
    end do

    call comms_reduce(eig_int(1, 1), num_wann*total_pts, 'SUM', error, comm)
    call comms_reduce(bands_proj(1, 1), num_wann*total_pts, 'SUM', error, comm)

    if (on_root .and. print_output%timing_level > 2) then
      call io_stopwatch_stop('plot: interpolate_bands: loop_kpoints', timer)
    end if
    !
    ! Interpolation Finished!
    ! Now we write plotting files
    !
    if (on_root) then
      emin = minval(eig_int) - 1.0_dp
      emax = maxval(eig_int) + 1.0_dp

      if (index(band_plot%format, 'gnu') > 0) then
        call plot_interpolate_gnuplot(band_plot, kpoint_path, bands_num_spec_points, num_wann)
      end if
      if (index(band_plot%format, 'xmgr') > 0) then
        call plot_interpolate_xmgrace(kpoint_path, bands_num_spec_points, num_wann, error)
      end if
      write (stdout, '(1x,a,f11.3,a)') &
        'Time to calculate interpolated band structure ', io_time() - time0, ' (sec)'
      write (stdout, *)
    end if

    if (allocated(ham_r_cut)) then
      deallocate (ham_r_cut, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error in deallocating ham_r_cut in plot_interpolate_bands', comm)
        return
      end if
    end if
    if (allocated(irvec_cut)) then
      deallocate (irvec_cut, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error in deallocating irvec_cut in plot_interpolate_bands', comm)
        return
      end if
    end if

    if (print_output%timing_level > 1) call io_stopwatch_stop('plot: interpolate_bands', timer)

    if (allocated(idx_special_points)) then
      deallocate (idx_special_points, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error in deallocating idx_special_points in &
            &plot_interpolate_bands', comm)
        return
      end if
    end if
    if (allocated(xval_special_points)) then
      deallocate (xval_special_points, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error in deallocating xval_special_points in &
            &plot_interpolate_bands', comm)
        return
      end if
    end if
    if (allocated(label_idx_special_points)) then
      deallocate (label_idx_special_points, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error in deallocating label_idx_special_points in &
            &plot_interpolate_bands', comm)
        return
      end if
    end if

  contains

    !================================================!
    subroutine plot_cut_hr(real_space_ham, real_lattice, mp_grid, num_wann, &
                           wannier_centres_translated, stdout, error)
      !================================================!
      !
      !!  In real-space picture, ham_r(j,i,k) is an interaction between
      !!  j_th WF at 0 and i_th WF at the lattice point translated
      !!  by matmul(real_lattice(:,:),irvec(:,k))
      !!  We truncate Hamiltonian matrix when
      !!   1) |  r_i(0) - r_j (R) | > dist_cutoff
      !!   2) |  ham_r(i,j,k)     | < hr_cutoff
      !!  while the condition 1) is essential to get a meaningful band structure,
      !!    ( dist_cutoff must be smaller than the shortest distance from
      !!      the center of W-S supercell to the points at the cell boundaries )
      !!  the condition 2) is optional.
      !!
      !!  limitation: when bands_plot_dim .ne. 3
      !!      one_dim_vec must be parallel to one of the cartesian axis
      !!      and perpendicular to the other two primitive lattice vectors
      !================================================!

      use w90_constants, only: dp, cmplx_0, eps8
      use w90_wannier90_types, only: band_plot_type, real_space_ham_type
      use w90_error, only: w90_error_type, set_error_alloc, set_error_warn

      implicit none

      ! arguments
      type(real_space_ham_type), intent(in) :: real_space_ham
      type(w90_error_type), allocatable, intent(out) :: error

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

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

      ! local variables
      integer :: nrpts_tmp
      integer :: one_dim_vec, two_dim_vec(2)
      integer :: i, j, n1, n2, n3, i1, i2, i3
      real(kind=dp), allocatable :: ham_r_tmp(:, :)
      real(kind=dp), allocatable :: shift_vec(:, :)
      real(kind=dp) :: dist_ij_vec(3)
      real(kind=dp) :: dist_vec(3)
      real(kind=dp) :: dist

      allocate (ham_r_tmp(num_wann, num_wann), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating ham_r_tmp in plot_cut_hr', comm)
        return
      end if

      irvec_max = maxval(irvec, DIM=2) + 1

      if (real_space_ham%system_dim .ne. 3) then
        ! Find one_dim_vec which is parallel to one_dim_dir
        ! two_dim_vec - the other two lattice vectors
        ! Along the confined directions, take only irvec=0
        j = 0
        do i = 1, 3
          if (abs(abs(real_lattice(real_space_ham%one_dim_dir, i)) &
                  - sqrt(dot_product(real_lattice(:, i), real_lattice(:, i)))) .lt. eps8) then
            one_dim_vec = i
            j = j + 1
          end if
        end do
        if (j .ne. 1) then
          call set_error_warn(error, 'Error: 1-d lattice vector not defined in plot_cut_hr', comm)
          return
        end if
        j = 0
        do i = 1, 3
          if (i .ne. one_dim_vec) then
            j = j + 1
            two_dim_vec(j) = i
          end if
        end do
        if (real_space_ham%system_dim .eq. 1) then
          irvec_max(two_dim_vec(1)) = 0
          irvec_max(two_dim_vec(2)) = 0
        end if
        if (real_space_ham%system_dim .eq. 2) irvec_max(one_dim_vec) = 0
      end if

      nrpts_cut = (2*irvec_max(1) + 1)*(2*irvec_max(2) + 1)*(2*irvec_max(3) + 1)
      allocate (ham_r_cut(num_wann, num_wann, nrpts_cut), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating ham_r_cut in plot_cut_hr', comm)
        return
      end if
      allocate (irvec_cut(3, nrpts_cut), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating irvec_cut in plot_cut_hr', comm)
        return
      end if
      allocate (shift_vec(3, nrpts_cut), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating shift_vec in plot_cut_hr', comm)
        return
      end if

      nrpts_tmp = 0
      do n1 = -irvec_max(1), irvec_max(1)
        do n2 = -irvec_max(2), irvec_max(2)
          loop_n3: do n3 = -irvec_max(3), irvec_max(3)
            do irpt = 1, nrpts
              i1 = mod(n1 - irvec(1, irpt), mp_grid(1))
              i2 = mod(n2 - irvec(2, irpt), mp_grid(2))
              i3 = mod(n3 - irvec(3, irpt), mp_grid(3))
              if (i1 .eq. 0 .and. i2 .eq. 0 .and. i3 .eq. 0) then
                nrpts_tmp = nrpts_tmp + 1
                ham_r_cut(:, :, nrpts_tmp) = ham_r(:, :, irpt)
                irvec_cut(1, nrpts_tmp) = n1
                irvec_cut(2, nrpts_tmp) = n2
                irvec_cut(3, nrpts_tmp) = n3
                cycle loop_n3
              end if
            end do
          end do loop_n3
        end do
      end do

      if (nrpts_tmp .ne. nrpts_cut) then
        write (stdout, '(a)') 'FAILED TO EXPAND ham_r'
        call set_error_warn(error, 'Error in plot_cut_hr', comm)
        return
      end if

      ! AAM: 29/10/2009 Bug fix thanks to Dr Shujun Hu, NIMS, Japan.
      do irpt = 1, nrpts_cut
        ! line below is incorrect for non-orthorhombic cells
        !shift_vec(:,irpt) = matmul(real_lattice(:,:),real(irvec_cut(:,irpt),dp))
        ! line below is the same as calculating
        ! matmul(transpose(real_lattice(:,:)),irvec_cut(:,irpt))
        shift_vec(:, irpt) = matmul(real(irvec_cut(:, irpt), dp), real_lattice(:, :))
      end do

      ! note: dist_cutoff_mode does not necessarily follow bands_plot_dim
      ! e.g. for 1-d system (bands_plot_dim=1) we can still apply 3-d dist_cutoff (dist_cutoff_mode=three_dim)
      if (index(real_space_ham%dist_cutoff_mode, 'one_dim') > 0) then
        do i = 1, num_wann
          do j = 1, num_wann
            dist_ij_vec(real_space_ham%one_dim_dir) = &
              wannier_centres_translated(real_space_ham%one_dim_dir, i) - &
              wannier_centres_translated(real_space_ham%one_dim_dir, j)
            do irpt = 1, nrpts_cut
              dist_vec(real_space_ham%one_dim_dir) = dist_ij_vec(real_space_ham%one_dim_dir) + &
                                                     shift_vec(real_space_ham%one_dim_dir, irpt)
              dist = abs(dist_vec(real_space_ham%one_dim_dir))
              if (dist .gt. real_space_ham%dist_cutoff) &
                ham_r_cut(j, i, irpt) = cmplx_0
            end do
          end do
        end do
      else if (index(real_space_ham%dist_cutoff_mode, 'two_dim') > 0) then
        do i = 1, num_wann
          do j = 1, num_wann
            dist_ij_vec(:) = wannier_centres_translated(:, i) - wannier_centres_translated(:, j)
            do irpt = 1, nrpts_cut
              dist_vec(:) = dist_ij_vec(:) + shift_vec(:, irpt)
              dist_vec(real_space_ham%one_dim_dir) = 0.0_dp
              dist = sqrt(dot_product(dist_vec, dist_vec))
              if (dist .gt. real_space_ham%dist_cutoff) &
                ham_r_cut(j, i, irpt) = cmplx_0
            end do
          end do
        end do
      else
        do i = 1, num_wann
          do j = 1, num_wann
            dist_ij_vec(:) = wannier_centres_translated(:, i) - wannier_centres_translated(:, j)
            do irpt = 1, nrpts_cut
              dist_vec(:) = dist_ij_vec(:) + shift_vec(:, irpt)
              dist = sqrt(dot_product(dist_vec, dist_vec))
              if (dist .gt. real_space_ham%dist_cutoff) &
                ham_r_cut(j, i, irpt) = cmplx_0
            end do
          end do
        end do
      end if

      do irpt = 1, nrpts_cut
        do i = 1, num_wann
          do j = 1, num_wann
            if (abs(ham_r_cut(j, i, irpt)) .lt. real_space_ham%hr_cutoff) &
              ham_r_cut(j, i, irpt) = cmplx_0
          end do
        end do
      end do

      write (stdout, '(/1x,a78)') repeat('-', 78)
      write (stdout, '(1x,4x,a)') &
        'Maximum absolute value of Real-space Hamiltonian at each lattice point'
      write (stdout, '(1x,8x,a62)') repeat('-', 62)
      write (stdout, '(1x,11x,a,11x,a)') 'Lattice point R', 'Max |H_ij(R)|'
      !  output maximum ham_r_cut at each lattice point
      do irpt = 1, nrpts_cut
        ham_r_tmp(:, :) = abs(ham_r_cut(:, :, irpt))
        write (stdout, '(1x,9x,3I5,9x,F12.6)') irvec_cut(:, irpt), maxval(ham_r_tmp)
      end do
      !
      return

    end subroutine plot_cut_hr

    !================================================!
    subroutine plot_interpolate_gnuplot(band_plot, kpoint_path, bands_num_spec_points, num_wann)
      !================================================!
      !
      !! Plots the interpolated band structure in gnuplot format
      !
      !================================================!

      use w90_constants, only: dp
      use w90_types, only: kpoint_path_type
      use w90_wannier90_types, only: band_plot_type

      implicit none

      ! arguments
      type(band_plot_type), intent(in) :: band_plot
      type(kpoint_path_type), intent(in) :: kpoint_path
      integer, intent(in) :: num_wann, bands_num_spec_points

      open (newunit=bndunit, file=trim(seedname)//'_band.dat', form='formatted')
      open (newunit=gnuunit, file=trim(seedname)//'_band.gnu', form='formatted')
      !
      ! Gnuplot format
      !
      do i = 1, num_wann
        do nkp = 1, total_pts
          if (allocated(band_plot%project)) then
            write (bndunit, '(3E16.8)') xval(nkp), eig_int(i, nkp), bands_proj(i, nkp)
          else
            write (bndunit, '(2E16.8)') xval(nkp), eig_int(i, nkp)
          end if
        end do
        write (bndunit, *) ' '
      end do
      close (bndunit)
      ! Axis labels
      if (kpoint_path%bands_kpt_explicit) then
        do i = 1, num_spts
          if (i > 1) then
            if (idx_special_points(i) .eq. idx_special_points(i - 1) + 1 .and. &
                kpoint_path%labels(label_idx_special_points(i)) .ne. kpoint_path%labels(label_idx_special_points(i - 1))) then
              ! If two different point indeces are consecutive, the label should be combined
              glabel(i) = TRIM(kpoint_path%labels(label_idx_special_points(i - 1)))//'|'// &
                          TRIM(kpoint_path%labels(label_idx_special_points(i)))
            end if
          else
            glabel(i) = TRIM(kpoint_path%labels(label_idx_special_points(i)))
          end if
        end do
      else
        glabel(1) = TRIM(kpoint_path%labels(1))
        do i = 2, num_paths
          if (kpoint_path%labels(2*(i - 1)) /= kpoint_path%labels(2*(i - 1) + 1)) then
            glabel(i) = TRIM(kpoint_path%labels(2*(i - 1)))//'|'// &
                        TRIM(kpoint_path%labels(2*(i - 1) + 1))
          else
            glabel(i) = TRIM(kpoint_path%labels(2*(i - 1)))
          end if
        end do
        glabel(num_paths + 1) = TRIM(kpoint_path%labels(2*num_paths))
      end if
      ! gnu file
      write (gnuunit, 701) xval(total_pts), emin, emax
      if (kpoint_path%bands_kpt_explicit) then
        do i = 1, num_spts
          write (gnuunit, 705) xval(idx_special_points(i)), emin, xval(idx_special_points(i)), emax
        end do
        write (gnuunit, 702, advance="no") TRIM(glabel(1)), 0.0_dp, &
          (TRIM(glabel(i)), xval(idx_special_points(i)), i=2, num_spts - 1)
        write (gnuunit, 703) TRIM(glabel(num_spts)), xval(idx_special_points(num_spts))
      else
        do i = 1, num_paths - 1
          write (gnuunit, 705) sum(kpath_len(1:i)), emin, sum(kpath_len(1:i)), emax
        end do
        write (gnuunit, 702, advance="no") TRIM(glabel(1)), 0.0_dp, &
          (TRIM(glabel(i + 1)), sum(kpath_len(1:i)), i=1, bands_num_spec_points/2 - 1)
        write (gnuunit, 703) TRIM(glabel(1 + bands_num_spec_points/2)), sum(kpath_len(:))
      end if
      write (gnuunit, *) 'plot ', '"'//trim(seedname)//'_band.dat', '"'
      close (gnuunit)

      if (allocated(band_plot%project)) then
        open (newunit=gnuunit, file=trim(seedname)//'_band_proj.gnu', form='formatted')
        write (gnuunit, '(a)') '#File to plot a colour-mapped Bandstructure'
        write (gnuunit, '(a)') 'set palette defined ( 0 "blue", 3 "green", 6 "yellow", 10 "red" )'
        write (gnuunit, '(a)') 'unset ztics'
        write (gnuunit, '(a)') 'unset key'
        write (gnuunit, '(a)') '# can make pointsize smaller (~0.5). Too small and nothing is &
          &printed'
        write (gnuunit, '(a)') 'set pointsize 0.8'
        write (gnuunit, '(a)') 'set grid xtics'
        write (gnuunit, '(a)') 'set view 0,0'
        write (gnuunit, '(a,f9.5,a)') 'set xrange [0:', xval(total_pts), ']'
        write (gnuunit, '(a,f9.5,a,f9.5,a)') 'set yrange [', emin, ':', emax, ']'
        if (kpoint_path%bands_kpt_explicit) then
          write (gnuunit, 702, advance="no") TRIM(glabel(1)), 0.0_dp, &
            (TRIM(glabel(i)), xval(idx_special_points(i)), i=2, num_spts - 1)
          write (gnuunit, 703) TRIM(glabel(num_spts)), xval(idx_special_points(num_spts))
        else
          write (gnuunit, 702, advance="no") glabel(1), 0.0_dp, &
            (glabel(i + 1), sum(kpath_len(1:i)), i=1, bands_num_spec_points/2 - 1)
          write (gnuunit, 703) glabel(1 + bands_num_spec_points/2), sum(kpath_len(:))
        end if

        write (gnuunit, '(a,a,a,a)') 'splot ', '"'//trim(seedname)//'_band.dat', '"', &
          ' u 1:2:3 w p pt 13 palette'
        write (gnuunit, '(a)') '#use the next lines to make a nice figure for a paper'
        write (gnuunit, '(a)') '#set term postscript enhanced eps color lw 0.5 dl 0.5'
        write (gnuunit, '(a)') '#set pointsize 0.275'
      end if
      !
701   format('set style data dots', /, 'set nokey', /, 'set xrange [0:', F8.5, ']', /, &
             'set yrange [', F9.5, ' :', F9.5, ']')
702   format('set xtics (', :20('"', A, '" ', F8.5, ','))
703   format(A, '" ', F8.5, ')')
705   format('set arrow from ', F8.5, ',', F10.5, ' to ', F8.5, ',', F10.5, ' nohead')

    end subroutine plot_interpolate_gnuplot

    !================================================!
    subroutine plot_interpolate_xmgrace(kpoint_path, bands_num_spec_points, num_wann, error)
      !================================================!
      !
      !! Plots the interpolated band structure in Xmgrace format
      !
      !================================================!

      use w90_io, only: io_date
      use w90_types, only: kpoint_path_type
      use w90_error, only: w90_error_type, set_error_warn

      implicit none

      type(kpoint_path_type), intent(in) :: kpoint_path
      type(w90_error_type), allocatable, intent(out) :: error

      integer, intent(in) :: num_wann, bands_num_spec_points
      character(len=9) :: cdate, ctime

      if (kpoint_path%bands_kpt_explicit) then
        call set_error_warn(error, "bands_kpt_explicit not implemented with xmgrace format", comm)
      end if

      call io_date(cdate, ctime)

      ! Axis labels

      ! Switch any G to Gamma

      do i = 1, bands_num_spec_points
        if (kpoint_path%labels(i) == 'G') then
          ctemp(i) = '\xG\0'
        else
          ctemp(i) = kpoint_path%labels(i)
        end if
      end do

      xlabel(1) = ' '//trim(ctemp(1))//' '
      do i = 2, num_paths
        if (ctemp(2*(i - 1)) /= ctemp(2*(i - 1) + 1)) then
          xlabel(i) = trim(ctemp(2*(i - 1)))//'|'//trim(ctemp(2*(i - 1) + 1))
        else
          xlabel(i) = ctemp(2*(i - 1))
        end if
      end do
      xlabel(num_paths + 1) = ctemp(bands_num_spec_points)

      open (newunit=gnuunit, file=trim(seedname)//'_band.agr', form='formatted')
      !
      ! Xmgrace format
      !
      write (gnuunit, '(a)') '# Grace project file                      '
      write (gnuunit, '(a)') '# written using Wannier90 www.wannier.org '
      write (gnuunit, '(a)') '@version 50113                            '
      write (gnuunit, '(a)') '@page size 792, 612                       '
      write (gnuunit, '(a)') '@page scroll 5%                           '
      write (gnuunit, '(a)') '@page inout 5%                            '
      write (gnuunit, '(a)') '@link page off                            '
      write (gnuunit, '(a)') '@timestamp def "'//cdate//' at '//ctime//'" '
      write (gnuunit, '(a)') '@with g0'
      write (gnuunit, '(a)') '@    world xmin 0.00'
      write (gnuunit, '(a,f10.5)') '@    world xmax ', xval(total_pts)
      write (gnuunit, '(a,f10.5)') '@    world ymin ', emin
      write (gnuunit, '(a,f10.5)') '@    world ymax ', emax
      write (gnuunit, '(a)') '@default linewidth 1.5'
      write (gnuunit, '(a)') '@    xaxis  tick on'
      write (gnuunit, '(a)') '@    xaxis  tick major 1'
      write (gnuunit, '(a)') '@    xaxis  tick major color 1'
      write (gnuunit, '(a)') '@    xaxis  tick major linestyle 3'
      write (gnuunit, '(a)') '@    xaxis  tick major grid on'
      write (gnuunit, '(a)') '@    xaxis  tick spec type both'
      write (gnuunit, '(a,i0)') '@    xaxis  tick spec ', 1 + bands_num_spec_points/2
      write (gnuunit, '(a)') '@    xaxis  tick major 0, 0'
      do i = 1, bands_num_spec_points/2
        write (gnuunit, '(a,i0,a,a)') '@    xaxis  ticklabel ', i - 1, ',', '"'// &
          trim(adjustl(xlabel(i)))//'"'
        write (gnuunit, '(a,i0,a,f10.5)') '@    xaxis  tick major ', i, ' , ', sum(kpath_len(1:i))
      end do
      write (gnuunit, '(a,i0,a)') '@    xaxis  ticklabel ', bands_num_spec_points/2 &
        , ',"'//trim(adjustl(xlabel(1 + bands_num_spec_points/2)))//'"'
      write (gnuunit, '(a)') '@    xaxis  ticklabel char size 1.500000'
      write (gnuunit, '(a)') '@    yaxis  tick major 10'
      write (gnuunit, '(a)') '@    yaxis  label "Band Energy (eV)"'
      write (gnuunit, '(a)') '@    yaxis  label char size 1.500000'
      write (gnuunit, '(a)') '@    yaxis  ticklabel char size 1.500000'
      do i = 1, num_wann
        write (gnuunit, '(a,i0,a)') '@    s', i - 1, ' line color 1'
      end do
      do i = 1, num_wann
        write (gnuunit, '(a,i0)') '@target G0.S', i - 1
        write (gnuunit, '(a)') '@type xy'
        do nkp = 1, total_pts
          write (gnuunit, '(2E16.8)') xval(nkp), eig_int(i, nkp)
        end do
        write (gnuunit, '(a,i0)') '&'
      end do

    end subroutine plot_interpolate_xmgrace

  end subroutine plot_interpolate_bands

  !================================================!
  subroutine plot_fermi_surface(fermi_energy_list, recip_lattice, fermi_surface_plot, num_wann, &
                                ham_r, irvec, ndegen, nrpts, timing_level, stdout, seedname, &
                                timer, error, comm)
    !================================================!
    !
    !!  Prepares a Xcrysden bxsf file to view the fermi surface
    !
    !================================================!

    use w90_constants, only: dp, cmplx_0, twopi
    use w90_io, only: io_date, io_time, io_stopwatch_start, io_stopwatch_stop
    use w90_wannier90_types, only: fermi_surface_plot_type
    use w90_error, only: w90_error_type, set_error_alloc, set_error_fatal, set_error_warn
    use w90_types, only: timer_list_type

    implicit none

    ! arguments
    type(fermi_surface_plot_type), intent(in)   :: fermi_surface_plot
    type(timer_list_type), intent(inout) :: timer
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm
    complex(kind=dp), intent(in) :: ham_r(:, :, :)
    character(len=50), intent(in)  :: seedname
    real(kind=dp), allocatable, intent(in)      :: fermi_energy_list(:)
    real(kind=dp), intent(in) :: recip_lattice(3, 3)
    integer, intent(in) :: irvec(:, :)
    integer, intent(in) :: ndegen(:)
    integer, intent(in) :: nrpts
    integer, intent(in) :: num_wann
    integer, intent(in) :: stdout
    integer, intent(in) :: timing_level

    ! local variables
    complex(kind=dp), allocatable :: ham_pack(:)
    complex(kind=dp)   :: fac
    complex(kind=dp), allocatable :: ham_kprm(:, :)
    complex(kind=dp), allocatable :: U_int(:, :)
    complex(kind=dp), allocatable :: cwork(:)
    real(kind=dp), allocatable    :: rwork(:)
    real(kind=dp), allocatable  :: eig_int(:, :)
    real(kind=dp)      :: rdotk, time0
    integer, allocatable :: iwork(:), ifail(:)
    integer              :: loop_x, loop_y, loop_z, INFO, ikp, i, j, ierr
    integer              :: irpt, nfound, npts_plot, loop_kpt, bxsf_unit
    integer              :: fermi_n
    character(len=9)     :: cdate, ctime

    if (timing_level > 1) call io_stopwatch_start('plot: fermi_surface', timer)
    time0 = io_time()
    write (stdout, *)
    write (stdout, '(1x,a)') 'Calculating Fermi surface'
    write (stdout, *)

    fermi_n = 0
    if (allocated(fermi_energy_list)) fermi_n = size(fermi_energy_list)
    if (fermi_n > 1) then
      call set_error_alloc(error, "Error in plot: nfermi>1. Set the fermi level " &
                           //"using the input parameter 'fermi_level'", comm)
      return
    end if

    allocate (ham_pack((num_wann*(num_wann + 1))/2), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating ham_pack plot_fermi_surface', comm)
      return
    end if
    allocate (ham_kprm(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating ham_kprm plot_fermi_surface', comm)
      return
    end if
    allocate (U_int(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating U_int in plot_fermi_surface', comm)
      return
    end if
    allocate (cwork(2*num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cwork in plot_fermi_surface', comm)
      return
    end if
    allocate (rwork(7*num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating rwork in plot_fermi_surface', comm)
      return
    end if
    allocate (iwork(5*num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating iwork in plot_fermi_surface', comm)
      return
    end if
    allocate (ifail(num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating ifail in plot_fermi_surface', comm)
      return
    end if

    npts_plot = (fermi_surface_plot%num_points + 1)**3
    allocate (eig_int(num_wann, npts_plot), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating eig_int in plot_fermi_surface', comm)
      return
    end if
    eig_int = 0.0_dp
    U_int = (0.0_dp, 0.0_dp)

    ikp = 0
    do loop_x = 1, fermi_surface_plot%num_points + 1
      do loop_y = 1, fermi_surface_plot%num_points + 1
        do loop_z = 1, fermi_surface_plot%num_points + 1
          ikp = ikp + 1

          ham_kprm = cmplx_0
          do irpt = 1, nrpts
            rdotk = twopi*real((loop_x - 1)*irvec(1, irpt) + &
                               (loop_y - 1)*irvec(2, irpt) + (loop_z - 1)* &
                               irvec(3, irpt), dp)/real(fermi_surface_plot%num_points, dp)
            fac = cmplx(cos(rdotk), sin(rdotk), dp)/real(ndegen(irpt), dp)
            ham_kprm = ham_kprm + fac*ham_r(:, :, irpt)
          end do
          ! Diagonalise H_k (->basis of eigenstates)
          do j = 1, num_wann
            do i = 1, j
              ham_pack(i + ((j - 1)*j)/2) = ham_kprm(i, j)
            end do
          end do
          call ZHPEVX('N', 'A', 'U', num_wann, ham_pack, 0.0_dp, 0.0_dp, 0, 0, -1.0_dp, &
                      nfound, eig_int(1, ikp), U_int, num_wann, cwork, rwork, iwork, ifail, info)
          if (info < 0) then
            write (stdout, '(a,i3,a)') 'THE ', -info, ' ARGUMENT OF ZHPEVX HAD AN ILLEGAL VALUE'
            call set_error_fatal(error, 'Error in plot_fermi_surface', comm)
            return
          end if
          if (info > 0) then
            write (stdout, '(i3,a)') info, ' EIGENVECTORS FAILED TO CONVERGE'
            call set_error_warn(error, 'Error in plot_fermi_surface', comm)
            return
          end if
        end do
      end do
    end do

    call io_date(cdate, ctime)
    open (newunit=bxsf_unit, FILE=trim(seedname)//'.bxsf', STATUS='UNKNOWN', FORM='FORMATTED')
    write (bxsf_unit, *) ' BEGIN_INFO'
    write (bxsf_unit, *) '      #'
    write (bxsf_unit, *) '      # this is a Band-XCRYSDEN-Structure-File'
    write (bxsf_unit, *) '      # for Fermi Surface Visualisation'
    write (bxsf_unit, *) '      #'
    write (bxsf_unit, *) '      # Generated by the Wannier90 code http://www.wannier.org'
    write (bxsf_unit, *) '      # On ', cdate, ' at ', ctime
    write (bxsf_unit, *) '      #'
    write (bxsf_unit, *) '      Fermi Energy:', fermi_energy_list(1)
    write (bxsf_unit, *) ' END_INFO'
    write (bxsf_unit, *)
    write (bxsf_unit, *) ' BEGIN_BLOCK_BANDGRID_3D'
    write (bxsf_unit, *) 'from_wannier_code'
    write (bxsf_unit, *) ' BEGIN_BANDGRID_3D_fermi'
    write (bxsf_unit, *) num_wann
    write (bxsf_unit, *) fermi_surface_plot%num_points + 1, fermi_surface_plot%num_points + 1, &
      fermi_surface_plot%num_points + 1
    write (bxsf_unit, *) '0.0 0.0 0.0'
    write (bxsf_unit, *) (recip_lattice(1, i), i=1, 3)
    write (bxsf_unit, *) (recip_lattice(2, i), i=1, 3)
    write (bxsf_unit, *) (recip_lattice(3, i), i=1, 3)
    do i = 1, num_wann
      write (bxsf_unit, *) 'BAND: ', i
      do loop_kpt = 1, npts_plot
        write (bxsf_unit, '(2E16.8)') eig_int(i, loop_kpt)
      end do
    end do
    write (bxsf_unit, *) 'END_BANDGRID_3D'
    write (bxsf_unit, *) ' END_BLOCK_BANDGRID_3D'
    close (bxsf_unit)

    write (stdout, '(1x,a,f11.3,a)') 'Time to calculate interpolated Fermi surface ', &
      io_time() - time0, ' (sec)'
    write (stdout, *)

    if (timing_level > 1) call io_stopwatch_stop('plot: fermi_surface', timer)

    return

  end subroutine plot_fermi_surface

  !================================================!
  subroutine plot_wannier(wannier_plot, wvfn_read, wannier_data, print_output, u_matrix_opt, &
                          dis_manifold, real_lattice, atom_data, kpt_latt, u_matrix, num_kpts, &
                          num_bands, num_wann, have_disentangled, spinors, bohr, stdout, seedname, &
                          timer, dist_k, error, comm)
    !================================================!
    !! Plot the WF in Xcrysden format
    !! based on code written by Michel Posternak
    !
    !================================================!

    use w90_constants, only: dp, cmplx_0, cmplx_i, twopi, cmplx_1
    use w90_io, only: io_date, io_stopwatch_start, io_stopwatch_stop
    use w90_types, only: wannier_data_type, atom_data_type, dis_manifold_type, print_output_type, &
                         timer_list_type
    use w90_wannier90_types, only: wvfn_read_type, wannier_plot_type
    use w90_comms, only: w90_comm_type
    use w90_error, only: w90_error_type, set_error_alloc, set_error_dealloc, set_error_file, &
                         set_error_warn

    implicit none

    ! arguments
    type(atom_data_type), intent(in) :: atom_data
    type(dis_manifold_type), intent(in) :: dis_manifold
    type(print_output_type), intent(in) :: print_output
    type(wannier_data_type), intent(in) :: wannier_data
    type(wannier_plot_type), intent(in) :: wannier_plot
    type(wvfn_read_type), intent(in) :: wvfn_read
    type(timer_list_type), intent(inout) :: timer
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    complex(kind=dp), intent(in) :: u_matrix(:, :, :)
    complex(kind=dp), intent(in) :: u_matrix_opt(:, :, :)

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

    integer, intent(in) :: dist_k(:)
    integer, intent(in) :: num_bands
    integer, intent(in) :: num_kpts
    integer, intent(in) :: num_wann
    integer, intent(in) :: stdout

    logical, intent(in) :: have_disentangled
    logical, intent(in) :: spinors

    character(len=50), intent(in)  :: seedname

    ! local variables
    real(kind=dp) :: tmax, tmaxx, x_0ang, y_0ang, z_0ang
    real(kind=dp) :: fxcry(3), dirl(3, 3), ratmax, ratio
    real(kind=dp) :: upspinor, dnspinor, upphase, dnphase

    complex(kind=dp), allocatable :: wann_func(:, :, :, :)
    complex(kind=dp), allocatable :: r_wvfn(:, :)
    complex(kind=dp), allocatable :: r_wvfn_tmp(:, :)
    complex(kind=dp), allocatable :: wann_func_nc(:, :, :, :, :) ! add the spinor dim.
    complex(kind=dp), allocatable :: r_wvfn_nc(:, :, :) ! add the spinor dim.
    complex(kind=dp), allocatable :: r_wvfn_tmp_nc(:, :, :) ! add the spinor dim.
    complex(kind=dp) :: catmp, wmod

    logical :: have_file, on_root

    integer :: num_nodes, my_node_id
    integer :: i, j, nsp, nat, nbnd, ierr
    integer :: loop_kpt, nk, ngx, ngy, ngz, nxx, nyy, nzz
    integer :: loop_b, nx, ny, nz, npoint, file_unit, loop_w, num_inc
    integer :: wann_plot_num

    character(len=11) :: wfnname
    character(len=60) :: wanxsf, wancube
    character(len=9)  :: cdate, ctime
    logical           :: inc_band(num_bands)

    ! Pre-contracted wavefunction: band sum done once per unit cell point
    complex(kind=dp), allocatable :: c_wvfn(:, :)
    complex(kind=dp), allocatable :: c_wvfn_nc(:, :, :)
    complex(kind=dp) :: uw, phase_yz
    integer :: ngrid

    ! Factored phase arrays
    complex(kind=dp), allocatable :: phase_x(:), phase_y(:), phase_z(:)
    integer :: nxx_lo, nxx_hi, nyy_lo, nyy_hi, nzz_lo, nzz_hi

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

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

    !
    if (print_output%timing_level > 1) call io_stopwatch_start('plot: wannier', timer)
    !
    associate (ngs => wannier_plot%supercell)
      !
      if (.not. spinors) then
        write (wfnname, 200) 1, wvfn_read%spin_channel
      else
        write (wfnname, 199) 1
      end if
      inquire (file=wfnname, exist=have_file)
      if (.not. have_file) then
        call set_error_file(error, 'plot_wannier: file '//wfnname//' not found', comm)
        return
      end if

      if (wvfn_read%formatted) then
        open (newunit=file_unit, file=wfnname, form='formatted', status='old', iostat=ierr)
      else
        open (newunit=file_unit, file=wfnname, form='unformatted', status='old', iostat=ierr)
      end if
      if (ierr /= 0) then
        call set_error_file(error, 'plot_wannier: could not open file '//wfnname, comm)
        return
      end if
      if (wvfn_read%formatted) then
        read (file_unit, *, iostat=ierr) ngx, ngy, ngz, nk, nbnd
      else
        read (file_unit, iostat=ierr) ngx, ngy, ngz, nk, nbnd
      end if
      close (file_unit)
      if (ierr /= 0) then
        call set_error_file(error, 'plot_wannier: error reading file '//wfnname, comm)
        return
      end if

200   format('UNK', i5.5, '.', i1)
199   format('UNK', i5.5, '.', 'NC')

      if (allocated(wannier_plot%list)) then
        wann_plot_num = size(wannier_plot%list)
      else
        wann_plot_num = 0
      end if

      ! Supercell grid bounds
      nxx_lo = -((ngs(1))/2)*ngx
      nxx_hi = ((ngs(1) + 1)/2)*ngx - 1
      nyy_lo = -((ngs(2))/2)*ngy
      nyy_hi = ((ngs(2) + 1)/2)*ngy - 1
      nzz_lo = -((ngs(3))/2)*ngz
      nzz_hi = ((ngs(3) + 1)/2)*ngz - 1
      ngrid = ngx*ngy*ngz

      allocate (wann_func(nxx_lo:nxx_hi, nyy_lo:nyy_hi, nzz_lo:nzz_hi, wann_plot_num), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating wann_func in plot_wannier', comm)
        return
      end if
      wann_func = cmplx_0
      if (spinors) then
        allocate (wann_func_nc(nxx_lo:nxx_hi, nyy_lo:nyy_hi, nzz_lo:nzz_hi, 2, wann_plot_num), &
                  stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error in allocating wann_func_nc in plot_wannier', comm)
          return
        end if
        wann_func_nc = cmplx_0
      end if
      if (.not. spinors) then
        if (have_disentangled) then
          allocate (r_wvfn_tmp(ngx*ngy*ngz, maxval(dis_manifold%ndimwin)), stat=ierr)
          if (ierr /= 0) then
            call set_error_alloc(error, 'Error in allocating r_wvfn_tmp in plot_wannier', comm)
            return
          end if
        end if
        allocate (r_wvfn(ngx*ngy*ngz, num_wann), stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error in allocating r_wvfn in plot_wannier', comm)
          return
        end if
      else
        if (have_disentangled) then
          allocate (r_wvfn_tmp_nc(ngx*ngy*ngz, maxval(dis_manifold%ndimwin), 2), stat=ierr)
          if (ierr /= 0) then
            call set_error_alloc(error, 'Error in allocating r_wvfn_tmp_nc in plot_wannier', comm)
            return
          end if
        end if
        allocate (r_wvfn_nc(ngx*ngy*ngz, num_wann, 2), stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error in allocating r_wvfn_nc in plot_wannier', comm)
          return
        end if
      end if

      if (.not. spinors) then
        allocate (c_wvfn(ngrid, wann_plot_num), stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error in allocating c_wvfn in plot_wannier', comm)
          return
        end if
      else
        allocate (c_wvfn_nc(ngrid, wann_plot_num, 2), stat=ierr)
        if (ierr /= 0) then
          call set_error_alloc(error, 'Error in allocating c_wvfn_nc in plot_wannier', comm)
          return
        end if
      end if

      allocate (phase_x(nxx_lo:nxx_hi), phase_y(nyy_lo:nyy_hi), phase_z(nzz_lo:nzz_hi), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating phase arrays in plot_wannier', comm)
        return
      end if

      call io_date(cdate, ctime)
      do loop_kpt = 1, num_kpts
        if (dist_k(loop_kpt) /= my_node_id) cycle

        inc_band = .true.
        num_inc = num_wann
        if (have_disentangled) then
          inc_band(:) = dis_manifold%lwindow(:, loop_kpt)
          num_inc = dis_manifold%ndimwin(loop_kpt)
        end if

        if (.not. spinors) then
          write (wfnname, 200) loop_kpt, wvfn_read%spin_channel
        else
          write (wfnname, 199) loop_kpt
        end if

        if (have_disentangled) then
          call plot_read_unk(wfnname, wvfn_read%formatted, spinors, inc_band, num_inc, &
                             num_bands, loop_kpt, ngx, ngy, ngz, r_wvfn_tmp, r_wvfn_tmp_nc, &
                             stdout, error, comm)
        else
          call plot_read_unk(wfnname, wvfn_read%formatted, spinors, inc_band, num_bands, &
                             num_bands, loop_kpt, ngx, ngy, ngz, r_wvfn, r_wvfn_nc, &
                             stdout, error, comm)
        end if
        if (allocated(error)) return

        if (have_disentangled) then
          if (.not. spinors) then
            r_wvfn = cmplx_0
            do loop_w = 1, num_wann
              do loop_b = 1, num_inc
                r_wvfn(:, loop_w) = r_wvfn(:, loop_w) + &
                                    u_matrix_opt(loop_b, loop_w, loop_kpt)*r_wvfn_tmp(:, loop_b)
              end do
            end do
          else
            r_wvfn_nc = cmplx_0
            do loop_w = 1, num_wann
              do loop_b = 1, num_inc
                call zaxpy(ngx*ngy*ngz, u_matrix_opt(loop_b, loop_w, loop_kpt), r_wvfn_tmp_nc(1, loop_b, 1), 1, & ! up-spinor
                           r_wvfn_nc(1, loop_w, 1), 1)
                call zaxpy(ngx*ngy*ngz, u_matrix_opt(loop_b, loop_w, loop_kpt), r_wvfn_tmp_nc(1, loop_b, 2), 1, & ! down-spinor
                           r_wvfn_nc(1, loop_w, 2), 1)
              end do
            end do
          end if
        end if

        ! Contract band index: c_wvfn(npoint, w) = sum_b u_matrix(b, list(w), k) * r_wvfn(npoint, b)
        if (.not. spinors) then
          c_wvfn = cmplx_0
        else
          c_wvfn_nc = cmplx_0
        end if
        do loop_b = 1, num_wann
          do loop_w = 1, wann_plot_num
            uw = u_matrix(loop_b, wannier_plot%list(loop_w), loop_kpt)
            do npoint = 1, ngrid
              if (.not. spinors) then
                c_wvfn(npoint, loop_w) = c_wvfn(npoint, loop_w) + uw*r_wvfn(npoint, loop_b)
              else
                c_wvfn_nc(npoint, loop_w, 1) = c_wvfn_nc(npoint, loop_w, 1) + uw*r_wvfn_nc(npoint, loop_b, 1)
                c_wvfn_nc(npoint, loop_w, 2) = c_wvfn_nc(npoint, loop_w, 2) + uw*r_wvfn_nc(npoint, loop_b, 2)
              end if
            end do
          end do
        end do

        ! Precompute factored phase arrays for this k-point
        do nxx = nxx_lo, nxx_hi
          phase_x(nxx) = exp(twopi*cmplx_i*kpt_latt(1, loop_kpt)*real(nxx - 1, dp)/real(ngx, dp))
        end do
        do nyy = nyy_lo, nyy_hi
          phase_y(nyy) = exp(twopi*cmplx_i*kpt_latt(2, loop_kpt)*real(nyy - 1, dp)/real(ngy, dp))
        end do
        do nzz = nzz_lo, nzz_hi
          phase_z(nzz) = exp(twopi*cmplx_i*kpt_latt(3, loop_kpt)*real(nzz - 1, dp)/real(ngz, dp))
        end do

        ! nxx, nyy, nzz span a parallelogram in the real space mesh, of side
        ! 2*nphir, and centered around the maximum of phi_i, nphimx(i, 1 2 3)
        !
        ! nx ny nz are the nxx nyy nzz brought back to the unit cell in
        ! which u_nk(r)=cptwrb(r,n)  is represented
        !
        ! There is a big performance improvement in looping over num_wann
        ! in the inner loop. This is poor memory access for wann_func and
        ! but the reduced number of operations wins out.
        do nzz = nzz_lo, nzz_hi
          nz = mod(nzz, ngz)
          if (nz .lt. 1) nz = nz + ngz
          do nyy = nyy_lo, nyy_hi
            ny = mod(nyy, ngy)
            if (ny .lt. 1) ny = ny + ngy
            phase_yz = phase_y(nyy)*phase_z(nzz)
            do nxx = nxx_lo, nxx_hi
              nx = mod(nxx, ngx)
              if (nx .lt. 1) nx = nx + ngx
              npoint = nx + (ny - 1)*ngx + (nz - 1)*ngy*ngx
              catmp = phase_x(nxx)*phase_yz
              do loop_w = 1, wann_plot_num
                if (.not. spinors) then
                  wann_func(nxx, nyy, nzz, loop_w) = &
                    wann_func(nxx, nyy, nzz, loop_w) + c_wvfn(npoint, loop_w)*catmp
                else
                  wann_func_nc(nxx, nyy, nzz, 1, loop_w) = &
                    wann_func_nc(nxx, nyy, nzz, 1, loop_w) + c_wvfn_nc(npoint, loop_w, 1)*catmp
                  wann_func_nc(nxx, nyy, nzz, 2, loop_w) = &
                    wann_func_nc(nxx, nyy, nzz, 2, loop_w) + c_wvfn_nc(npoint, loop_w, 2)*catmp
                end if
              end do
            end do
          end do
        end do

      end do !loop over kpoints

      if (spinors) then
        call comms_reduce(wann_func_nc(nxx_lo, nyy_lo, nzz_lo, 1, 1), &
                          size(wann_func_nc), 'SUM', error, comm)
      else
        call comms_reduce(wann_func(nxx_lo, nyy_lo, nzz_lo, 1), &
                          size(wann_func), 'SUM', error, comm)
      end if
      if (allocated(error)) return

      if (on_root) then
        if (spinors) then
          do nzz = nzz_lo, nzz_hi
            do nyy = nyy_lo, nyy_hi
              do nxx = nxx_lo, nxx_hi
                do loop_w = 1, wann_plot_num
                  upspinor = real(wann_func_nc(nxx, nyy, nzz, 1, loop_w)* &
                                  conjg(wann_func_nc(nxx, nyy, nzz, 1, loop_w)), dp)
                  dnspinor = real(wann_func_nc(nxx, nyy, nzz, 2, loop_w)* &
                                  conjg(wann_func_nc(nxx, nyy, nzz, 2, loop_w)), dp)
                  if (wannier_plot%spinor_phase) then
                    upphase = sign(1.0_dp, real(wann_func_nc(nxx, nyy, nzz, 1, loop_w), dp))
                    dnphase = sign(1.0_dp, real(wann_func_nc(nxx, nyy, nzz, 2, loop_w), dp))
                  else
                    upphase = 1.0_dp; dnphase = 1.0_dp
                  end if
                  select case (wannier_plot%spinor_mode)
                  case ('total')
                    wann_func(nxx, nyy, nzz, loop_w) = cmplx(sqrt(upspinor + dnspinor), 0.0_dp, dp)
                  case ('up')
                    wann_func(nxx, nyy, nzz, loop_w) = cmplx(sqrt(upspinor), 0.0_dp, dp)*upphase
                  case ('down')
                    wann_func(nxx, nyy, nzz, loop_w) = cmplx(sqrt(dnspinor), 0.0_dp, dp)*dnphase
                  case default
                    call set_error_file(error, 'plot_wannier: Invalid wannier_plot_spinor_mode ' &
                                        //trim(wannier_plot%spinor_mode), comm)
                    return
                  end select
                  wann_func(nxx, nyy, nzz, loop_w) = wann_func(nxx, nyy, nzz, loop_w)/real(num_kpts, dp)
                end do
              end do
            end do
          end do
        end if
      end if

      if (on_root) then
        if (.not. spinors) then !!!!! For spinor Wannier functions, the steps below are not necessary.
          ! fix the global phase by setting the wannier to
          ! be real at the point where it has max. modulus

          do loop_w = 1, wann_plot_num
            tmaxx = 0.0
            wmod = cmplx_1
            do nzz = nzz_lo, nzz_hi
              do nyy = nyy_lo, nyy_hi
                do nxx = nxx_lo, nxx_hi
                  wann_func(nxx, nyy, nzz, loop_w) = wann_func(nxx, nyy, nzz, loop_w)/real(num_kpts, dp)
                  tmax = real(wann_func(nxx, nyy, nzz, loop_w)* &
                              conjg(wann_func(nxx, nyy, nzz, loop_w)), dp)
                  if (tmax > tmaxx) then
                    tmaxx = tmax
                    wmod = wann_func(nxx, nyy, nzz, loop_w)
                  end if
                end do
              end do
            end do
            wmod = wmod/sqrt(real(wmod)**2 + aimag(wmod)**2)
            wann_func(:, :, :, loop_w) = wann_func(:, :, :, loop_w)/wmod
            write (stdout, '(6x,a,i4,7x,a,f11.6,SP,f11.6,"i")') "Wannier Function Num: ", &
              wannier_plot%list(loop_w), "Phase Factor = ", 1/wmod
          end do
          write (stdout, *) ''
          !
          ! Check the 'reality' of the WF
          !
          do loop_w = 1, wann_plot_num
            ratmax = 0.0_dp
            do nzz = nzz_lo, nzz_hi
              do nyy = nyy_lo, nyy_hi
                do nxx = nxx_lo, nxx_hi
                  if (abs(real(wann_func(nxx, nyy, nzz, loop_w), dp)) >= 0.01_dp) then
                    ratio = abs(aimag(wann_func(nxx, nyy, nzz, loop_w)))/ &
                            abs(real(wann_func(nxx, nyy, nzz, loop_w), dp))
                    ratmax = max(ratmax, ratio)
                  end if
                end do
              end do
            end do
            write (stdout, '(6x,a,i4,7x,a,f11.6)') 'Wannier Function Num: ', wannier_plot%list(loop_w), &
              'Maximum Im/Re Ratio = ', ratmax
          end do
        end if !!!!!
        write (stdout, *) ' '
        if (wannier_plot%format .eq. 'xcrysden') then
          call internal_xsf_format()
        elseif (wannier_plot%format .eq. 'cube') then
          call internal_cube_format(atom_data, wannier_data, real_lattice, bohr, error)
          if (allocated(error)) return
        else
          call set_error_warn(error, 'wannier_plot_format not recognised in plot_wannier', comm)
          return
        end if

        if (print_output%timing_level > 1) call io_stopwatch_stop('plot: wannier', timer)
      end if !on_root

    end associate

    if (allocated(c_wvfn)) then
      deallocate (c_wvfn, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error in deallocating c_wvfn in plot_wannier', comm)
        return
      end if
    end if
    if (allocated(c_wvfn_nc)) then
      deallocate (c_wvfn_nc, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error in deallocating c_wvfn_nc in plot_wannier', comm)
        return
      end if
    end if
    if (allocated(phase_x)) then
      deallocate (phase_x, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error in deallocating phase_x in plot_wannier', comm)
        return
      end if
    end if
    if (allocated(phase_y)) then
      deallocate (phase_y, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error in deallocating phase_y in plot_wannier', comm)
        return
      end if
    end if
    if (allocated(phase_z)) then
      deallocate (phase_z, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error in deallocating phase_z in plot_wannier', comm)
        return
      end if
    end if
    return

  contains

    !================================================!
    subroutine internal_cube_format(atom_data, wannier_data, real_lattice, bohr, error)
      !================================================!
      !
      !! Write WFs in Gaussian cube format.
      !
      !================================================!

      use w90_utility, only: utility_translate_home, utility_cart_to_frac, utility_frac_to_cart, &
                             utility_inverse_mat, utility_recip_lattice_base
      use w90_types, only: wannier_data_type, atom_data_type
      use w90_wannier90_types, only: wvfn_read_type
      use w90_error, only: w90_error_type, set_error_alloc, set_error_warn, set_error_dealloc

      implicit none

      type(wannier_data_type), intent(in) :: wannier_data
      type(atom_data_type), intent(in) :: atom_data
      type(w90_error_type), allocatable, intent(out) :: error
      real(kind=dp), intent(in) :: bohr

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

      real(kind=dp), allocatable :: wann_cube(:, :, :)
      real(kind=dp) :: inv_lattice(3, 3), recip_lattice(3, 3), pos_frac(3), volume
      real(kind=dp) :: rstart(3), rend(3), rlength(3), orig(3), dgrid(3)
      real(kind=dp) :: moda(3), modb(3)
      real(kind=dp) :: val_Q
      real(kind=dp) :: comf(3), wcf(3), diff(3), difc(3), dist
      integer :: ierr, iname, max_elements
      integer :: isp, iat, nzz, nyy, nxx, loop_w, qxx, qyy, qzz, wann_index
      integer :: istart(3), iend(3), ilength(3), nend
      integer :: ixx, iyy, izz
      integer :: irdiff(3), icount
      integer, allocatable :: atomic_Z(:)
      logical :: lmol, lcrys
      character(len=2), dimension(109) :: periodic_table = (/ &
           & 'h ', 'he', &
           & 'li', 'be', 'b ', 'c ', 'n ', 'o ', 'f ', 'ne', &
           & 'na', 'mg', 'al', 'si', 'p ', 's ', 'cl', 'ar', &
           & 'k ', 'ca', 'sc', 'ti', 'v ', 'cr', 'mn', 'fe', 'co', 'ni', 'cu', 'zn', 'ga', 'ge', 'as', 'se', 'br', 'kr', &
           & 'rb', 'sr', 'y ', 'zr', 'nb', 'mo', 'tc', 'ru', 'rh', 'pd', 'ag', 'cd', 'in', 'sn', 'sb', 'te', 'i ', 'xe', &
           & 'cs', 'ba', &
           & 'la', 'ce', 'pr', 'nd', 'pm', 'sm', 'eu', 'gd', 'tb', 'dy', 'ho', 'er', 'tm', 'yb', 'lu', &
           & 'hf', 'ta', 'w ', 're', 'os', 'ir', 'pt', 'au', 'hg', 'tl', 'pb', 'bi', 'po', 'at', 'rn', &
           & 'fr', 'ra', &
           & 'ac', 'th', 'pa', 'u ', 'np', 'pu', 'am', 'cm', 'bk', 'cf', 'es', 'fm', 'md', 'no', 'lr', &
           & 'rf', 'db', 'sg', 'bh', 'hs', 'mt'/)

      associate (ngs => wannier_plot%supercell)

        allocate (atomic_Z(atom_data%num_species), stat=ierr)
        if (ierr .ne. 0) then
          call set_error_alloc(error, 'Error: allocating atomic_Z in plot_wannier: cube_format', comm)
          return
        end if

        call utility_recip_lattice_base(real_lattice, recip_lattice, volume)
        lmol = .false.
        lcrys = .false.
        if (index(wannier_plot%mode, 'mol') > 0) lmol = .true.      ! molecule mode
        if (index(wannier_plot%mode, 'crys') > 0) lcrys = .true.    ! crystal mode

        val_Q = 1.0_dp ! dummy value for cube file

        ! Assign atomic numbers to species
        max_elements = size(periodic_table)
        atomic_Z(:) = 0
        do isp = 1, atom_data%num_species
          do iname = 1, max_elements
            if (atom_data%symbol(isp) .eq. periodic_table(iname)) then
              atomic_Z(isp) = iname
              exit
            end if
          end do
        end do

202     format(a, '_', i5.5, '.cube')

        ! Lengths of real and reciprocal lattice vectors
        do i = 1, 3
          moda(i) = sqrt(real_lattice(i, 1)*real_lattice(i, 1) &
                         + real_lattice(i, 2)*real_lattice(i, 2) &
                         + real_lattice(i, 3)*real_lattice(i, 3))
          modb(i) = sqrt(recip_lattice(i, 1)*recip_lattice(i, 1) &
                         + recip_lattice(i, 2)*recip_lattice(i, 2) &
                         + recip_lattice(i, 3)*recip_lattice(i, 3))
        end do

        ! Grid spacing in each lattice direction
        dgrid(1) = moda(1)/ngx; dgrid(2) = moda(2)/ngy; dgrid(3) = moda(3)/ngz

        ! Find "centre of mass" of atomic positions (in fractional coordinates)
        call utility_inverse_mat(real_lattice, inv_lattice)
        comf(:) = 0.0_dp
        do isp = 1, atom_data%num_species
          do iat = 1, atom_data%species_num(isp)
            call utility_cart_to_frac(atom_data%pos_cart(:, iat, isp), pos_frac, inv_lattice)
            comf(:) = comf(:) + pos_frac(:)
          end do
        end do
        comf(:) = comf(:)/atom_data%num_atoms

        ! Loop over WFs
        do loop_w = 1, wann_plot_num

          wann_index = wannier_plot%list(loop_w)
          write (wancube, 202) trim(seedname), wann_index

          ! Find start and end of cube wrt simulation (home) cell origin
          do i = 1, 3
            ! ... in terms of distance along each lattice vector direction i
            rstart(i) = (wannier_data%centres(1, wann_index)*recip_lattice(i, 1) &
                         + wannier_data%centres(2, wann_index)*recip_lattice(i, 2) &
                         + wannier_data%centres(3, wann_index)*recip_lattice(i, 3))*moda(i)/twopi &
                        - twopi*wannier_plot%radius/(moda(i)*modb(i))
            rend(i) = (wannier_data%centres(1, wann_index)*recip_lattice(i, 1) &
                       + wannier_data%centres(2, wann_index)*recip_lattice(i, 2) &
                       + wannier_data%centres(3, wann_index)*recip_lattice(i, 3))*moda(i)/twopi &
                      + twopi*wannier_plot%radius/(moda(i)*modb(i))
          end do

          rlength(:) = rend(:) - rstart(:)
          ilength(:) = ceiling(rlength(:)/dgrid(:))

          ! ... in terms of integer gridpoints along each lattice vector direction i
          istart(:) = floor(rstart(:)/dgrid(:)) + 1
          iend(:) = istart(:) + ilength(:) - 1

          ! Origin of cube wrt simulation (home) cell in Cartesian co-ordinates
          do i = 1, 3
            orig(i) = real(istart(1) - 1, dp)*dgrid(1)*real_lattice(1, i)/moda(1) &
                      + real(istart(2) - 1, dp)*dgrid(2)*real_lattice(2, i)/moda(2) &
                      + real(istart(3) - 1, dp)*dgrid(3)*real_lattice(3, i)/moda(3)
          end do

          ! Debugging
          if (print_output%iprint > 3) then
            write (stdout, '(a,i12)') 'loop_w  =', loop_w
            write (stdout, '(a,3f12.6)') 'comf    =', (comf(i), i=1, 3)
            write (stdout, '(a,3i12)') 'ngi     =', ngx, ngy, ngz
            write (stdout, '(a,3f12.6)') 'dgrid   =', (dgrid(i), i=1, 3)
            write (stdout, '(a,3f12.6)') 'rstart  =', (rstart(i), i=1, 3)
            write (stdout, '(a,3f12.6)') 'rend    =', (rend(i), i=1, 3)
            write (stdout, '(a,3f12.6)') 'rlength =', (rlength(i), i=1, 3)
            write (stdout, '(a,3i12)') 'istart  =', (istart(i), i=1, 3)
            write (stdout, '(a,3i12)') 'iend    =', (iend(i), i=1, 3)
            write (stdout, '(a,3i12)') 'ilength =', (ilength(i), i=1, 3)
            write (stdout, '(a,3f12.6)') 'orig    =', (orig(i), i=1, 3)
            write (stdout, '(a,3f12.6)') 'wann_cen=', (wannier_data%centres(i, wann_index), i=1, 3)
          end if

          allocate (wann_cube(1:ilength(1), 1:ilength(2), 1:ilength(3)), stat=ierr)
          if (ierr .ne. 0) then
            call set_error_alloc(error, 'Error: allocating wann_cube in plot_wannier: cube_format', comm)
            return
          end if

          ! initialise
          wann_cube = 0.0_dp

          do nzz = 1, ilength(3)
            qzz = nzz + istart(3) - 1
            izz = int((abs(qzz) - 1)/ngz)
!            if (qzz.lt.-ngz) qzz=qzz+izz*ngz
!            if (qzz.gt.(ngs(3)-1)*ngz-1) then
            if (qzz .lt. nzz_lo) qzz = qzz + izz*ngz
            if (qzz .gt. nzz_hi) then
              write (stdout, *) 'Error plotting WF cube. Try one of the following:'
              write (stdout, *) '   (1) increase wannier_plot_supercell;'
              write (stdout, *) '   (2) decrease wannier_plot_radius;'
              write (stdout, *) '   (3) set wannier_plot_format=xcrysden'
              call set_error_warn(error, 'Error plotting WF cube.', comm)
              return
            end if
            do nyy = 1, ilength(2)
              qyy = nyy + istart(2) - 1
              iyy = int((abs(qyy) - 1)/ngy)
!               if (qyy.lt.-ngy) qyy=qyy+iyy*ngy
!               if (qyy.gt.(ngs(2)-1)*ngy-1) then
              if (qyy .lt. nyy_lo) qyy = qyy + iyy*ngy
              if (qyy .gt. nyy_hi) then
                write (stdout, *) 'Error plotting WF cube. Try one of the following:'
                write (stdout, *) '   (1) increase wannier_plot_supercell;'
                write (stdout, *) '   (2) decrease wannier_plot_radius;'
                write (stdout, *) '   (3) set wannier_plot_format=xcrysden'
                call set_error_warn(error, 'Error plotting WF cube.', comm)
                return
              end if
              do nxx = 1, ilength(1)
                qxx = nxx + istart(1) - 1
                ixx = int((abs(qxx) - 1)/ngx)
!                  if (qxx.lt.-ngx) qxx=qxx+ixx*ngx
!                  if (qxx.gt.(ngs(1)-1)*ngx-1) then
                if (qxx .lt. nxx_lo) qxx = qxx + ixx*ngx
                if (qxx .gt. nxx_hi) then
                  write (stdout, *) 'Error plotting WF cube. Try one of the following:'
                  write (stdout, *) '   (1) increase wannier_plot_supercell;'
                  write (stdout, *) '   (2) decrease wannier_plot_radius;'
                  write (stdout, *) '   (3) set wannier_plot_format=xcrysden'
                  call set_error_warn(error, 'Error plotting WF cube.', comm)
                  return
                end if
                wann_cube(nxx, nyy, nzz) = real(wann_func(qxx, qyy, qzz, loop_w), dp)
              end do
            end do
          end do

          ! WF centre in fractional coordinates
          call utility_cart_to_frac(wannier_data%centres(:, wann_index), wcf(:), inv_lattice)

          ! The vector (in fractional coordinates) from WF centre to "centre of mass"
          diff(:) = comf(:) - wcf(:)

          ! Corresponding nearest cell vector
          irdiff(:) = nint(diff(:))

          if (print_output%iprint > 3) then
            write (stdout, '(a,3f12.6)') 'wcf     =', (wcf(i), i=1, 3)
            write (stdout, '(a,3f12.6)') 'diff    =', (diff(i), i=1, 3)
            write (stdout, '(a,3i12)') 'irdiff  =', (irdiff(i), i=1, 3)
          end if

          if (lmol) then ! In "molecule mode" translate origin of cube to bring it in coincidence with the atomic positions
            orig(:) = orig(:) + real(irdiff(1), kind=dp)*real_lattice(1, :) &
                      + real(irdiff(2), kind=dp)*real_lattice(2, :) &
                      + real(irdiff(3), kind=dp)*real_lattice(3, :)
            if (print_output%iprint > 3) write (stdout, '(a,3f12.6,/)') 'orig-new=', (orig(i), i=1, 3)
          else ! In "crystal mode" count number of atoms within a given radius of wannier centre
            icount = 0
            do isp = 1, atom_data%num_species
              do iat = 1, atom_data%species_num(isp)
                call utility_cart_to_frac(atom_data%pos_cart(:, iat, isp), pos_frac, inv_lattice)
                do nzz = -ngs(3)/2, (ngs(3) + 1)/2
                  do nyy = -ngs(2)/2, (ngs(2) + 1)/2
                    do nxx = -ngs(1)/2, (ngs(1) + 1)/2
                      diff(:) = pos_frac(:) - wcf(:) &
                                + (/real(nxx, kind=dp), real(nyy, kind=dp), real(nzz, kind=dp)/)
                      call utility_frac_to_cart(diff, difc, real_lattice)
                      dist = sqrt(difc(1)*difc(1) + difc(2)*difc(2) + difc(3)*difc(3))
                      if (dist .le. (wannier_plot%scale*wannier_plot%radius)) then
                        icount = icount + 1
                      end if
                    end do
                  end do
                end do
              end do ! iat
            end do ! isp
            if (print_output%iprint > 3) write (stdout, '(a,i12)') 'icount  =', icount
          end if

          ! Write cube file (everything in Bohr)
          open (newunit=file_unit, file=trim(wancube), form='formatted', status='unknown')
          ! First two lines are comments
          write (file_unit, *) '     Generated by Wannier90 code http://www.wannier.org'
          write (file_unit, *) '     On ', cdate, ' at ', ctime
          ! Number of atoms, origin of cube (Cartesians) wrt simulation (home) cell
          if (lmol) then
            write (file_unit, '(i4,3f13.5)') atom_data%num_atoms, orig(1)/bohr, orig(2)/bohr, orig(3)/bohr
          else
            write (file_unit, '(i4,3f13.5)') icount, orig(1)/bohr, orig(2)/bohr, orig(3)/bohr
          end if
          ! Number of grid points in each direction, lattice vector
          write (file_unit, '(i4,3f13.5)') ilength(1), real_lattice(1, 1)/(real(ngx, dp)*bohr), &
            real_lattice(1, 2)/(real(ngx, dp)*bohr), real_lattice(1, 3)/(real(ngx, dp)*bohr)
          write (file_unit, '(i4,3f13.5)') ilength(2), real_lattice(2, 1)/(real(ngy, dp)*bohr), &
            real_lattice(2, 2)/(real(ngy, dp)*bohr), real_lattice(2, 3)/(real(ngy, dp)*bohr)
          write (file_unit, '(i4,3f13.5)') ilength(3), real_lattice(3, 1)/(real(ngz, dp)*bohr), &
            real_lattice(3, 2)/(real(ngz, dp)*bohr), real_lattice(3, 3)/(real(ngz, dp)*bohr)

          ! Atomic number, valence charge, position of atom
!         do isp=1,num_species
!            do iat=1,atoms_species_num(isp)
!               write(file_unit,'(i4,4f13.5)') atomic_Z(isp), val_Q, (atoms_pos_cart(i,iat,isp)/bohr,i=1,3)
!            end do
!         end do

          do isp = 1, atom_data%num_species
            do iat = 1, atom_data%species_num(isp)
              if (lmol) then ! In "molecule mode", write atomic coordinates as they appear in input file
                write (file_unit, '(i4,4f13.5)') atomic_Z(isp), val_Q, (atom_data%pos_cart(i, iat, isp)/bohr, i=1, 3)
              else           ! In "crystal mode", write atoms in supercell within a given radius of Wannier centre
                call utility_cart_to_frac(atom_data%pos_cart(:, iat, isp), pos_frac, inv_lattice)
                do nzz = -ngs(3)/2, (ngs(3) + 1)/2
                  do nyy = -ngs(2)/2, (ngs(2) + 1)/2
                    do nxx = -ngs(1)/2, (ngs(1) + 1)/2
                      diff(:) = pos_frac(:) - wcf(:) &
                                + (/real(nxx, kind=dp), real(nyy, kind=dp), real(nzz, kind=dp)/)
                      call utility_frac_to_cart(diff, difc, real_lattice)
                      dist = sqrt(difc(1)*difc(1) + difc(2)*difc(2) + difc(3)*difc(3))
                      if (dist .le. (wannier_plot%scale*wannier_plot%radius)) then
                        diff(:) = pos_frac(:) &
                                  + (/real(nxx, kind=dp), real(nyy, kind=dp), real(nzz, kind=dp)/)
                        call utility_frac_to_cart(diff, difc, real_lattice)
                        write (file_unit, '(i4,4f13.5)') atomic_Z(isp), val_Q, (difc(i)/bohr, i=1, 3)
                      end if
                    end do
                  end do
                end do
              end if
            end do ! iat
          end do ! isp

          ! Volumetric data in batches of 6 values per line, 'z'-direction first.
          do nxx = 1, ilength(1)
            do nyy = 1, ilength(2)
              do nzz = 1, ilength(3), 6
                nend = min(nzz + 5, ilength(3))
                write (file_unit, '(6E13.5)') wann_cube(nxx, nyy, nzz:nend)
              end do
            end do
          end do

          deallocate (wann_cube, stat=ierr)
          if (ierr .ne. 0) then
            call set_error_dealloc(error, 'Error: deallocating wann_cube in plot_wannier: cube_format', comm)
            return
          end if

        end do

        deallocate (atomic_Z, stat=ierr)
        if (ierr .ne. 0) then
          call set_error_dealloc(error, 'Error: deallocating atomic_Z in plot_wannier: cube_format', comm)
          return
        end if

      end associate

      return

    end subroutine internal_cube_format

    subroutine internal_xsf_format()

      implicit none

201   format(a, '_', i5.5, '.xsf')

      associate (ngs => wannier_plot%supercell)

        ! this is to create the WF...xsf output, to be read by XCrySDen
        ! (coordinates + isosurfaces)

        x_0ang = -real(((ngs(1))/2)*ngx + 1, dp)/real(ngx, dp)*real_lattice(1, 1) - &
                 real(((ngs(2))/2)*ngy + 1, dp)/real(ngy, dp)*real_lattice(2, 1) - &
                 real(((ngs(3))/2)*ngz + 1, dp)/real(ngz, dp)*real_lattice(3, 1)
        y_0ang = -real(((ngs(1))/2)*ngx + 1, dp)/real(ngx, dp)*real_lattice(1, 2) - &
                 real(((ngs(2))/2)*ngy + 1, dp)/real(ngy, dp)*real_lattice(2, 2) - &
                 real(((ngs(3))/2)*ngz + 1, dp)/real(ngz, dp)*real_lattice(3, 2)
        z_0ang = -real(((ngs(1))/2)*ngx + 1, dp)/real(ngx, dp)*real_lattice(1, 3) - &
                 real(((ngs(2))/2)*ngy + 1, dp)/real(ngy, dp)*real_lattice(2, 3) - &
                 real(((ngs(3))/2)*ngz + 1, dp)/real(ngz, dp)*real_lattice(3, 3)

        fxcry(1) = real(ngs(1)*ngx - 1, dp)/real(ngx, dp)
        fxcry(2) = real(ngs(2)*ngy - 1, dp)/real(ngy, dp)
        fxcry(3) = real(ngs(3)*ngz - 1, dp)/real(ngz, dp)
        do j = 1, 3
          dirl(:, j) = fxcry(:)*real_lattice(:, j)
        end do

        do loop_b = 1, wann_plot_num

          write (wanxsf, 201) trim(seedname), wannier_plot%list(loop_b)

          open (newunit=file_unit, file=trim(wanxsf), form='formatted', status='unknown')
          write (file_unit, *) '      #'
          write (file_unit, *) '      # Generated by the Wannier90 code http://www.wannier.org'
          write (file_unit, *) '      # On ', cdate, ' at ', ctime
          write (file_unit, *) '      #'
          ! should pass this into the code
          if (index(wannier_plot%mode, 'mol') > 0) then
            write (file_unit, '("ATOMS")')
          else
            write (file_unit, '("CRYSTAL")')
            write (file_unit, '("PRIMVEC")')
            write (file_unit, '(3f12.7)') real_lattice(1, 1), real_lattice(1, 2), real_lattice(1, 3)
            write (file_unit, '(3f12.7)') real_lattice(2, 1), real_lattice(2, 2), real_lattice(2, 3)
            write (file_unit, '(3f12.7)') real_lattice(3, 1), real_lattice(3, 2), real_lattice(3, 3)
            write (file_unit, '("CONVVEC")')
            write (file_unit, '(3f12.7)') real_lattice(1, 1), real_lattice(1, 2), real_lattice(1, 3)
            write (file_unit, '(3f12.7)') real_lattice(2, 1), real_lattice(2, 2), real_lattice(2, 3)
            write (file_unit, '(3f12.7)') real_lattice(3, 1), real_lattice(3, 2), real_lattice(3, 3)
            write (file_unit, '("PRIMCOORD")')
            write (file_unit, '(i6,"  1")') atom_data%num_atoms
          end if
          do nsp = 1, atom_data%num_species
            do nat = 1, atom_data%species_num(nsp)
              write (file_unit, '(a2,3x,3f12.7)') atom_data%label(nsp), (atom_data%pos_cart(i, nat, nsp), i=1, 3)
            end do
          end do

          write (file_unit, '(/)')
          write (file_unit, '("BEGIN_BLOCK_DATAGRID_3D",/,"3D_field",/, "BEGIN_DATAGRID_3D_UNKNOWN")')
          write (file_unit, '(3i6)') ngs(1)*ngx, ngs(2)*ngy, ngs(3)*ngz
          write (file_unit, '(3f12.6)') x_0ang, y_0ang, z_0ang
          write (file_unit, '(3f12.7)') dirl(1, 1), dirl(1, 2), dirl(1, 3)
          write (file_unit, '(3f12.7)') dirl(2, 1), dirl(2, 2), dirl(2, 3)
          write (file_unit, '(3f12.7)') dirl(3, 1), dirl(3, 2), dirl(3, 3)
          write (file_unit, '(6e13.5)') &
            (((real(wann_func(nx, ny, nz, loop_b)), nx=nxx_lo, nxx_hi), &
              ny=nyy_lo, nyy_hi), nz=nzz_lo, nzz_hi)
          write (file_unit, '("END_DATAGRID_3D",/, "END_BLOCK_DATAGRID_3D")')
          close (file_unit)

        end do

      end associate
      return
    end subroutine internal_xsf_format

  end subroutine plot_wannier

  subroutine plot_read_unk(wfnname, formatted, spinors, inc_band, num_inc, num_bands, &
                           kpt, ngx, ngy, ngz, wvfn, wvfn_nc, stdout, error, comm)
    !! Read a single UNK file into `wvfn` (or `wvfn_nc` for spinors), keeping
    !! the first `num_inc` bands flagged in `inc_band` (bands not flagged are
    !! read and discarded).
    !!
    !! Failures set the error and return immediately. This is safe even though
    !! the caller distributes k-points over ranks: set_error_file synchronises
    !! the failing rank via comms_sync_error, and the other ranks pick the
    !! error up in the matching handshake at their next synchronised
    !! collective (e.g. the comms_reduce after the caller's k-point loop).

    use w90_constants, only: dp
    use w90_comms, only: w90_comm_type
    use w90_error, only: w90_error_type, set_error_file

    implicit none

    character(len=*), intent(in) :: wfnname
    !! Name of the UNK file to read
    logical, intent(in) :: formatted
    !! Whether the UNK file is formatted
    logical, intent(in) :: spinors
    !! Whether the wavefunctions are spinors
    logical, intent(in) :: inc_band(:)
    !! Bands to keep
    integer, intent(in) :: num_inc
    !! Number of bands to keep
    integer, intent(in) :: num_bands
    !! Total number of bands in the file
    integer, intent(in) :: kpt
    !! Expected k-point index (checked against the file header)
    integer, intent(in) :: ngx, ngy, ngz
    !! Expected grid dimensions (checked against the file header)
    complex(kind=dp), allocatable, intent(inout) :: wvfn(:, :)
    !! Destination, band as second index (caller-allocated; if .not. spinors)
    complex(kind=dp), intent(inout), allocatable :: wvfn_nc(:, :, :)
    !! Spinor destination, spin as third index (caller-allocated; if spinors)
    integer, intent(in) :: stdout
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer :: file_unit, ierr, ix, iy, iz, ik, nbnd, counter, loop_b, ngpts

    logical :: have_file

    ngpts = ngx*ngy*ngz

    ! A missing UNK file must be detected here rather than silently
    ! (re)created by the open below (status='old' forbids creation).
    inquire (file=wfnname, exist=have_file)
    if (.not. have_file) then
      call set_error_file(error, 'plot_wannier: file '//wfnname//' not found', comm)
      return
    end if

    if (formatted) then
      open (newunit=file_unit, file=wfnname, form='formatted', status='old', iostat=ierr)
    else
      open (newunit=file_unit, file=wfnname, form='unformatted', status='old', iostat=ierr)
    end if
    if (ierr /= 0) then
      call set_error_file(error, 'plot_wannier: could not open file '//wfnname, comm)
      return
    end if

    if (formatted) then
      read (file_unit, *, iostat=ierr) ix, iy, iz, ik, nbnd
    else
      read (file_unit, iostat=ierr) ix, iy, iz, ik, nbnd
    end if
    if (ierr /= 0) then
      call set_error_file(error, 'plot_wannier: error reading file '//wfnname, comm)
      close (file_unit)
      return
    end if

    if ((ix /= ngx) .or. (iy /= ngy) .or. (iz /= ngz) .or. (ik /= kpt)) then
      write (stdout, '(1x,a,a)') 'WARNING: mismatch in file', trim(wfnname)
      write (stdout, '(1x,5(a6,I5))') '   ix=', ix, '   iy=', iy, '   iz=', iz, '   ik=', ik, ' nbnd=', nbnd
      write (stdout, '(1x,5(a6,I5))') '  ngx=', ngx, '  ngy=', ngy, '  ngz=', ngz, '  kpt=', kpt, 'bands=', num_bands
      call set_error_file(error, 'plot_wannier: mismatch in file '//wfnname, comm)
      close (file_unit)
      return
    end if

    counter = 1
    do loop_b = 1, num_bands
      if (counter > num_inc) exit
      if (.not. spinors) then
        call plot_read_unk_band(wvfn(:, counter))
      else
        call plot_read_unk_band(wvfn_nc(:, counter, 1)) ! up-spinor
        if (ierr == 0) call plot_read_unk_band(wvfn_nc(:, counter, 2)) ! down-spinor
      end if
      if (ierr /= 0) then
        call set_error_file(error, 'plot_wannier: error reading file '//wfnname, comm)
        close (file_unit)
        return
      end if
      if (inc_band(loop_b)) counter = counter + 1
    end do

    close (file_unit)

  contains

    subroutine plot_read_unk_band(band)
      !! Read one band's worth of grid values, leaving any failure in `ierr`
      complex(kind=dp), intent(out) :: band(:)

      real(kind=dp) :: w_real, w_imag
      integer :: nx

      if (formatted) then
        do nx = 1, ngpts
          read (file_unit, *, iostat=ierr) w_real, w_imag
          if (ierr /= 0) return
          band(nx) = cmplx(w_real, w_imag, kind=dp)
        end do
      else
        read (file_unit, iostat=ierr) (band(nx), nx=1, ngpts)
      end if
    end subroutine plot_read_unk_band

  end subroutine plot_read_unk

  !================================================!
  subroutine plot_u_matrices(u_matrix_opt, u_matrix, kpt_latt, dis_manifold, &
                             have_disentangled, num_wann, num_kpts, num_bands, seedname, error, comm)
    !================================================!
    !
    !! Plot u_matrix and u_matrix_opt to textfiles in readable format
    !
    !================================================!

    use w90_constants, only: dp, cmplx_0, cmplx_1
    use w90_io, only: io_time, io_date
    use w90_types, only: dis_manifold_type
    use w90_error, only: w90_error_type, set_error_alloc, set_error_dealloc

    implicit none

    character(len=50), intent(in)  :: seedname
    complex(kind=dp), intent(in) :: u_matrix(:, :, :)
    complex(kind=dp), intent(in) :: u_matrix_opt(:, :, :)
    integer, intent(in) :: num_bands
    integer, intent(in) :: num_kpts
    integer, intent(in) :: num_wann
    logical, intent(in) :: have_disentangled
    real(kind=dp), intent(in) :: kpt_latt(:, :)
    type(dis_manifold_type), intent(in) :: dis_manifold
    type(w90_error_type), allocatable, intent(out) :: error ! no error condition here
    type(w90_comm_type), intent(in) :: comm

    complex(kind=dp), allocatable :: v_matrix(:, :)
    character(len=33)  :: header
    character(len=9)   :: cdate, ctime
    integer :: matunit, i, j, nkp, ierr

    call io_date(cdate, ctime)
    header = 'written on '//cdate//' at '//ctime

    ! u matrix
    open (newunit=matunit, file=trim(seedname)//'_u.mat', form='formatted')
    write (matunit, *) header
    write (matunit, *) num_kpts, num_wann, num_wann
    do nkp = 1, num_kpts
      write (matunit, *)
      write (matunit, '(f15.10,sp,f15.10,sp,f15.10)') kpt_latt(:, nkp)  ! shouldn't this really be e15.10?
      write (matunit, '(f15.10,sp,f15.10)') ((u_matrix(i, j, nkp), i=1, num_wann), j=1, num_wann)
    end do
    close (matunit)

    if (have_disentangled) then
      ! u_opt matrix
      open (newunit=matunit, file=trim(seedname)//'_u_dis.mat', form='formatted')
      write (matunit, *) header
      write (matunit, *) num_kpts, num_wann, num_bands
      do nkp = 1, num_kpts
        write (matunit, *)
        write (matunit, '(f15.10,sp,f15.10,sp,f15.10)') kpt_latt(:, nkp)
        write (matunit, '(f15.10,sp,f15.10)') ((u_matrix_opt(i, j, nkp), i=1, num_bands), j=1, num_wann)
      end do
      close (matunit)

      ! v matrix
      allocate (v_matrix(num_bands, num_wann), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating v_matrix in plot_u_matrices', comm)
        return
      end if
      open (newunit=matunit, file=trim(seedname)//'_v.mat', form='formatted')
      write (matunit, *) header
      write (matunit, *) num_kpts, num_wann, num_bands
      do nkp = 1, num_kpts
        call zgemm('n', 'n', num_bands, num_wann, num_wann, cmplx_1, u_matrix_opt(:, :, nkp), &
                   num_bands, u_matrix(:, :, nkp), num_wann, cmplx_0, v_matrix, num_bands)
        write (matunit, *)
        write (matunit, '(f15.10,sp,f15.10,sp,f15.10)') kpt_latt(:, nkp)
        write (matunit, '(f15.10,sp,f15.10)') ((v_matrix(i, j), i=1, num_bands), j=1, num_wann)
      end do
      close (matunit)
      deallocate (v_matrix, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating vmatrix in plot_u_matrices', comm)
        return
      end if
    end if
  end subroutine plot_u_matrices

  !================================================!
  subroutine plot_bvec(kmesh_info, num_kpts, seedname, error, comm)
    !================================================!
    !! June 2018: RM and SP
    !! Write to file the matrix elements of bvector and their weights
    !! This is used by EPW to compute the velocity.
    !! You need "write_bvec = .true." in your wannier input
    !!
    !================================================!

    use w90_io, only: io_date
    use w90_types, only: kmesh_info_type
    use w90_error, only: w90_error_type, set_error_file

    implicit none

    type(kmesh_info_type), intent(in) :: kmesh_info
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer :: nkp, nn, file_unit
    character(len=33) :: header
    character(len=9)  :: cdate, ctime

    integer, intent(in) :: num_kpts
    character(len=50), intent(in)  :: seedname

    call io_date(cdate, ctime)
    header = 'written on '//cdate//' at '//ctime

    open (newunit=file_unit, file=trim(seedname)//'.bvec', form='formatted', status='unknown', err=101)
    write (file_unit, *) header ! Date and time
    write (file_unit, *) num_kpts, kmesh_info%nntot
    do nkp = 1, num_kpts
      do nn = 1, kmesh_info%nntot
        write (file_unit, '(4F20.14)') kmesh_info%bk(:, nn, nkp), kmesh_info%wb(nn)
      end do
    end do
    close (file_unit)

    return

101 call set_error_file(error, 'Error: plot_bvec: problem opening file '//trim(seedname)//'.bvec', comm)
    return

  end subroutine plot_bvec

  !================================================!
  subroutine plot_write_rmn(kmesh_info, m_matrix, kpt_latt, irvec, nrpts, num_kpts, &
                            num_wann, seedname, dist_k, error, comm)
    !================================================!
    !
    !! Write out the matrix elements of r
    !
    !================================================!

    use w90_comms, only: comms_reduce, w90_comm_type, mpisize, mpirank
    use w90_constants, only: twopi, cmplx_i, dp
    use w90_error, only: w90_error_type, set_error_file
    use w90_io, only: io_date
    use w90_types, only: kmesh_info_type

    implicit none

    ! arguments
    type(kmesh_info_type), intent(in) :: kmesh_info
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: nrpts
    integer, intent(in) :: irvec(:, :)
    integer, intent(in) :: num_wann
    integer, intent(in) :: num_kpts
    integer, intent(in) :: dist_k(:) ! MPI k-point distribution
    real(kind=dp), intent(in)     :: kpt_latt(:, :)
    complex(kind=dp), intent(in)  :: m_matrix(:, :, :, :)
    character(len=50), intent(in) :: seedname

    ! local variables
    integer :: loop_rpt, m, n, nkp, ind, nn, file_unit, ierr
    integer :: my_node_id, nkp_rank
    ! nkp_rank is the rank-local kpoint index for m_matrix decomposition
    real(kind=dp) :: rdotk
    complex(kind=dp) :: fac
    complex(kind=dp) :: position(3)
    character(len=33) :: header
    character(len=9)  :: cdate, ctime
    logical :: on_root = .false.

    my_node_id = mpirank(comm)

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

    if (on_root) then
      open (newunit=file_unit, file=trim(seedname)//'_r.dat', form='formatted', status='unknown', &
            iostat=ierr)
      if (ierr /= 0) then
        call set_error_file(error, 'Error: hamiltonian_write_rmn: problem opening file '//trim(seedname)//'_r', comm)
        return
      end if

      call io_date(cdate, ctime)
      header = 'written on '//cdate//' at '//ctime
      write (file_unit, *) header ! Date and time
      write (file_unit, *) num_wann
      write (file_unit, *) nrpts
    end if

    do loop_rpt = 1, nrpts
      do m = 1, num_wann
        do n = 1, num_wann

          position(:) = 0._dp
          nkp_rank = 1
          do nkp = 1, num_kpts
            if (dist_k(nkp) /= my_node_id) cycle

            rdotk = twopi*dot_product(kpt_latt(:, nkp), real(irvec(:, loop_rpt), dp))
            fac = exp(-cmplx_i*rdotk)/real(num_kpts, dp)
            do ind = 1, 3
              do nn = 1, kmesh_info%nntot
                if (m .eq. n) then
                  ! For loop_rpt==rpt_origin, this reduces to
                  ! Eq.(32) of Marzari and Vanderbilt PRB 56,
                  ! 12847 (1997). Otherwise, is is Eq.(44)
                  ! Wang, Yates, Souza and Vanderbilt PRB 74,
                  ! 195118 (2006), modified according to
                  ! Eqs.(27,29) of Marzari and Vanderbilt
                  position(ind) = position(ind) - kmesh_info%wb(nn)*kmesh_info%bk(ind, nn, nkp) &
                                  *aimag(log(m_matrix(n, m, nn, nkp_rank)))*fac
                else
                  ! Eq.(44) Wang, Yates, Souza and Vanderbilt PRB 74, 195118 (2006)
                  position(ind) = position(ind) + cmplx_i*kmesh_info%wb(nn) &
                                  *kmesh_info%bk(ind, nn, nkp)*m_matrix(n, m, nn, nkp_rank)*fac
                end if
              end do
            end do
            nkp_rank = nkp_rank + 1
          end do ! global k list
          call comms_reduce(position(1), 3, 'SUM', error, comm)
          if (on_root) write (file_unit, '(5I5,6F12.6)') irvec(:, loop_rpt), n, m, position(:)
        end do
      end do
    end do

    if (on_root) close (file_unit)
  end subroutine plot_write_rmn

  !================================================!
  subroutine plot_write_r2mn(num_kpts, num_wann, kmesh_info, m_matrix, seedname, dist_k, error, comm)
    !================================================!
    !
    ! Write seedname.r2mn file
    !
    !================================================!

    use w90_comms, only: w90_comm_type
    use w90_constants, only: dp
    use w90_error, only: w90_error_type, set_error_file
    use w90_types, only: kmesh_info_type

    implicit none

    type(kmesh_info_type), intent(in) :: kmesh_info
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: num_kpts, num_wann
    integer, intent(in) :: dist_k(:) ! MPI k-point distribution
    complex(kind=dp), intent(in) :: m_matrix(:, :, :, :)
    character(len=50), intent(in)  :: seedname

    integer :: r2mnunit, nw1, nw2, nkp, nn, ierr
    integer :: nkp_rank, my_node_id
    real(kind=dp) :: r2ave_mn, delta
    logical :: on_root = .false.

    ! note that here I use formulas analogue to Eq. 23, and not to the
    ! shift-invariant Eq. 32 .

    my_node_id = mpirank(comm)

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

    if (on_root) then
      open (newunit=r2mnunit, file=trim(seedname)//'.r2mn', form='formatted', iostat=ierr)
      if (ierr /= 0) then
        call set_error_file(error, 'Error opening file '//trim(seedname)//'.r2mn in plot_write_r2mn', comm)
        return
      end if
    end if

    do nw1 = 1, num_wann
      do nw2 = 1, num_wann
        r2ave_mn = 0.0_dp
        delta = 0.0_dp
        if (nw1 .eq. nw2) delta = 1.0_dp
        nkp_rank = 1
        do nkp = 1, num_kpts
          if (dist_k(nkp) /= my_node_id) cycle

          do nn = 1, kmesh_info%nntot
            ! [GP-begin, Apr13, 2012: corrected sign inside "real"]
            r2ave_mn = r2ave_mn + kmesh_info%wb(nn)* &
                       (2.0_dp*delta - real(m_matrix(nw1, nw2, nn, nkp_rank) + &
                                            conjg(m_matrix(nw2, nw1, nn, nkp_rank)), dp))
          end do
          nkp_rank = nkp_rank + 1
        end do ! global k list
        call comms_reduce(r2ave_mn, 1, 'SUM', error, comm)
        r2ave_mn = r2ave_mn/real(num_kpts, dp)
        if (on_root) write (r2mnunit, '(2i6,f20.12)') nw1, nw2, r2ave_mn
      end do
    end do
    if (on_root) close (r2mnunit)
  end subroutine plot_write_r2mn

  !================================================!
  subroutine plot_calc_projection(num_bands, num_wann, num_kpts, u_matrix_opt, eigval, lwindow, &
                                  timing_level, iprint, stdout, timer)
    !================================================!
    !
    ! Calculates and writes the projection of each Wannier function
    ! on the original bands within the outer window.
    !
    !================================================!

    use w90_comms, only: w90_comm_type
    use w90_constants, only: dp
    use w90_error, only: w90_error_type
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_types, only: timer_list_type

    implicit none

    ! arguments
    type(timer_list_type), intent(inout) :: timer
    integer, intent(in) :: num_bands
    integer, intent(in) :: num_kpts
    integer, intent(in) :: num_wann
    integer, intent(in) :: stdout
    integer, intent(in) :: timing_level, iprint
    logical, intent(in) :: lwindow(:, :)
    complex(kind=dp), intent(in) :: u_matrix_opt(:, :, :)
    real(kind=dp), intent(in) :: eigval(:, :)

    ! local variables
    integer :: nw, nb, nkp, counter
    real(kind=dp) :: summ

    if (timing_level > 1 .and. iprint > 0) call io_stopwatch_start('wann: calc_projection', timer)

    if (iprint > 0) then
      write (stdout, '(/1x,a78)') repeat('-', 78)
      write (stdout, '(1x,9x,a)') &
        'Projection of Bands in Outer Window on all Wannier Functions'
      write (stdout, '(1x,8x,62a)') repeat('-', 62)
      write (stdout, '(1x,16x,a)') '   Kpt  Band      Eigval      |Projection|^2'
      write (stdout, '(1x,16x,a47)') repeat('-', 47)
    end if

    do nkp = 1, num_kpts
      counter = 0
      do nb = 1, num_bands
        if (lwindow(nb, nkp)) then
          counter = counter + 1
          summ = 0.0_dp
          do nw = 1, num_wann
            summ = summ + abs(u_matrix_opt(counter, nw, nkp))**2
          end do
          if (iprint > 0) write (stdout, '(1x,16x,i5,1x,i5,1x,f14.6,2x,f14.8)') &
            nkp, nb, eigval(nb, nkp), summ
        end if
      end do
    end do
    if (iprint > 0) write (stdout, '(1x,a78/)') repeat('-', 78)

    if (timing_level > 1 .and. iprint > 0) call io_stopwatch_stop('wann: calc_projection', timer)

    return

  end subroutine plot_calc_projection

  !================================================!
  subroutine plot_write_vdw_data(num_wann, wannier_data, real_lattice, u_matrix, u_matrix_opt, &
                                 have_disentangled, w90_system, error, comm, stdout, seedname)
    !================================================!
    !
    ! Write a file with Wannier centres, spreads and occupations for
    ! post-processing computation of vdW C6 coeffients.
    !
    ! Based on code written by Lampros Andrinopoulos.
    !================================================!

    use w90_constants, only: cmplx_0
    use w90_constants, only: dp
    use w90_error, only: w90_error_type, set_error_alloc, set_error_dealloc, set_error_input
    use w90_io, only: io_date
    use w90_types, only: wannier_data_type, w90_system_type
    use w90_utility, only: utility_translate_home

    implicit none

    type(wannier_data_type), intent(in) :: wannier_data
    type(w90_system_type), intent(in) :: w90_system
    type(w90_error_type), allocatable, intent(out)  :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: num_wann
    real(kind=dp), intent(in) :: real_lattice(3, 3)
    complex(kind=dp), intent(in) :: u_matrix(:, :, :)
    complex(kind=dp), intent(in) :: u_matrix_opt(:, :, :)
    integer, intent(in) :: stdout
    logical, intent(in) :: have_disentangled
    character(len=50), intent(in)  :: seedname

    integer          :: iw, vdw_unit, r, s, k, m, ierr, ndim
    real(kind=dp)    :: wc(3, num_wann)
    real(kind=dp)    :: ws(num_wann)
    complex(kind=dp), allocatable :: f_w(:, :), v_matrix(:, :) !f_w2(:,:)

    wc = wannier_data%centres
    ws = wannier_data%spreads

    ! translate Wannier centres to the home unit cell
    do iw = 1, num_wann
      call utility_translate_home(wc(:, iw), real_lattice)
    end do

    allocate (f_w(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating f_w in plot_write_vdw_data', comm)
      return
    end if

!~    ! aam: remove f_w2 at end
!~    allocate(f_w2(num_wann, num_wann),stat=ierr)
!~    if (ierr/=0) call io_error('Error in allocating f_w2 in plot_write_vdw_data')

    if (have_disentangled) then

      ! dimension of occupied subspace
      if (w90_system%num_valence_bands .le. 0) then
        call set_error_input(error, 'Please set num_valence_bands in seedname.win', comm)
        return
      end if

      ndim = w90_system%num_valence_bands

      allocate (v_matrix(ndim, num_wann), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating V_matrix in plot_write_vdw_data', comm)
        return
      end if

      ! aam: initialise
      f_w(:, :) = cmplx_0
      v_matrix(:, :) = cmplx_0
!~       f_w2(:,:) = cmplx_0

      ! aam: IN THE END ONLY NEED DIAGONAL PART, SO COULD SIMPLIFY...
      ! aam: calculate V = U_opt . U
      do s = 1, num_wann
        do k = 1, ndim
          do m = 1, num_wann
            v_matrix(k, s) = v_matrix(k, s) + u_matrix_opt(k, m, 1)*u_matrix(m, s, 1)
          end do
        end do
      end do

      ! aam: calculate f = V^dagger . V
      do r = 1, num_wann
        do s = 1, num_wann
          do k = 1, ndim
            f_w(r, s) = f_w(r, s) + v_matrix(k, s)*conjg(v_matrix(k, r))
          end do
        end do
      end do

!~       ! original formulation
!~       do r=1,num_wann
!~          do s=1,num_wann
!~             do nkp=1,num_kpts
!~                do k=1,ndimfroz(nkp)
!~                   do m=1,num_wann
!~                      do l=1,num_wann
!~                         f_w2(r,s) = f_w2(r,s) + &
!~                              u_matrix_opt(k,m,nkp) * u_matrix(m,s,nkp) * &
!~                              conjg(u_matrix_opt(k,l,nkp)) * conjg(u_matrix(l,r,nkp))
!~                      end do
!~                   end do
!~                end do
!~             end do
!~          end do
!~       end do

!~       ! test equivalence
!~       do r=1,num_wann
!~          do s=1,num_wann
!~             if (abs(real(f_w(r,s),dp)-real(f_w2(r,s),dp)).gt.eps6) then
!~                write(*,'(i6,i6,f16.10,f16.10)') r,s,real(f_w(r,s),dp),real(f_w2(r,s),dp)
!~             end if
!~             if (abs(aimag(f_w(r,s))-aimag(f_w2(r,s))).gt.eps6) then
!~                write(*,'(a,i6,i6,f16.10,f16.10)') 'Im: ',r,s,aimag(f_w(r,s)),aimag(f_w2(r,s))
!~             end if
!~          end do
!~       end do

    else
      ! for valence only, all occupancies are unity
      f_w(:, :) = 1.0_dp
    end if

    ! aam: write the seedname.vdw file directly here
    open (newunit=vdw_unit, file=trim(seedname)//'.vdw', action='write')
    if (have_disentangled) then
      write (vdw_unit, '(a)') 'disentangle T'
    else
      write (vdw_unit, '(a)') 'disentangle F'
    end if
    write (vdw_unit, '(a)') 'amalgamate F'
    write (vdw_unit, '(a,i3)') 'degeneracy', w90_system%num_elec_per_state
    write (vdw_unit, '(a)') 'num_frag 2'
    write (vdw_unit, '(a)') 'num_wann'
    write (vdw_unit, '(i3,1x,i3)') num_wann/2, num_wann/2
    write (vdw_unit, '(a)') 'tol_occ 0.9'
    write (vdw_unit, '(a)') 'pxyz'
    write (vdw_unit, '(a)') 'F F F'
    write (vdw_unit, '(a)') 'F F F'
    write (vdw_unit, '(a)') 'tol_dist 0.05'
    write (vdw_unit, '(a)') 'centres_spreads_occ'
    write (vdw_unit, '(a)') 'ang'
    do iw = 1, num_wann
      write (vdw_unit, '(4(f13.10,1x),1x,f11.8)') wc(1:3, iw), ws(iw), real(f_w(iw, iw))
    end do
    close (vdw_unit)

    write (stdout, '(/a/)') ' vdW data written to file '//trim(seedname)//'.vdw'

    if (have_disentangled) then
      deallocate (v_matrix, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error in deallocating v_matrix in plot_write_vdw_data', comm)
        return
      end if
    end if

    deallocate (f_w, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating f_w in plot_write_vdw_data', comm)
      return
    end if

    return

  end subroutine plot_write_vdw_data

  !================================================!
  subroutine plot_svd_omega_i(num_wann, num_kpts, kmesh_info, m_matrix, print_output, timer, &
                              dist_k, error, comm, stdout)
    !================================================!

    use w90_comms, only: w90_comm_type, mpirank, comms_allreduce
    use w90_constants, only: dp, cmplx_0
    use w90_error, only: w90_error_type, set_error_alloc, set_error_dealloc, set_error_fatal
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_types, only: kmesh_info_type, print_output_type, timer_list_type

    implicit none

    ! arguments
    complex(kind=dp), intent(in) :: m_matrix(:, :, :, :)
    integer, intent(in) :: dist_k(:)
    integer, intent(in) :: num_wann, num_kpts
    integer, intent(in) :: stdout
    type(kmesh_info_type), intent(in) :: kmesh_info
    type(print_output_type), intent(in) :: print_output
    type(timer_list_type), intent(inout) :: timer
    type(w90_comm_type), intent(in) :: comm
    type(w90_error_type), allocatable, intent(out) :: error

    ! local variables
    complex(kind=dp), allocatable :: cv1(:, :), cv2(:, :)
    complex(kind=dp), allocatable :: cw1(:), cw2(:)
    complex(kind=dp), allocatable :: cpad1(:)
    real(kind=dp), allocatable :: singvd(:)

    integer :: ierr, info, nkrank
    integer :: nkp, nn, nb, na, ind
    real(kind=dp) :: omt1, omt2, omt3

    nkrank = count(dist_k == mpirank(comm)) ! number k this rank, for dimensioning

    if (print_output%timing_level > 1 .and. print_output%iprint > 0) then
      call io_stopwatch_start('wann: svd_omega_i', timer)
    end if

    allocate (cw1(10*num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cw1 in plot_svd_omega_i', comm)
      return
    end if
    allocate (cw2(10*num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cw2 in plot_svd_omega_i', comm)
      return
    end if
    allocate (cv1(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cv1 in plot_svd_omega_i', comm)
      return
    end if
    allocate (cv2(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cv2 in plot_svd_omega_i', comm)
      return
    end if
    allocate (singvd(num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating singvd in plot_svd_omega_i', comm)
      return
    end if
    allocate (cpad1(num_wann*num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cpad1 in plot_svd_omega_i', comm)
      return
    end if

    cw1 = cmplx_0; cw2 = cmplx_0; cv1 = cmplx_0; cv2 = cmplx_0; cpad1 = cmplx_0
    singvd = 0.0_dp

    ! singular value decomposition
    omt1 = 0.0_dp; omt2 = 0.0_dp; omt3 = 0.0_dp
    do nkp = 1, nkrank
      do nn = 1, kmesh_info%nntot
        ind = 1
        do nb = 1, num_wann
          do na = 1, num_wann
            cpad1(ind) = m_matrix(na, nb, nn, nkp)
            ind = ind + 1
          end do
        end do
        call zgesvd('A', 'A', num_wann, num_wann, cpad1, num_wann, singvd, cv1, &
                    num_wann, cv2, num_wann, cw1, 10*num_wann, cw2, info)
        if (info .ne. 0) then
          call set_error_fatal(error, 'ERROR: Singular value decomp. zgesvd failed', comm)
          return
        end if

        do nb = 1, num_wann
          omt1 = omt1 + kmesh_info%wb(nn)*(1.0_dp - singvd(nb)**2)
          omt2 = omt2 - kmesh_info%wb(nn)*(2.0_dp*log(singvd(nb)))
          omt3 = omt3 + kmesh_info%wb(nn)*(acos(singvd(nb))**2)
        end do
      end do
    end do
    call comms_allreduce(omt1, 1, 'SUM', error, comm)
    call comms_allreduce(omt2, 1, 'SUM', error, comm)
    call comms_allreduce(omt3, 1, 'SUM', error, comm)
    omt1 = omt1/real(num_kpts, dp)
    omt2 = omt2/real(num_kpts, dp)
    omt3 = omt3/real(num_kpts, dp)
    if (print_output%iprint > 0) then
      write (stdout, *) ' '
      write (stdout, '(2x,a,f15.9,1x,a)') 'Omega Invariant:   1-s^2 = ', &
        omt1*print_output%lenconfac**2, '('//trim(print_output%length_unit)//'^2)'
      write (stdout, '(2x,a,f15.9,1x,a)') '                 -2log s = ', &
        omt2*print_output%lenconfac**2, '('//trim(print_output%length_unit)//'^2)'
      write (stdout, '(2x,a,f15.9,1x,a)') '                  acos^2 = ', &
        omt3*print_output%lenconfac**2, '('//trim(print_output%length_unit)//'^2)'
    end if

    deallocate (cpad1, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cpad1 in plot_svd_omega_i', comm)
      return
    end if
    deallocate (singvd, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating singvd in plot_svd_omega_i', comm)
      return
    end if
    deallocate (cv2, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cv2 in plot_svd_omega_i', comm)
      return
    end if
    deallocate (cv1, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cv1 in plot_svd_omega_i', comm)
      return
    end if
    deallocate (cw2, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cw2 in plot_svd_omega_i', comm)
      return
    end if
    deallocate (cw1, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cw1 in plot_svd_omega_i', comm)
      return
    end if

    if (print_output%timing_level > 1 .and. print_output%iprint > 0) then
      call io_stopwatch_stop('wann: svd_omega_i', timer)
    end if
  end subroutine plot_svd_omega_i

  !================================================!
  subroutine plot_write_xyz(translate_home_cell, num_wann, wannier_centres, real_lattice, &
                            atom_data, print_output, error, comm, stdout, seedname)
    !================================================!
    !
    ! Write xyz file with Wannier centres
    !
    !================================================!

    use w90_io, only: io_date
    use w90_constants, only: dp
    use w90_utility, only: utility_translate_home
    use w90_error, only: w90_error_type, set_error_file
    use w90_types, only: atom_data_type, print_output_type

    implicit none

    type(atom_data_type), intent(in) :: atom_data
    type(print_output_type), intent(in) :: print_output
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

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

    integer :: iw, ind, xyz_unit, nsp, nat, ierr
    character(len=9) :: cdate, ctime
    real(kind=dp) :: wc(3, num_wann)

    wc = wannier_centres

    if (translate_home_cell) then
      do iw = 1, num_wann
        call utility_translate_home(wc(:, iw), real_lattice)
      end do
    end if

    if (print_output%iprint > 2) then
      write (stdout, '(1x,a)') 'Final centres (translated to home cell for writing xyz file)'
      do iw = 1, num_wann
        write (stdout, '(2x, "WF centre", i5, 2x, "(", f10.6, ",", f10.6, ",", f10.6, " )")') &
          iw, (wc(ind, iw)*print_output%lenconfac, ind=1, 3)
      end do
      write (stdout, '(1x,a78)') repeat('-', 78)
      write (stdout, *)
    end if

    open (newunit=xyz_unit, file=trim(seedname)//'_centres.xyz', form='formatted', iostat=ierr)
    if (ierr /= 0) then
      call set_error_file(error, 'Error opening file '//trim(seedname)//'_centres.xyz in wann_write_xyz', comm)
      return
    end if
    write (xyz_unit, '(i6)') num_wann + atom_data%num_atoms
    call io_date(cdate, ctime)
    write (xyz_unit, *) 'Wannier centres, written by Wannier90 on'//cdate//' at '//ctime
    do iw = 1, num_wann
      write (xyz_unit, '("X",6x,3(f14.8,3x))') (wc(ind, iw), ind=1, 3)
    end do
    do nsp = 1, atom_data%num_species
      do nat = 1, atom_data%species_num(nsp)
        write (xyz_unit, '(a2,5x,3(f14.8,3x))') atom_data%label(nsp), atom_data%pos_cart(:, nat, nsp)
      end do
    end do
    close (xyz_unit)

    write (stdout, '(/a)') ' Wannier centres written to file '//trim(seedname)//'_centres.xyz'

    return

  end subroutine plot_write_xyz

end module w90_plot_mod
