disentangle.F90 Source File


This file depends on

sourcefile~~disentangle.f90~~EfferentGraph sourcefile~disentangle.f90 disentangle.F90 sourcefile~comms.f90 comms.F90 sourcefile~disentangle.f90->sourcefile~comms.f90 sourcefile~constants.f90 constants.F90 sourcefile~disentangle.f90->sourcefile~constants.f90 sourcefile~error.f90 error.F90 sourcefile~disentangle.f90->sourcefile~error.f90 sourcefile~io.f90 io.F90 sourcefile~disentangle.f90->sourcefile~io.f90 sourcefile~sitesym.f90 sitesym.F90 sourcefile~disentangle.f90->sourcefile~sitesym.f90 sourcefile~types.f90 types.F90 sourcefile~disentangle.f90->sourcefile~types.f90 sourcefile~utility.f90 utility.F90 sourcefile~disentangle.f90->sourcefile~utility.f90 sourcefile~wannier90_types.f90 wannier90_types.F90 sourcefile~disentangle.f90->sourcefile~wannier90_types.f90 sourcefile~comms.f90->sourcefile~constants.f90 sourcefile~error_base.f90 error_base.F90 sourcefile~comms.f90->sourcefile~error_base.f90 sourcefile~error.f90->sourcefile~comms.f90 sourcefile~error.f90->sourcefile~error_base.f90 sourcefile~io.f90->sourcefile~comms.f90 sourcefile~io.f90->sourcefile~constants.f90 sourcefile~io.f90->sourcefile~types.f90 sourcefile~io.f90->sourcefile~error_base.f90 sourcefile~sitesym.f90->sourcefile~comms.f90 sourcefile~sitesym.f90->sourcefile~constants.f90 sourcefile~sitesym.f90->sourcefile~error.f90 sourcefile~sitesym.f90->sourcefile~utility.f90 sourcefile~sitesym.f90->sourcefile~wannier90_types.f90 sourcefile~types.f90->sourcefile~constants.f90 sourcefile~utility.f90->sourcefile~comms.f90 sourcefile~utility.f90->sourcefile~constants.f90 sourcefile~utility.f90->sourcefile~error.f90 sourcefile~wannier90_types.f90->sourcefile~constants.f90

Files dependent on this one

sourcefile~~disentangle.f90~~AfferentGraph sourcefile~disentangle.f90 disentangle.F90 sourcefile~library_interface.f90 library_interface.F90 sourcefile~library_interface.f90->sourcefile~disentangle.f90 sourcefile~c_interface.f90 c_interface.F90 sourcefile~c_interface.f90->sourcefile~library_interface.f90 sourcefile~library_extra.f90 library_extra.F90 sourcefile~library_extra.f90->sourcefile~library_interface.f90 sourcefile~wannier_prog.f90 wannier_prog.F90 sourcefile~wannier_prog.f90->sourcefile~library_interface.f90 sourcefile~wannier_prog.f90->sourcefile~library_extra.f90

Source Code

!-*- 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_disentangle: extract subspace from entangled bands    !
!                                                            !
!------------------------------------------------------------!

module w90_disentangle_mod
  !! This module contains the core routines to extract an optimal
  !! subspace from a set of entangled bands.

  use w90_constants, only: dp

  implicit none

  public :: dis_main
  public :: setup_m_loc
  public :: dis_otsu_thresholds

  ! histogram controls for dis_proj_auto; compile-time constants for now,
  ! thread into the input parser if user control is ever wanted
  real(kind=dp), parameter :: otsu_lower_bound = 0.0_dp
  real(kind=dp), parameter :: otsu_upper_bound = 1.0_dp
  integer, parameter :: otsu_nbins = 64

contains
  !================================================!

  subroutine dis_main(dis_control, dis_spheres, dis_manifold, kmesh_info, kpt_latt, sitesym, &
                      print_output, m_matrix_orig_local, u_matrix, u_matrix_opt, eigval, &
                      real_lattice, omega_invariant, num_bands, num_kpts, num_wann, gamma_only, &
                      lsitesymmetry, stdout, timer, dist_k, error, comm)
    !================================================!
    !
    !! Main disentanglement routine
    !
    !================================================!
    use w90_comms, only: comms_bcast, w90_comm_type, mpirank
    use w90_constants, only: dp, cmplx_0, cmplx_1
    use w90_error
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_sitesym, only: sitesym_replace_d_matrix_band, sitesym_symmetrize_u_matrix, &
                           sitesym_symmetrize_zmatrix, sitesym_dis_extract_symmetry
    use w90_types, only: dis_manifold_type, kmesh_info_type, print_output_type, timer_list_type
    use w90_utility, only: utility_recip_lattice_base
    use w90_wannier90_types, only: dis_control_type, dis_spheres_type, sitesym_type

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

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

    real(kind=dp), pointer, intent(in) :: eigval(:, :) ! (num_bands, num_kpts)
    real(kind=dp), intent(in) :: kpt_latt(:, :)
    real(kind=dp), intent(inout) :: omega_invariant
    real(kind=dp), intent(in) :: real_lattice(3, 3)

    complex(kind=dp), intent(inout) :: u_matrix(:, :, :) ! (num_wann, num_wann, num_kpts)
    complex(kind=dp), intent(inout) :: u_matrix_opt(:, :, :) ! (num_bands, num_wann, num_kpts)
    complex(kind=dp), intent(inout) :: m_matrix_orig_local(:, :, :, :) ! this is the only "m matrix" here now

    type(dis_control_type), intent(inout)  :: dis_control
    type(dis_manifold_type), intent(inout) :: dis_manifold
    type(dis_spheres_type), intent(in) :: dis_spheres
    type(kmesh_info_type), intent(in) :: kmesh_info
    type(print_output_type), intent(in) :: print_output
    type(sitesym_type), intent(inout) :: sitesym
    type(w90_comm_type), intent(in) :: comm
    type(timer_list_type), intent(inout) :: timer
    type(w90_error_type), allocatable, intent(out) :: error

    ! internal variables
    real(kind=dp) :: recip_lattice(3, 3), volume
    integer :: nkp, nkp2, nn, j, ierr, nkp_global
    logical :: linner                         !! Is there a frozen window
    logical :: lfrozen(num_bands, num_kpts)   !! true if the i-th band inside outer window is frozen
    integer :: ndimfroz(num_kpts)             !! number of frozen bands at nkp-th k point
    integer :: indxfroz(num_bands, num_kpts)  !! number of bands inside outer window at nkp-th k point
    integer :: indxnfroz(num_bands, num_kpts) !! outer-window band index for the i-th non-frozen state
    complex(kind=dp), allocatable :: a_matrix(:, :, :) ! (num_bands, num_wann, num_kpts)
    !! (equals 1 if it is the bottom of outer window)

    real(kind=dp), allocatable :: eigval_opt(:, :)  !! At input it contains a large set of eigenvalues. At
    !! it is slimmed down to contain only those inside the energy window.

    complex(kind=dp), allocatable :: cwb(:, :), cww(:, :)

    ! pllel setup
    integer :: nkrank, ikg, ikl, my_node_id
    integer, allocatable :: global_k(:)
    logical :: on_root = .false.

    my_node_id = mpirank(comm)
    on_root = (my_node_id == 0)
    nkrank = count(dist_k == my_node_id) ! this routine must proceed also in the case of zero k-points this rank, to ensure collective communications are matched

    allocate (a_matrix(num_bands, num_wann, num_kpts), stat=ierr) ! a_matrix is local to disentangle()
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating a_matrix in dis_main', comm)
      return
    end if
    a_matrix = u_matrix_opt ! initial projections are passed to this routine via u_matrix_opt

    allocate (global_k(nkrank), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating global_k in dis_main', comm)
      return
    end if
    global_k = huge(1); ikl = 1
    do ikg = 1, num_kpts
      if (dist_k(ikg) == my_node_id) then
        global_k(ikl) = ikg
        ikl = ikl + 1
      end if
    end do

    if (print_output%timing_level > 0) call io_stopwatch_start('dis: main', timer)

    call utility_recip_lattice_base(real_lattice, recip_lattice, volume)

    if (print_output%iprint > 0) write (stdout, '(/1x,a)') &
      '*------------------------------- DISENTANGLE --------------------------------*'

    ! Allocate arrays
    allocate (eigval_opt(num_bands, num_kpts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating eigval_opt in dis_main', comm)
      return
    end if
    eigval_opt(1:num_bands, 1:num_kpts) = eigval(1:num_bands, 1:num_kpts)

    ! Set up energy windows
    if (dis_manifold%frozen_proj) then
      call dis_windows_proj(dis_manifold, eigval_opt, a_matrix, m_matrix_orig_local, &
                            indxfroz, indxnfroz, ndimfroz, dis_manifold%nfirstwin, &
                            print_output%iprint, kmesh_info%nnlist, kmesh_info%nntot, num_bands, &
                            num_kpts, num_wann, print_output%timing_level, lfrozen, linner, &
                            on_root, stdout, dist_k, global_k, my_node_id, timer, error, comm)
      if (allocated(error)) return
    else
      call dis_windows(dis_spheres, dis_manifold, eigval_opt, kpt_latt, recip_lattice, indxfroz, &
                       indxnfroz, ndimfroz, print_output%iprint, num_bands, num_kpts, num_wann, &
                       print_output%timing_level, lfrozen, linner, on_root, stdout, timer, error, comm)
      if (allocated(error)) return
    end if

    ! Construct the unitarized projection
    call dis_project(a_matrix, u_matrix_opt, dis_manifold%ndimwin, dis_manifold%nfirstwin, &
                     num_bands, num_kpts, num_wann, print_output%timing_level, on_root, &
                     print_output%iprint, timer, error, stdout, comm)
    if (allocated(error)) return

    ! If there is an inner window, need to modify projection procedure
    ! (Sec. III.G SMV)
    if (linner) then
      if (lsitesymmetry) then
        call set_error_fatal(error, 'in symmetry-adapted mode, frozen window not implemented yet', &
                             comm)
        return
      end if
      if (print_output%iprint > 0) write (stdout, '(3x,a)') 'Using an inner window (linner = T)'
      call dis_proj_froz(u_matrix_opt, indxfroz, ndimfroz, dis_manifold%ndimwin, &
                         print_output%iprint, num_bands, num_kpts, num_wann, &
                         print_output%timing_level, lfrozen, on_root, timer, error, stdout, comm)
      if (allocated(error)) return
    else
      if (print_output%iprint > 0) write (stdout, '(3x,a)') 'No inner window (linner = F)'
    end if

    ! Debug
    call internal_check_orthonorm(u_matrix_opt, dis_manifold%ndimwin, num_kpts, num_wann, &
                                  print_output%timing_level, on_root, timer, error, stdout, comm)
    if (allocated(error)) return

    ! For frozen_proj, these are done inside dis_windows_proj()
    if (.not. dis_manifold%frozen_proj) then
      ! Slim down the original Mmn(k,b)

      call internal_slim_m(m_matrix_orig_local, dis_manifold%ndimwin, dis_manifold%nfirstwin, &
                           kmesh_info%nnlist, kmesh_info%nntot, num_bands, print_output%timing_level, &
                           timer, dist_k, global_k, error, comm)
      if (allocated(error)) return

      dis_manifold%lwindow = .false.
      do nkp = 1, num_kpts
        do j = dis_manifold%nfirstwin(nkp), dis_manifold%nfirstwin(nkp) + dis_manifold%ndimwin(nkp) - 1
          dis_manifold%lwindow(j, nkp) = .true.
        end do
      end do
    end if

    if (lsitesymmetry) then
      call sitesym_symmetrize_u_matrix(sitesym, u_matrix_opt, num_bands, num_bands, num_kpts, &
                                       num_wann, stdout, error, comm, dis_manifold%lwindow)
      if (allocated(error)) return
    end if

    !RS: calculate initial U_{opt}(Rk) from U_{opt}(k)
    ! Extract the optimally-connected num_wann-dimensional subspaces

    if (gamma_only) then
      call dis_extract_gamma(dis_control, kmesh_info, print_output, dis_manifold, &
                             m_matrix_orig_local, u_matrix_opt, eigval_opt, omega_invariant, &
                             indxnfroz, ndimfroz, num_bands, num_kpts, num_wann, timer, error, &
                             stdout, comm)
      if (allocated(error)) return
    else
      call dis_extract(dis_control, kmesh_info, sitesym, print_output, dis_manifold, &
                       m_matrix_orig_local, u_matrix_opt, eigval_opt, omega_invariant, indxnfroz, &
                       ndimfroz, my_node_id, num_bands, num_kpts, num_wann, lsitesymmetry, timer, &
                       nkrank, global_k, error, stdout, comm)
      if (allocated(error)) return
    end if

    ! Allocate workspace
    allocate (cwb(num_wann, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cwb in dis_main', comm)
      return
    end if
    allocate (cww(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cww in dis_main', comm)
      return
    end if

    ! Find the num_wann x num_wann overlap matrices between
    ! the basis states of the optimal subspaces
    do nkp = 1, nkrank
      nkp_global = global_k(nkp)
      do nn = 1, kmesh_info%nntot
        nkp2 = kmesh_info%nnlist(nkp_global, nn)
        call zgemm('C', 'N', num_wann, dis_manifold%ndimwin(nkp2), dis_manifold%ndimwin(nkp_global), &
                   cmplx_1, u_matrix_opt(:, :, nkp_global), num_bands, &
                   m_matrix_orig_local(:, :, nn, nkp), num_bands, cmplx_0, cwb, num_wann)
        call zgemm('N', 'N', num_wann, num_wann, dis_manifold%ndimwin(nkp2), cmplx_1, cwb, &
                   num_wann, u_matrix_opt(:, :, nkp2), num_bands, cmplx_0, cww, num_wann)
        m_matrix_orig_local(1:num_wann, 1:num_wann, nn, nkp) = cww(:, :)
      end do
    end do

    ! Find the initial u_matrix
    if (lsitesymmetry) call sitesym_replace_d_matrix_band(sitesym, num_wann, error, comm)

    if (gamma_only) then
      call internal_find_u_gamma(a_matrix, u_matrix, u_matrix_opt, dis_manifold%ndimwin, num_wann, &
                                 print_output%timing_level, stdout, timer, error, comm)
      if (allocated(error)) return
    else
      call internal_find_u(sitesym, a_matrix, u_matrix, u_matrix_opt, dis_manifold%ndimwin, &
                           num_bands, num_kpts, num_wann, print_output%timing_level, &
                           lsitesymmetry, on_root, stdout, timer, error, comm)
      if (allocated(error)) return
    end if

    !zero the unused elements of u_matrix_opt (just in case...)
    do nkp = 1, num_kpts
      do j = 1, num_wann
        if (dis_manifold%ndimwin(nkp) < num_bands) &
          u_matrix_opt(dis_manifold%ndimwin(nkp) + 1:, j, nkp) = cmplx_0
      end do
    end do

    ! Deallocate workspace
    deallocate (cww, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cww in dis_main', comm)
      return
    end if
    deallocate (cwb, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cwb in dis_main', comm)
      return
    end if
    deallocate (global_k, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating global_k in dis_main', comm)
      return
    end if
    deallocate (a_matrix, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating a_matrix in dis_main', comm)
      return
    end if

    if (print_output%timing_level > 0 .and. on_root) call io_stopwatch_stop('dis: main', timer)

    return
    !================================================!
  end subroutine dis_main

  subroutine setup_m_loc(kmesh_info, print_output, m_matrix_local, m_matrix_orig_local, u_matrix, &
                         num_bands, num_kpts, num_wann, timer, dist_k, error, comm)
    !================================================!
    !
    ! map m_matrix_orig_local to m_matrix_local
    !  at entry, m_matrix_local is not allocated
    !
    !================================================!
    use w90_comms, only: w90_comm_type, mpirank
    use w90_constants, only: dp, cmplx_0, cmplx_1
    use w90_error
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_types, only: kmesh_info_type, print_output_type, timer_list_type

    ! arguments
    integer, intent(in) :: num_bands, num_kpts, num_wann
    integer, intent(in) :: dist_k(:)

    complex(kind=dp), intent(in) :: u_matrix(:, :, :) ! (num_wann, num_wann, num_kpts) -- full array duplicated on all ranks
    complex(kind=dp), intent(in) :: m_matrix_orig_local(:, :, :, :) ! (num_bands, num_bands, nntot, num_kpts) -- only local kpts
    complex(kind=dp), intent(inout) :: m_matrix_local(:, :, :, :) ! (num_wann, num_wann, nntot, rank_kpts) -- only local kpts

    type(kmesh_info_type), intent(in) :: kmesh_info
    type(print_output_type), intent(in) :: print_output
    type(w90_comm_type), intent(in) :: comm
    type(timer_list_type), intent(inout) :: timer
    type(w90_error_type), allocatable, intent(out) :: error

    ! internal variables
    complex(kind=dp), allocatable :: cwb(:, :), cww(:, :)
    integer :: nkp, nkp2, nn, ierr, nkp_global, nkrank
    integer, allocatable :: global_k(:)
    integer :: ikg, ikl, my_node_id

    if (print_output%timing_level > 1) call io_stopwatch_start('dis: setup_m_loc', timer)

    ! local-global k index mapping
    my_node_id = mpirank(comm)
    nkrank = count(dist_k == my_node_id)
    allocate (global_k(nkrank), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating global_k in setup_m_loc', comm)
      return
    end if
    global_k = huge(1); ikl = 1
    do ikg = 1, num_kpts
      if (dist_k(ikg) == my_node_id) then
        global_k(ikl) = ikg ! global [1,num_kpts] index corresponding to local [1,nk_this_node] index
        ikl = ikl + 1
      end if
    end do

    allocate (cwb(num_wann, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cwb in setup_m_loc', comm)
      return
    end if
    allocate (cww(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cww in setup_m_loc', comm)
      return
    end if

    do nkp = 1, nkrank
      nkp_global = global_k(nkp)

      do nn = 1, kmesh_info%nntot
        nkp2 = kmesh_info%nnlist(nkp_global, nn)
        call zgemm('C', 'N', num_wann, num_wann, num_wann, cmplx_1, u_matrix(:, :, nkp_global), &
                   num_wann, m_matrix_orig_local(:, :, nn, nkp), num_bands, cmplx_0, cwb, num_wann)
        call zgemm('N', 'N', num_wann, num_wann, num_wann, cmplx_1, cwb, num_wann, &
                   u_matrix(:, :, nkp2), num_wann, cmplx_0, cww, num_wann)

        m_matrix_local(1:num_wann, 1:num_wann, nn, nkp) = cww(:, :)
      end do
    end do

    deallocate (cwb, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cwb in setup_m_loc', comm)
      return
    end if
    deallocate (cww, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cww in setup_m_loc', comm)
      return
    end if
    deallocate (global_k, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating global_k in setup_m_loc', comm)
      return
    end if
    if (print_output%timing_level > 1) call io_stopwatch_stop('dis: setup_m_loc', timer)
    return
    !================================================!
  end subroutine setup_m_loc

  subroutine internal_check_orthonorm(u_matrix_opt, ndimwin, num_kpts, num_wann, timing_level, &
                                      on_root, timer, error, stdout, comm)
    !================================================!
    !
    !! This subroutine checks that the states in the columns of the
    !! final matrix U_opt are orthonormal at every k-point, i.e.,
    !! that the matrix is unitary in the sense that
    !! conjg(U_opt).U_opt = 1  (but not  U_opt.conjg(U_opt) = 1).
    !!
    !! In particular, this checks whether the projected gaussians
    !! are indeed orthogonal to the frozen states, at those k-points
    !! where both are present in the trial subspace.
    !
    !================================================!
    use w90_comms, only: w90_comm_type
    use w90_constants, only: dp, cmplx_0, cmplx_1
    use w90_constants, only: eps8
    use w90_error
    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
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: timing_level
    integer, intent(in) :: stdout
    integer, intent(in) :: num_kpts, num_wann
    integer, intent(in) :: ndimwin(:) ! (num_kpts)

    complex(kind=dp), intent(inout) :: u_matrix_opt(:, :, :)
    logical, intent(in) :: on_root

    ! local variables
    integer          :: nkp, l, m, j

    complex(kind=dp) :: ctmp

    if (timing_level > 1) call io_stopwatch_start('dis: main: check_orthonorm', timer)

    do nkp = 1, num_kpts
      do l = 1, num_wann
        do m = 1, l
          ctmp = cmplx_0
          do j = 1, ndimwin(nkp)
            ctmp = ctmp + conjg(u_matrix_opt(j, m, nkp))*u_matrix_opt(j, l, nkp)
          end do
          if (l .eq. m) then
            if (abs(ctmp - cmplx_1) .gt. eps8) then
              if (on_root) then
                write (stdout, '(3i6,2f16.12)') nkp, l, m, ctmp
                write (stdout, '(1x,a)') &
                  'The trial orbitals for disentanglement are not orthonormal'
              end if
              call set_error_fatal(error, 'Error in dis_main: check_orthonorm: orthonormal error 1', comm)
              return
            end if
          else
            if (abs(ctmp) .gt. eps8) then
              if (on_root) then
                write (stdout, '(3i6,2f16.12)') nkp, l, m, ctmp
                write (stdout, '(1x,a)') &
                  'The trial orbitals for disentanglement are not orthonormal'
              end if
              call set_error_fatal(error, 'Error in dis_main: check_orthonorm: orthonormal error 2', comm)
              return
            end if
          end if
        end do
      end do
    end do

    if (timing_level > 1) call io_stopwatch_stop('dis: main: check_orthonorm', timer)

    return
    !================================================!
  end subroutine internal_check_orthonorm

  subroutine internal_slim_m(m_matrix_orig_local, ndimwin, nfirstwin, nnlist, nntot, num_bands, &
                             timing_level, timer, dist_k, global_k, error, comm)
    !================================================!
    !
    !! This subroutine slims down the original Mmn(k,b), removing
    !! rows and columns corresponding to u_nks that fall outside
    !! the outer energy window.
    !
    !================================================!
    use w90_comms, only: w90_comm_type, mpirank
    use w90_constants, only: dp, cmplx_0
    use w90_error
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_types, only: timer_list_type

    implicit none

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

    integer, intent(in) :: dist_k(:), global_k(:)
    integer, intent(in) :: ndimwin(:)
    integer, intent(in) :: nfirstwin(:) ! (num_kpts) index of lowest band inside outer window at nkp-th
    integer, intent(in) :: nntot, nnlist(:, :) ! (num_kpts, nntot)
    integer, intent(in) :: num_bands
    integer, intent(in) :: timing_level

    complex(kind=dp), intent(inout) :: m_matrix_orig_local(:, :, :, :)

    ! local variables
    integer :: nkp, nkp2, nn, i, j, m, n, ierr, nkp_global, nkrank, my_node_id

    complex(kind=dp), allocatable :: cmtmp(:, :)

    if (timing_level > 1) call io_stopwatch_start('dis: main: slim_m', timer)

    my_node_id = mpirank(comm)
    nkrank = count(dist_k == my_node_id) ! number of k points this rank

    allocate (cmtmp(num_bands, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cmtmp in dis_main: slim_m', comm)
      return
    end if
    do nkp = 1, nkrank
      nkp_global = global_k(nkp)
      do nn = 1, nntot
        nkp2 = nnlist(nkp_global, nn)
        do j = 1, ndimwin(nkp2)
          n = nfirstwin(nkp2) + j - 1
          do i = 1, ndimwin(nkp_global)
            m = nfirstwin(nkp_global) + i - 1
            cmtmp(i, j) = m_matrix_orig_local(m, n, nn, nkp)
          end do
        end do
        m_matrix_orig_local(:, :, nn, nkp) = cmplx_0
        do j = 1, ndimwin(nkp2)
          do i = 1, ndimwin(nkp_global)
            m_matrix_orig_local(i, j, nn, nkp) = cmtmp(i, j)
          end do
        end do
      end do
    end do

    deallocate (cmtmp, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cmtmp in dis_main: slim_m', comm)
      return
    end if

    if (timing_level > 1) call io_stopwatch_stop('dis: main: slim_m', timer)

    return
    !================================================!
  end subroutine internal_slim_m

  subroutine internal_find_u(sitesym, a_matrix, u_matrix, u_matrix_opt, ndimwin, num_bands, &
                             num_kpts, num_wann, timing_level, lsitesymmetry, on_root, stdout, &
                             timer, error, comm)
    !================================================!
    !
    !! This subroutine finds the initial guess for the square unitary
    !! rotation matrix u_matrix. The method is similar to Sec. III.D
    !! of SMV, but with square instead of rectangular matrices:
    !!
    !! First find caa, the square overlap matrix <psitilde_nk|g_m>,
    !! where psitilde is an eigenstate of the optimal subspace.
    !!
    !! Note that, contrary to what is implied in Sec. III.E of SMV,
    !! this does *not* need to be computed by brute: instead we take
    !! advantage of the previous computation of overlaps with the
    !! same projections that are used to initiate the minimization of
    !! Omega.
    !!
    !! Note: |psi> U_opt = |psitilde> and obviously
    !! <psitilde| = (U_opt)^dagger <psi|
    !
    !================================================!
    use w90_constants, only: dp, cmplx_0, cmplx_1
    use w90_error
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_sitesym, only: sitesym_symmetrize_u_matrix
    use w90_types, only: timer_list_type
    use w90_wannier90_types, only: sitesym_type

    implicit none

    ! arguments
    type(sitesym_type), intent(inout) :: sitesym
    type(timer_list_type), intent(inout) :: timer
    type(w90_comm_type), intent(in) :: comm
    type(w90_error_type), allocatable, intent(out) :: error

    integer, intent(in) :: ndimwin(:) ! (num_kpts)
    integer, intent(in) :: num_bands, num_kpts, num_wann
    integer, intent(in) :: stdout
    integer, intent(in) :: timing_level

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

    logical, intent(in) :: on_root, lsitesymmetry

    ! local variables
    integer :: nkp, info, ierr
    complex(kind=dp), allocatable :: caa(:, :, :)
    ! For ZGESVD
    real(kind=dp), allocatable :: svals(:)
    real(kind=dp), allocatable :: rwork(:)

    complex(kind=dp), allocatable :: cv(:, :)
    complex(kind=dp), allocatable :: cz(:, :)
    complex(kind=dp), allocatable :: cwork(:)

    if (timing_level > 1) call io_stopwatch_start('dis: main: find_u', timer)

    ! Currently, this part is not parallelized; thus, we perform the task only on root and then broadcast the result.
    if (on_root) then
      ! Allocate arrays needed for ZGESVD
      allocate (svals(num_wann), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating svals in dis_main: find_u', comm)
        return
      end if
      allocate (rwork(5*num_wann), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating rwork in dis_main: find_u', comm)
        return
      end if
      allocate (cv(num_wann, num_wann), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating cv in dis_main: find_u', comm)
        return
      end if
      allocate (cz(num_wann, num_wann), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating cz in dis_main: find_u', comm)
        return
      end if
      allocate (cwork(4*num_wann), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating cwork in dis_main: find_u', comm)
        return
      end if
      allocate (caa(num_wann, num_wann, num_kpts), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating caa in dis_main: find_u', comm)
        return
      end if

      do nkp = 1, num_kpts
        if (lsitesymmetry) then
          if (sitesym%ir2ik(sitesym%ik2ir(nkp)) .ne. nkp) cycle
        end if
        call zgemm('C', 'N', num_wann, num_wann, ndimwin(nkp), cmplx_1, u_matrix_opt(:, :, nkp), &
                   num_bands, a_matrix(:, :, nkp), num_bands, cmplx_0, caa(:, :, nkp), num_wann)
        ! Singular-value decomposition
        call zgesvd('A', 'A', num_wann, num_wann, caa(:, :, nkp), num_wann, svals, cz, num_wann, &
                    cv, num_wann, cwork, 4*num_wann, rwork, info)
        if (info .ne. 0) then
          if (on_root) write (stdout, *) ' ERROR: IN ZGESVD IN dis_main'
          if (on_root) write (stdout, *) 'K-POINT NKP=', nkp, ' INFO=', info
          if (info .lt. 0) then
            if (on_root) write (stdout, *) 'THE ', -info, '-TH ARGUMENT HAD ILLEGAL VALUE'
          end if
          call set_error_fatal(error, 'dis_main: find_u problem in ZGESVD 1', comm)
          return
        end if
        ! u_matrix is the initial guess for the unitary rotation of the
        ! basis states given by the subroutine extract
        call zgemm('N', 'N', num_wann, num_wann, num_wann, cmplx_1, cz, num_wann, cv, num_wann, &
                   cmplx_0, u_matrix(:, :, nkp), num_wann)
      end do
    end if
    call comms_bcast(u_matrix(1, 1, 1), num_wann*num_wann*num_kpts, error, comm)
    if (allocated(error)) return
!      if (lsitesymmetry) call sitesym_symmetrize_u_matrix(num_wann,u_matrix) !RS:

    if (on_root) then
      ! Deallocate arrays for ZGESVD
      deallocate (caa, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating caa in dis_main: find_u', comm)
        return
      end if
      deallocate (cwork, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating cwork in dis_main: find_u', comm)
        return
      end if
      deallocate (cz, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating cz in dis_main: find_u', comm)
        return
      end if
      deallocate (cv, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating cv in dis_main: find_u', comm)
        return
      end if
      deallocate (rwork, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating rwork in dis_main: find_u', comm)
        return
      end if
      deallocate (svals, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating svals in dis_main: find_u', comm)
        return
      end if
    end if

    if (lsitesymmetry) then
      call sitesym_symmetrize_u_matrix(sitesym, u_matrix, num_bands, num_wann, num_kpts, num_wann, &
                                       stdout, error, comm)
      if (allocated(error)) return
    end if

    if (timing_level > 1) call io_stopwatch_stop('dis: main: find_u', timer)

    return
    !================================================!
  end subroutine internal_find_u

  subroutine internal_find_u_gamma(a_matrix, u_matrix, u_matrix_opt, ndimwin, num_wann, &
                                   timing_level, stdout, timer, error, comm)
    !================================================!
    !
    !! Make initial u_matrix real
    !! Must be the case when gamma_only = .true.
    !
    !================================================!
    use w90_constants, only: dp
    use w90_error
    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
    type(w90_comm_type), intent(in) :: comm
    type(w90_error_type), allocatable, intent(out) :: error

    integer, intent(in) :: ndimwin(:)
    integer, intent(in) :: stdout
    integer, intent(in) :: timing_level, num_wann

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

    ! local variables
    integer :: info, ierr

    real(kind=dp), allocatable :: a_matrix_r(:, :)
    real(kind=dp), allocatable :: raa(:, :)
    real(kind=dp), allocatable :: u_opt_r(:, :)
    ! for dgesvd
    real(kind=dp), allocatable :: rv(:, :)
    real(kind=dp), allocatable :: rz(:, :)
    real(kind=dp), allocatable :: svals(:)
    real(kind=dp), allocatable :: work(:)

    if (timing_level > 1) call io_stopwatch_start('dis: main: find_u_gamma', timer)

    ! Allocate arrays needed for getting a_matrix_r
    allocate (u_opt_r(ndimwin(1), num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating u_opt_r in dis_main: find_u_gamma', comm)
      return
    end if
    allocate (a_matrix_r(ndimwin(1), num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating a_matrix_r in dis_main: find_u_gamma', comm)
      return
    end if

    ! Allocate arrays needed for dgesvd
    allocate (svals(num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating svals in dis_main: find_u_gamma', comm)
      return
    end if
    allocate (work(5*num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating rwork in dis_main: find_u_gamma', comm)
      return
    end if
    allocate (rv(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cv in dis_main: find_u_gamma', comm)
      return
    end if
    allocate (rz(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cz in dis_main: find_u_gamma', comm)
      return
    end if
    allocate (raa(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating raa in dis_main: find_u_gamma', comm)
      return
    end if

    u_opt_r(:, :) = real(u_matrix_opt(1:ndimwin(1), 1:num_wann, 1), dp)

    a_matrix_r(:, :) = real(a_matrix(1:ndimwin(1), 1:num_wann, 1), kind=dp)

    call dgemm('T', 'N', num_wann, num_wann, ndimwin(1), 1.0_dp, u_opt_r, ndimwin(1), a_matrix_r, &
               ndimwin(1), 0.0_dp, raa, num_wann)
    ! Singular-value decomposition
    call dgesvd('A', 'A', num_wann, num_wann, raa, num_wann, svals, rz, num_wann, rv, num_wann, &
                work, 5*num_wann, info)
    if (info .ne. 0) then
      write (stdout, *) ' ERROR: IN DGESVD IN dis_main'
      write (stdout, *) 'K-POINT = Gamma', ' INFO=', info
      if (info .lt. 0) then
        write (stdout, *) 'THE ', -info, '-TH ARGUMENT HAD ILLEGAL VALUE'
      end if
      call set_error_fatal(error, 'dis_main: find_u_gamma: problem in DGESVD 1', comm)
      return
    end if
    ! u_matrix is the initial guess for the unitary rotation of the
    ! basis states given by the subroutine extract
    call dgemm('N', 'N', num_wann, num_wann, num_wann, 1.0_dp, rz, num_wann, rv, num_wann, 0.0_dp, &
               raa, num_wann)

    u_matrix(:, :, 1) = cmplx(raa(:, :), 0.0_dp, dp)

    ! Deallocate arrays for DGESVD
    deallocate (raa, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating raa in dis_main: find_u_gamma', comm)
      return
    end if
    deallocate (rz, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating rz in dis_main: find_u_gamma', comm)
      return
    end if
    deallocate (rv, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating rv in dis_main: find_u_gamma', comm)
      return
    end if
    deallocate (work, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating work in dis_main: find_u_gamma', comm)
      return
    end if
    deallocate (svals, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating svals in dis_main: find_u_gamma', comm)
      return
    end if

    ! Deallocate arrays for a_matrix_r
    deallocate (a_matrix_r, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating a_matrix_r in dis_main: find_u_gamma', comm)
      return
    end if
    deallocate (u_opt_r, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating u_opt_r in dis_main: find_u_gamma', comm)
      return
    end if

    if (timing_level > 1) call io_stopwatch_stop('dis: main: find_u_gamma: find_u_gamma', timer)

    return
    !================================================!
  end subroutine internal_find_u_gamma

  subroutine dis_windows(dis_spheres, dis_manifold, eigval_opt, kpt_latt, recip_lattice, indxfroz, &
                         indxnfroz, ndimfroz, iprint, num_bands, num_kpts, num_wann, &
                         timing_level, lfrozen, linner, on_root, stdout, timer, error, comm)
    !================================================!
    !
    !! This subroutine selects the states that are inside the outer
    !! window (ie, the energy window out of which we fish out the
    !! optimally-connected subspace) and those that are inside the
    !! inner window (that make up the frozen manifold, and are
    !! straightfowardly included as they are). This, in practice,
    !! amounts to slimming down the original num_wann x num_wann overlap
    !! matrices, removing rows and columns that belong to u_nks that
    !! have been excluded forever, and marking (indexing) the rows and
    !! columns that correspond to frozen states.
    !!
    !! Note - in windows eigval_opt are shifted, so the lowest ones go
    !! from nfirstwin(nkp) to nfirstwin(nkp)+ndimwin(nkp)-1, and above
    !! they are set to zero.
    !
    !================================================!

    ! OUTPUT:
    !     ndimwin(nkp)   number of bands inside outer window at nkp-th k poi
    !     ndimfroz(nkp)  number of frozen bands at nkp-th k point
    !     lfrozen(i,nkp) true if the i-th band inside outer window is frozen
    !     linner         true if there is an inner window
    !     indxfroz(i,nkp) outer-window band index for the i-th frozen state
    !                     (equals 1 if it is the bottom of outer window)
    !     indxnfroz(i,nkp) outer-window band index for the i-th non-frozen s
    !                     (equals 1 if it is the bottom of outer window)
    !     nfirstwin(nkp) index of lowest band inside outer window at nkp-th
    ! MODIFIED:
    !     eigval_opt(nb,nkp) At input it contains a large set of eigenvalues. At
    !                    it is slimmed down to contain only those inside the
    !                    energy window, stored in nb=1,...,ndimwin(nkp)
    use w90_constants, only: dp
    use w90_error
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_types, only: dis_manifold_type, kmesh_info_type, print_output_type, timer_list_type
    use w90_wannier90_types, only: dis_spheres_type

    implicit none

    ! arguments
    type(dis_spheres_type), intent(in) :: dis_spheres
    type(dis_manifold_type), intent(inout) :: dis_manifold ! ndimwin alone is modified
    type(timer_list_type), intent(inout) :: timer
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: iprint, timing_level
    integer, intent(in) :: stdout
    integer, intent(in) :: num_bands, num_kpts, num_wann
    integer, intent(inout) :: ndimfroz(:)
    integer, intent(inout) :: indxfroz(:, :)
    integer, intent(inout) :: indxnfroz(:, :)

    real(kind=dp), intent(in) :: kpt_latt(3, num_kpts), recip_lattice(3, 3)
    real(kind=dp), intent(inout) :: eigval_opt(:, :)

    logical, intent(in) :: on_root
    logical, intent(inout) :: linner
    logical, intent(inout) :: lfrozen(:, :)

    ! local variables
    integer :: i, j, nkp
    integer :: imin, imax, kifroz_min, kifroz_max
    real(kind=dp) :: dk(3)
    logical :: dis_ok

    if (timing_level > 1) call io_stopwatch_start('dis: windows', timer)

    linner = .false.

    if (iprint > 0) write (stdout, '(1x,a)') &
      '+----------------------------------------------------------------------------+'
    if (iprint > 0) write (stdout, '(1x,a)') &
      '|                              Energy  Windows                               |'
    if (iprint > 0) write (stdout, '(1x,a)') &
      '|                              ---------------                               |'
    if (iprint > 0) write (stdout, '(1x,a,f10.5,a,f10.5,a)') &
      '|                   Outer: ', dis_manifold%win_min, '  to ', dis_manifold%win_max, &
      '  (eV)                   |'
    if (dis_manifold%frozen_states) then
      if (iprint > 0) write (stdout, '(1x,a,f10.5,a,f10.5,a)') &
        '|                   Inner: ', dis_manifold%froz_min, '  to ', dis_manifold%froz_max, &
        '  (eV)                   |'
    else
      if (iprint > 0) write (stdout, '(1x,a)') &
        '|                   No frozen states were specified                          |'
    end if
    if (iprint > 0) write (stdout, '(1x,a)') &
      '+----------------------------------------------------------------------------+'

    do nkp = 1, num_kpts
      ! Check which eigenvalues fall within the outer window
      if ((eigval_opt(1, nkp) .gt. dis_manifold%win_max) .or. &
          (eigval_opt(num_bands, nkp) .lt. dis_manifold%win_min)) then
        if (on_root) then
          write (stdout, *) ' ERROR AT K-POINT: ', nkp
          write (stdout, *) ' ENERGY WINDOW (eV):    [', dis_manifold%win_min, ',', &
            dis_manifold%win_max, ']'
          write (stdout, *) ' EIGENVALUE RANGE (eV): [', &
            eigval_opt(1, nkp), ',', eigval_opt(num_bands, nkp), ']'
          call set_error_fatal(error, 'dis_windows: The outer energy window contains no eigenvalues', comm)
          return
        end if
      end if

      dis_manifold%ndimwin(nkp) = num_bands
      dis_manifold%nfirstwin(nkp) = 1

      ! Note: we assume that eigvals are ordered from the bottom up
      imin = 0
      imax = 0
      do i = 1, num_bands
        if (imin .eq. 0) then
          if ((eigval_opt(i, nkp) .ge. dis_manifold%win_min) .and. &
              (eigval_opt(i, nkp) .le. dis_manifold%win_max)) imin = i
          imax = i
        end if
        if (eigval_opt(i, nkp) .le. dis_manifold%win_max) imax = i
      end do

      dis_manifold%ndimwin(nkp) = imax - imin + 1
      dis_manifold%nfirstwin(nkp) = imin

      !~~ GS-start
      ! disentangle at the current k-point only if it is within one of the
      ! spheres centered at the k-points listed in kpt_dis
      if (dis_spheres%num .gt. 0) then
        dis_ok = .false.
        ! loop on the sphere centers
        do i = 1, dis_spheres%num
          dk = kpt_latt(:, nkp) - dis_spheres%spheres(1:3, i)
          dk = matmul(anint(dk) - dk, recip_lattice(:, :))
          ! if the current k-point is included in at least one sphere,
          ! then perform disentanglement as usual
          if (abs(dot_product(dk, dk)) .lt. dis_spheres%spheres(4, i)**2) then
            dis_ok = .true.
            exit
          end if
        end do
        ! this kpoint is not included in any sphere: no disentaglement
        if (.not. dis_ok) then
          dis_manifold%ndimwin(nkp) = num_wann
          dis_manifold%nfirstwin(nkp) = dis_spheres%first_wann
        end if
      end if
      !~~ GS-end

      if (dis_manifold%ndimwin(nkp) .lt. num_wann) then
        if (iprint > 0) write (stdout, '(1x, a17, i4, a8, i3, a9, i3)') 'Error at k-point ', nkp, &
          ' ndimwin=', dis_manifold%ndimwin(nkp), ' num_wann=', num_wann
        call set_error_fatal(error, 'dis_windows: Energy window contains fewer states than number of target WFs', comm)
        return
      end if

      do i = 1, dis_manifold%ndimwin(nkp)
        lfrozen(i, nkp) = .false.
      end do

      ! Check which eigenvalues fall within the inner window
      kifroz_min = 0
      kifroz_max = -1

      ! (note that the above obeys kifroz_max-kifroz_min+1=kdimfroz=0, as we w
      if (dis_manifold%frozen_states) then
        do i = imin, imax
          if (kifroz_min .eq. 0) then
            if ((eigval_opt(i, nkp) .ge. dis_manifold%froz_min) .and. &
                (eigval_opt(i, nkp) .le. dis_manifold%froz_max)) then
              ! Relative to bottom of outer window
              kifroz_min = i - imin + 1
              kifroz_max = i - imin + 1
            end if
          elseif (eigval_opt(i, nkp) .le. dis_manifold%froz_max) then
            kifroz_max = kifroz_max + 1
            ! DEBUG
            ! if(kifroz_max.ne.i-imin+1) stop 'something wrong...'
            ! ENDDEBUG
          end if
        end do
      end if

      ndimfroz(nkp) = kifroz_max - kifroz_min + 1

      if (ndimfroz(nkp) .gt. num_wann) then
        if (on_root) write (stdout, 401) nkp, ndimfroz(nkp), num_wann
401     format(' ERROR AT K-POINT ', i4, ' THERE ARE ', i2, &
               ' BANDS INSIDE THE INNER WINDOW AND ONLY', i2, &
               ' TARGET BANDS')
        if (on_root) write (stdout, 402) (eigval_opt(i, nkp), i=imin, imax)
402     format('BANDS: (eV)', 10(F10.5, 1X))
        call set_error_fatal(error, 'dis_windows: More states in the frozen window than target WFs', comm)
        return
      end if

      if (ndimfroz(nkp) .gt. 0) linner = .true.
      ! DEBUG
      !         write(*,'(a,i4,a,i2,a,i2)') 'k point ',nkp,
      !     &    ' lowest band in outer win is # ',imin,
      !     &    '   # frozen states is ',ndimfroz(nkp)
      ! ENDDEBUG
      ! Generate index array for frozen states (those inside inner window)
      if (ndimfroz(nkp) .gt. 0) then
        do i = 1, ndimfroz(nkp)
          indxfroz(i, nkp) = kifroz_min + i - 1
          lfrozen(indxfroz(i, nkp), nkp) = .true.
        end do
        if (indxfroz(ndimfroz(nkp), nkp) .ne. kifroz_max) then
          if (on_root) then
            write (stdout, *) ' Error at k-point ', nkp, ' frozen band #', i
            write (stdout, *) ' ndimfroz=', ndimfroz(nkp)
            write (stdout, *) ' kifroz_min=', kifroz_min
            write (stdout, *) ' kifroz_max=', kifroz_max
            write (stdout, *) ' indxfroz(i,nkp)=', indxfroz(i, nkp)
          end if
          call set_error_fatal(error, 'dis_windows: Something fishy... disentangle.F90+1065', comm)
          return
        end if
      end if

      ! Generate index array for non-frozen states
      i = 0
      do j = 1, dis_manifold%ndimwin(nkp)
        if (.not. lfrozen(j, nkp)) then
          i = i + 1
          indxnfroz(i, nkp) = j
        end if
      end do

      if (i .ne. dis_manifold%ndimwin(nkp) - ndimfroz(nkp)) then
        if (on_root) write (stdout, *) ' Error at k-point: ', nkp
        if (on_root) write (stdout, '(3(a,i5))') ' i: ', i, ' ndimwin: ', &
          dis_manifold%ndimwin(nkp), ' ndimfroz: ', ndimfroz(nkp)
        call set_error_input(error, 'dis_windows: i .ne. (ndimwin-ndimfroz) at k-point', comm)
        return
      end if

      ! Slim down eigval vector at present k
      do i = 1, dis_manifold%ndimwin(nkp)
        j = dis_manifold%nfirstwin(nkp) + i - 1
        eigval_opt(i, nkp) = eigval_opt(j, nkp)
      end do

      do i = dis_manifold%ndimwin(nkp) + 1, num_bands
        eigval_opt(i, nkp) = 0.0_dp
      end do

    end do
    ! [k-point loop (nkp)]

![ysl-b]
!~    if (gamma_only) then
!~       if (.not. allocated(ph_g)) then
!~          allocate(  ph_g(num_bands),stat=ierr )
!~          if (ierr/=0) call io_error('Error in allocating ph_g in dis_windows')
!~          ph_g = cmplx_1
!~       endif
!~       ! Apply same operation to ph_g
!~       do i = 1, ndimwin(1)
!~          j = nfirstwin(1) + i - 1
!~          ph_g(i) = ph_g(j)
!~       enddo
!~       do i = ndimwin(1) + 1, num_bands
!~          ph_g(i) = cmplx_0
!~       enddo
!~    endif
!~![ysl-e]

    if (iprint > 1) then ! iprint > 0 implies rank > 0
      write (stdout, '(1x,a)') &
        '|                        K-points with Frozen States                         |'
      write (stdout, '(1x,a)') &
        '|                        ---------------------------                         |'
      i = 0
      do nkp = 1, num_kpts
        if (ndimfroz(nkp) .gt. 0) then
          i = i + 1
          if (i .eq. 1) then
            write (stdout, '(1x,a,i6)', advance='no') '|', nkp
          else if ((i .gt. 1) .and. (i .lt. 12)) then
            write (stdout, '(i6)', advance='no') nkp
          else if (i .eq. 12) then
            write (stdout, '(i6,a)') nkp, '    |'
            i = 0
          end if
        end if
      end do
      if (i .ne. 0) then
        do j = 1, 12 - i
          write (stdout, '(6x)', advance='no')
        end do
        write (stdout, '(a)') '    |'
      end if
      write (stdout, '(1x,a)') &
        '+----------------------------------------------------------------------------+'
    end if

    if (iprint > 0) write (stdout, '(3x,a,i4)') 'Number of target bands to extract: ', num_wann
    if (iprint > 1) then
      write (stdout, '(4(1x,a,/),(1x,a))') &
        '+----------------------------------------------------------------------------+', &
        '|                                  Windows                                   |', &
        '|                                  -------                                   |', &
        '|               K-point      Ndimwin     Ndimfroz    Nfirstwin               |', &
        '|               ----------------------------------------------               |'

      do nkp = 1, num_kpts
        write (stdout, 403) nkp, dis_manifold%ndimwin(nkp), ndimfroz(nkp), dis_manifold%nfirstwin(nkp)
      end do
403   format(1x, '|', 14x, i6, 7x, i6, 7x, i6, 6x, i6, 18x, '|')
      write (stdout, '(1x,a)') &
        '+----------------------------------------------------------------------------+'
    end if

    if (timing_level > 1) call io_stopwatch_stop('dis: windows', timer)

    return
    !================================================!
  end subroutine dis_windows

  subroutine dis_windows_proj(dis_manifold, eigval_opt, a_matrix, m_matrix_orig_local, &
                              indxfroz, indxnfroz, ndimfroz, nfirstwin, iprint, nnlist, &
                              nntot, num_bands, num_kpts, num_wann, timing_level, lfrozen, &
                              linner, on_root, stdout, dist_k, global_k, my_node_id, timer, error, comm)
    !==================================================================!
    !                                                                  !
    !! This subroutine selects the states for disentanglement and frozen
    !! based on projectability. States with projectability < dis_proj_min
    !! are discarded, states with projectability >= dis_proj_min are
    !! included in the disentanglement, states with projectability
    !! >= dis_proj_max are frozen.
    !                                                                  !
    !==================================================================!

    ! OUTPUT:
    !     ndimwin(nkp)   number of bands inside outer window at nkp-th k point
    !     ndimfroz(nkp)  number of frozen bands at nkp-th k point
    !     lfrozen(i,nkp) true if the i-th band inside outer window is frozen
    !     linner         true if there is an inner window
    !     indxfroz(i,nkp) outer-window band index for the i-th frozen state
    !                     (equals 1 if it is the bottom of outer window.
    !                      The indexes are reordered after excluding
    !                      low-projectability states.)
    !     indxnfroz(i,nkp) outer-window band index for the i-th non-frozen s
    !                     (equals 1 if it is the bottom of outer window.
    !                      The indexes are reordered after excluding
    !                      low-projectability states.)
    !     nfirstwin(nkp) index of lowest band inside outer window at nkp-th
    ! MODIFIED:
    !     eigval_opt(nb,nkp) At input it contains a large set of eigenvalues. At
    !                    it is slimmed down to contain only those inside the
    !                    energy window, stored in nb=1,...,ndimwin(nkp)
    use w90_comms, only: w90_comm_type
    use w90_constants, only: dp, cmplx_0
    use w90_error
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_types, only: dis_manifold_type, timer_list_type
    use w90_wannier90_types, only: dis_control_type

    implicit none

    ! arguments
    type(dis_manifold_type), intent(inout) :: dis_manifold ! ndimwin alone is modified
    type(timer_list_type), intent(inout) :: timer
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: dist_k(:), global_k(:)
    !! assignment of k-points to MPI processes and global/local k index map
    integer, intent(in) :: iprint, timing_level
    integer, intent(in) :: stdout
    integer, intent(in) :: nntot, nnlist(:, :) ! (num_kpts, nntot)
    integer, intent(in) :: num_bands, num_kpts, num_wann
    integer, intent(in) :: my_node_id
    integer, intent(inout) :: ndimfroz(:)
    integer, intent(inout) :: indxfroz(:, :)
    integer, intent(inout) :: indxnfroz(:, :)
    integer, intent(inout) :: nfirstwin(:)

    complex(kind=dp), intent(inout) :: a_matrix(:, :, :)
    complex(kind=dp), intent(inout) :: m_matrix_orig_local(:, :, :, :)
    real(kind=dp), intent(inout) :: eigval_opt(:, :)

    logical, intent(in) :: on_root
    logical, intent(inout) :: linner
    logical, intent(inout) :: lfrozen(:, :)

    ! local variables
    integer :: indxkeep(num_bands, num_kpts)
    !     indxkeep(i,nkp) original outer-window band index for the i-th state
    !                     included in disentanglement, i.e. frozen + non-frozen state.
    !                     This is used to allow excluding states in the middle of
    !                     orignal outter window, and to generate a new outter window.
    !                     (equals 1 if it is the bottom of outer window)

    integer :: nkp, nn, nkp2, nkp_global
    integer :: i, j, k, l
    real(kind=dp) :: projs(num_bands)
    integer :: invindxkeep(num_bands)
    real(kind=dp), allocatable :: pooled(:), otsu_thr(:)
    real(kind=dp) :: pval
    integer :: ip, ierr, nclasses_eff
    logical :: degenerate

    if (timing_level > 1 .and. on_root) call io_stopwatch_start('dis: windows_proj', timer)

    linner = .false.

    ! Automatic projectability thresholds via multi-Otsu (dis_proj_auto). The
    ! a_matrix is full on every rank, so pooling over all bands and k-points is
    ! rank-uniform and needs no MPI communication.
    if (dis_manifold%proj_auto) then
      allocate (pooled(num_bands*num_kpts), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error allocating pooled in dis_windows_proj', comm)
        return
      end if
      ip = 0
      do nkp = 1, num_kpts
        do i = 1, num_bands
          pval = 0.0_dp
          do j = 1, num_wann
            pval = pval + real(a_matrix(i, j, nkp), dp)**2 + aimag(a_matrix(i, j, nkp))**2
          end do
          if (pval < 0.0_dp) pval = 0.0_dp
          if (pval > 1.0_dp) pval = 1.0_dp
          ip = ip + 1
          pooled(ip) = pval
        end do
      end do

      allocate (otsu_thr(dis_manifold%proj_auto_num_classes - 1), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error allocating otsu_thr in dis_windows_proj', comm)
        return
      end if
      call dis_otsu_thresholds(pooled, otsu_lower_bound, otsu_upper_bound, otsu_nbins, &
                               dis_manifold%proj_auto_num_classes, otsu_thr, nclasses_eff, degenerate)
      deallocate (pooled)

      if (degenerate) then
        deallocate (otsu_thr)
        ! message must fit the 128-char w90_error_type buffer
        call set_error_fatal(error, 'Error: dis_proj_auto: fewer than 3 distinct projectability '// &
                             'clusters; set dis_proj_min/dis_proj_max manually', comm)
        return
      end if
      ! A class needs its own populated bin, so more classes than clusters is
      ! not resolvable; reduce and tell the user.
      if (nclasses_eff < dis_manifold%proj_auto_num_classes .and. on_root) then
        write (stdout, '(1x,a,i0,a,i0,a,i0)') &
          ' dis_proj_auto: only ', nclasses_eff, ' distinct projectability clusters; reducing classes from ', &
          dis_manifold%proj_auto_num_classes, ' to ', nclasses_eff
      end if
      dis_manifold%proj_min = otsu_thr(1)
      dis_manifold%proj_max = otsu_thr(nclasses_eff - 1)
      deallocate (otsu_thr)
    end if

    if (on_root) write (stdout, '(1x,a)') &
      '+----------------------------------------------------------------------------+'
    if (on_root) write (stdout, '(1x,a)') &
      '|                              Energy  Windows                               |'
    if (on_root) write (stdout, '(1x,a)') &
      '|                              ---------------                               |'
    if (on_root) write (stdout, '(1x,a,f10.5,a,f10.5,a)') &
      '|                   Outer: ', dis_manifold%win_min, '  to ', dis_manifold%win_max, &
      '  (eV)                   |'
    if (dis_manifold%frozen_states) then
      if (on_root) write (stdout, '(1x,a,f10.5,a,f10.5,a)') &
        '|                   Inner: ', dis_manifold%froz_min, '  to ', dis_manifold%froz_max, &
        '  (eV)                   |'
    else
      if (on_root) write (stdout, '(1x,a)') &
        '|                   No frozen states were specified                          |'
    end if
    if (on_root) write (stdout, '(1x,a)') &
      '|----------------------------------------------------------------------------|'
    if (on_root) write (stdout, '(1x,a)') &
      '|                          Projectability  Windows                           |'
    if (on_root) write (stdout, '(1x,a)') &
      '|                          -----------------------                           |'
    if (dis_manifold%proj_auto) then
      if (on_root) write (stdout, '(1x,a,i2,a)') &
        '|         Thresholds determined automatically (multi-Otsu, ', &
        nclasses_eff, ' classes)       |'
    end if
    if (on_root) write (stdout, '(1x,a,f10.5,a,f10.5,a)') &
      '|               Discarded: ', 0.0_dp, '  to ', dis_manifold%proj_min, &
      '                         |'
    if (on_root) write (stdout, '(1x,a,f10.5,a,f10.5,a)') &
      '|            Disentangled: ', dis_manifold%proj_min, '  to ', 1.0_dp, &
      '                         |'
    if (dis_manifold%frozen_proj) then
      if (on_root) write (stdout, '(1x,a,f10.5,a,f10.5,a)') &
        '|                  Frozen: ', dis_manifold%proj_max, '  to ', 1.0_dp, &
        '                         |'
    else
      if (on_root) write (stdout, '(1x,a)') &
        '|                   No frozen states were specified                          |'
    end if
    if (on_root) write (stdout, '(1x,a)') &
      '+----------------------------------------------------------------------------+'

    do nkp = 1, num_kpts
      ! Generate the projectability array
      projs = 0.0_dp
      do i = 1, num_bands
        do j = 1, num_wann
          projs(i) = projs(i) + real(a_matrix(i, j, nkp), dp)**2 + aimag(a_matrix(i, j, nkp))**2
        end do
        if ((projs(i) < 0.0_dp) .or. (projs(i) > 1.0_dp)) then
          if (on_root) write (stdout, *) ' Error at k-point: ', nkp, '  band: ', i
          if (on_root) write (stdout, 411) (eigval_opt(j, nkp), j=1, num_bands)
411       format('Bands (eV): ', 10(F10.5, 1X))
          if (on_root) write (stdout, 412) (projs(j), j=1, num_bands)
412       format('Projectability: ', 10(F10.5, 1X))
          call set_error_fatal(error, 'dis_windows_proj: projectability < 0.0 or > 1.0', comm)
          return
        end if
      end do

      ! Check which eigenvalues fall within the inner/outer windows
      !
      ! Inside this subroutine, I just use indxkeep which is more flexible that
      ! allows me to exclude states in the middle of the outer window; outside
      ! of this subroutine, since I have already slimmed down the a_matrix
      ! and m_matrix, so nfirstwin = 1 is fine.
      nfirstwin(nkp) = 1
      indxkeep(:, nkp) = 0
      indxfroz(:, nkp) = 0
      indxnfroz(:, nkp) = 0
      lfrozen(:, nkp) = .false.
      dis_manifold%lwindow(:, nkp) = .false.
      j = 0 ! counter for all keep states
      k = 0 ! counter for frozen states
      l = 0 ! counter for non-frozen states
      do i = 1, num_bands
        ! exclude states outside disentanglement energy window
        if ((eigval_opt(i, nkp) < dis_manifold%win_min) .or. &
            (eigval_opt(i, nkp) > dis_manifold%win_max)) cycle
        ! freeze high-proj states + states inside frozen energy window, i.e. their union
        if ((projs(i) >= dis_manifold%proj_max) .or. &
            (dis_manifold%frozen_states .and. ((eigval_opt(i, nkp) >= dis_manifold%froz_min) &
                                               .and. (eigval_opt(i, nkp) <= dis_manifold%froz_max)))) then
          j = j + 1
          ! Inside outer window, relative to bottom of outer window, however the bottom is 1
          indxkeep(j, nkp) = i
          k = k + 1
          indxfroz(k, nkp) = i
          lfrozen(j, nkp) = .true.
          ! Relative to the total num_bands
          dis_manifold%lwindow(i, nkp) = .true.
        else if ((projs(i) >= dis_manifold%proj_min) .and. (projs(i) < dis_manifold%proj_max)) then
          j = j + 1
          indxkeep(j, nkp) = i
          l = l + 1
          indxnfroz(l, nkp) = i
          ! Relative to the total num_bands
          dis_manifold%lwindow(i, nkp) = .true.
        end if
      end do
      dis_manifold%ndimwin(nkp) = j
      ndimfroz(nkp) = k

      if (j /= k + l) then
        if (on_root) write (stdout, *) ' ERROR AT K-POINT: ', nkp
        if (on_root) write (stdout, '(3(a,i5))') ' ndimwin: ', dis_manifold%ndimwin(nkp), &
          ' ndimfroz: ', ndimfroz(nkp), ' ndimnfroz: ', l
        call set_error_fatal(error, 'dis_windows_proj: (ndimfroz + ndimnfroz) /= ndimwin at this k-point', comm)
        return
      end if

      if (dis_manifold%ndimwin(nkp) == 0) then
        if (on_root) write (stdout, *) ' ERROR AT K-POINT: ', nkp
        if (on_root) write (stdout, *) ' dis_proj_min:     ', dis_manifold%proj_min
        if (on_root) write (stdout, *) ' EIGENVALUE projectability: [', &
          minval(projs), ', ', maxval(projs), ']'
        call set_error_fatal(error, 'dis_windows_proj: The outer energy window contains no eigenvalues' &
                             //', consider decreasing dis_proj_min/increasing dis_win_max?', comm)
        return
      end if

      if (dis_manifold%ndimwin(nkp) < num_wann) then
        if (on_root) write (stdout, '(1x,a17,i4,a8,i3,a9,i3)') 'ERROR AT K-POINT ', &
          nkp, '  ndimwin=', dis_manifold%ndimwin(nkp), '  num_wann=', num_wann
        if (on_root) write (stdout, '(a)') 'Bands (eV):'
        if (on_root) write (stdout, 411) (eigval_opt(j, nkp), j=1, num_bands)
        if (on_root) write (stdout, '(a)') 'Projectability:'
        if (on_root) write (stdout, 412) (projs(j), j=1, num_bands)
        call set_error_fatal(error, 'dis_windows_proj: Energy window contains fewer states than number of target WFs' &
                             //', consider decreasing dis_proj_min/increasing dis_win_max?', comm)
        return
      end if

      if (ndimfroz(nkp) > num_wann) then
        if (on_root) write (stdout, 413) nkp, ndimfroz(nkp), num_wann
413     format(' ERROR AT K-POINT ', i4, ' THERE ARE ', i2, &
               ' BANDS INSIDE THE INNER WINDOW AND ONLY', i2, &
               ' TARGET BANDS')
        if (on_root) write (stdout, 414) (eigval_opt(indxfroz(i, nkp), nkp), i=1, ndimfroz(nkp))
414     format('BANDS: (eV)', 10(F10.5, 1X))
        call set_error_fatal(error, 'dis_windows_proj: More states in the frozen window than target WFs', comm)
        return
      end if

      if (ndimfroz(nkp) > 0) linner = .true.
      ! DEBUG
      !         write(*,'(a,i4,a,i2,a,i2)') 'k point ',nkp,
      !     &    ' lowest band in outer win is # ',imin,
      !     &    '   # frozen states is ',ndimfroz(nkp)
      ! ENDDEBUG

      ! Slim down eigval vector at present k, i.e. remove low-projectability states
      do i = 1, dis_manifold%ndimwin(nkp)
        j = indxkeep(i, nkp)
        if (j == i) cycle
        eigval_opt(i, nkp) = eigval_opt(j, nkp)
      end do
      eigval_opt(dis_manifold%ndimwin(nkp) + 1:num_bands, nkp) = 0.0_dp
      ! slim down a_matrix
      if (dis_manifold%ndimwin(nkp) .ne. num_bands) then
        do j = 1, num_wann
          do i = 1, dis_manifold%ndimwin(nkp)
            a_matrix(i, j, nkp) = a_matrix(indxkeep(i, nkp), j, nkp)
          end do
          a_matrix(dis_manifold%ndimwin(nkp) + 1:num_bands, j, nkp) = cmplx_0
        end do
      end if
      ! No need to slim u_matrix_opt, it is calculated from a_matrix in dis_project().
      ! Reorder the indexes.
      ! Find the reverse mapping.
      invindxkeep = 0
      do i = 1, dis_manifold%ndimwin(nkp)
        invindxkeep(indxkeep(i, nkp)) = i
      end do
      ! Reorder the indexes, to remove low-projectability states (which are excluded
      ! from disentanglement) that are in the middle of high-projectability states
      ! (which are included in disentanglement). So now these indexes are for the
      ! slimmed matrices, instead of the original num_bands-sized matrices.
      do i = 1, ndimfroz(nkp)
        j = indxfroz(i, nkp)
        indxfroz(i, nkp) = invindxkeep(j)
      end do
      indxfroz((ndimfroz(nkp) + 1):num_bands, nkp) = 0
      do i = 1, dis_manifold%ndimwin(nkp) - ndimfroz(nkp)
        j = indxnfroz(i, nkp)
        indxnfroz(i, nkp) = invindxkeep(j)
      end do
      indxnfroz((dis_manifold%ndimwin(nkp) - ndimfroz(nkp) + 1):num_bands, nkp) = 0

    end do
    ! [k-point loop (nkp)]

    ! slim down m_matrix since some states in the middle might be removed due to
    ! low projectability. I remove them here so that I can use the local variable
    ! indxkeep, then I will skip the internal_slim_m step which is for removing
    ! states outside of energy outer window.
    !
    ! slim down m_matrix_orig_local
    ! this is done outside of previous do loop since we need indxkeep on nn kpoint nkp2
    do nkp = 1, count(dist_k(:) == my_node_id)
      nkp_global = global_k(nkp)

      do nn = 1, nntot
        nkp2 = nnlist(nkp_global, nn)
        do j = 1, dis_manifold%ndimwin(nkp2)
          do i = 1, dis_manifold%ndimwin(nkp_global)
            m_matrix_orig_local(i, j, nn, nkp) = m_matrix_orig_local( &
                                                 indxkeep(i, nkp_global), indxkeep(j, nkp2), nn, nkp)
          end do
        end do
        m_matrix_orig_local(dis_manifold%ndimwin(nkp_global) + 1:num_bands, &
                            dis_manifold%ndimwin(nkp2) + 1:num_bands, nn, nkp) = cmplx_0
      end do
    end do

    if (iprint > 1) then
      if (on_root) write (stdout, '(1x,a)') &
        '|                        K-points with Frozen States                         |'
      if (on_root) write (stdout, '(1x,a)') &
        '|                        ---------------------------                         |'
      i = 0
      do nkp = 1, num_kpts
        if (ndimfroz(nkp) .gt. 0) then
          i = i + 1
          if (i .eq. 1) then
            if (on_root) write (stdout, '(1x,a,i6)', advance='no') '|', nkp
          else if ((i .gt. 1) .and. (i .lt. 12)) then
            if (on_root) write (stdout, '(i6)', advance='no') nkp
          else if (i .eq. 12) then
            if (on_root) write (stdout, '(i6,a)') nkp, '    |'
            i = 0
          end if
        end if
      end do
      if (i .ne. 0) then
        do j = 1, 12 - i
          if (on_root) write (stdout, '(6x)', advance='no')
        end do
        if (on_root) write (stdout, '(a)') '    |'
      end if
      if (on_root) write (stdout, '(1x,a)') &
        '+----------------------------------------------------------------------------+'
    end if

    if (on_root) write (stdout, '(3x,a,i4)') 'Number of target bands to extract: ', num_wann
    if (iprint > 1) then
      if (on_root) write (stdout, '(1x,a)') &
        '+----------------------------------------------------------------------------+'
      if (on_root) write (stdout, '(1x,a)') &
        '|                                  Windows                                   |'
      if (on_root) write (stdout, '(1x,a)') &
        '|                                  -------                                   |'
      if (on_root) write (stdout, '(1x,a)') &
        '|               K-point      Ndimwin     Ndimfroz    Nfirstwin               |'
      if (on_root) write (stdout, '(1x,a)') &
        '|               ----------------------------------------------               |'
      do nkp = 1, num_kpts
        if (on_root) write (stdout, 415) nkp, dis_manifold%ndimwin(nkp), ndimfroz(nkp), nfirstwin(nkp)
      end do
415   format(1x, '|', 14x, i6, 7x, i6, 7x, i6, 6x, i6, 18x, '|')
      if (on_root) write (stdout, '(1x,a)') &
        '+----------------------------------------------------------------------------+'
    end if

    if (iprint > 2) then
      if (on_root) then
        write (stdout, '(1x,a)') &
          '+----------------------------------------------------------------------------+'
        write (stdout, '(1x,a)') &
          '|                         Kept bands at each K-point                         |'
        write (stdout, '(1x,a)') &
          '|               ----------------------------------------------               |'
        do nkp = 1, num_kpts
          write (stdout, '(1x,"|")', advance="no")
          do i = 1, dis_manifold%ndimwin(nkp)
            write (stdout, '(1x,i0)', advance="no") indxkeep(i, nkp)
          end do
          write (stdout, '(1x,"|")')
        end do
        write (stdout, '(1x,a)') &
          '+----------------------------------------------------------------------------+'
      end if
    end if

    if (iprint > 2) then
      if (on_root) then
        write (stdout, '(1x,a)') &
          '+----------------------------------------------------------------------------+'
        write (stdout, '(1x,a)') &
          '|                       Frozen bands at each K-point                         |'
        write (stdout, '(1x,a)') &
          '|               ----------------------------------------------               |'
        do nkp = 1, num_kpts
          write (stdout, '(1x,"|")', advance="no")
          do i = 1, ndimfroz(nkp)
            write (stdout, '(1x,i0)', advance="no") indxfroz(i, nkp)
          end do
          write (stdout, '(1x,"|")')
        end do
        write (stdout, '(1x,a)') &
          '+----------------------------------------------------------------------------+'
      end if
    end if

    if (timing_level > 1) call io_stopwatch_stop('dis: windows_proj', timer)

    return
  end subroutine dis_windows_proj

  subroutine dis_project(a_matrix, u_matrix_opt, ndimwin, nfirstwin, num_bands, num_kpts, &
                         num_wann, timing_level, on_root, iprint, timer, error, stdout, comm)
    !================================================!
    !
    !! Construct projections for the start of the disentanglement routine
    !!
    !! Original notes from Nicola (refers only to the square case)
    !!
    !! This subroutine calculates the transformation matrix
    !! CU = CS^(-1/2).CA, where CS = CA.CA^dagger.
    !! CS is diagonalized with a Schur factorization, to be on the safe
    !! side of numerical stability.
    !!
    !! ZGEES computes for an N-by-N complex nonsymmetric matrix Y, the
    !! eigenvalues, the Schur form T, and, optionally, the matrix of
    !! Schur vectors. This gives the Schur factorization Y = Z*T*(Z**H).
    !!
    !! Optionally, it also orders the eigenvalues on the diagonal of the
    !! Schur form so that selected eigenvalues are at the top left.
    !! The leading components of Z then form an orthonormal basis for
    !! the invariant subspace corresponding to the selected eigenvalues.
    !!
    !! A complex matrix is in Schur form if it is upper triangular.
    !!
    !! Notes from Ivo disentangling (e.g. non-square) projection
    !! (See Sec. III.D of SMV paper)
    !! Compute the ndimwin(k) x num_wann matrix cu that yields,
    !! from the ndimwin original Bloch states, the num_wann Bloch-like
    !! states with maximal projection onto the num_wann localised
    !! functions:
    !!
    !! CU = CA.CS^{-1/2}, CS = transpose(CA).CA
    !!
    !! Use the singular-calue decomposition of the matrix CA:
    !!
    !! CA = CZ.CD.CVdagger (note: zgesvd spits out CVdagger)
    !!
    !! which yields
    !!
    !! CU = CZ.CD.CD^{-1}.CVdag
    !!
    !! where CZ is ndimwin(NKP) x ndimwin(NKP) and unitary, CD is
    !! ndimwin(NKP) x num_wann and diagonal, CD^{-1} is
    !! num_wann x num_wann and diagonal, and CVdag is
    !! num_wann x num_wann and unitary.
    !!
    !================================================!
    use w90_constants, only: dp, cmplx_0, cmplx_1, eps5
    use w90_error
    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
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: ndimwin(:), nfirstwin(:)
    integer, intent(in) :: num_bands, num_kpts, num_wann
    integer, intent(in) :: stdout, timing_level, iprint

    complex(kind=dp), intent(inout) :: a_matrix(:, :, :)
    complex(kind=dp), intent(inout) :: u_matrix_opt(:, :, :)

    logical, intent(in) :: on_root

    ! local variables
    integer :: i, j, l, m, nkp, info, ierr

    real(kind=dp), allocatable :: svals(:)
    real(kind=dp), allocatable :: rwork(:)

    complex(kind=dp) :: ctmp2
    complex(kind=dp), allocatable :: cwork(:)
    complex(kind=dp), allocatable :: cz(:, :)
    complex(kind=dp), allocatable :: cvdag(:, :)
!    complex(kind=dp), allocatable :: catmpmat(:,:,:)

    if (timing_level > 1) call io_stopwatch_start('dis: project', timer)

    if (iprint > 0) write (stdout, '(/1x,a)') &
      '                  Unitarised projection of Wannier functions                  '
    if (iprint > 0) write (stdout, '(1x,a)') &
      '                  ------------------------------------------                  '
    if (iprint > 0) write (stdout, '(3x,a)') 'A_mn = <psi_m|g_n> --> S = A.A^+ --> U = S^-1/2.A'
    if (iprint > 0) write (stdout, '(3x,a)', advance='no') 'In dis_project...'

    allocate (svals(num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating svals in dis_project', comm)
      return
    end if
    allocate (rwork(5*num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating rwork in dis_project', comm)
      return
    end if
    allocate (cvdag(num_bands, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cvdag in dis_project', comm)
      return
    end if
    allocate (cz(num_bands, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cz in dis_project', comm)
      return
    end if
    allocate (cwork(4*num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating cwork in dis_project', comm)
      return
    end if
    ! here we slim down the ca matrix
    ! up to here num_bands(=num_bands) X num_wann(=num_wann)
!    do nkp = 1, num_kpts
!       do j = 1, num_wann
!          do i = 1, ndimwin(nkp)
!             catmpmat(i,j,nkp) = a_matrix(nfirstwin(nkp)+i-1,j,nkp)
!          enddo
!       enddo
!       do j = 1, num_wann
!          a_matrix(1:ndimwin(nkp),j,nkp) = catmpmat(1:ndimwin(nkp),j,nkp)
!       enddo
!       do j = 1, num_wann
!          a_matrix(ndimwin(nkp)+1:num_bands,j,nkp) = cmplx_0
!       enddo
!    enddo
    ! in order to reduce the memory usage, we don't use catmpmat.
    do nkp = 1, num_kpts
      if (ndimwin(nkp) .ne. num_bands) then
        do j = 1, num_wann
          do i = 1, ndimwin(nkp)
            ctmp2 = a_matrix(nfirstwin(nkp) + i - 1, j, nkp)
            a_matrix(i, j, nkp) = ctmp2
          end do
          a_matrix(ndimwin(nkp) + 1:num_bands, j, nkp) = cmplx_0
        end do
      end if
    end do

    do nkp = 1, num_kpts
      ! SINGULAR VALUE DECOMPOSITION
      call zgesvd('A', 'A', ndimwin(nkp), num_wann, a_matrix(:, :, nkp), num_bands, svals, cz, &
                  num_bands, cvdag, num_bands, cwork, 4*num_bands, rwork, info)
      if (info .ne. 0) then
        if (on_root) write (stdout, *) ' ERROR: IN ZGESVD IN dis_project'
        if (on_root) write (stdout, *) ' K-POINT NKP=', nkp, ' INFO=', info
        if (info .lt. 0) then
          if (on_root) write (stdout, *) ' THE ', -info, '-TH ARGUMENT HAD ILLEGAL VALUE'
        end if
        call set_error_fatal(error, 'dis_project: problem in ZGESVD 1', comm)
        return
      end if

      ! NOTE THAT - AT LEAST FOR LINUX MKL LAPACK - THE OUTPUT OF ZGESVD
      ! GIVES ALREADY Vdagger, SO A=Z.S.Vdagger IS ACTUALLY GIVEN BY cz.s.cvda
      !
      ! also, cu is cz.cd.cd^-1.cvdag, and the asymmetric structure of cd.cd^-
      ! allows us to say cu=cz.cvdag, but where the sum on the inner index
      ! goes only from 1 to num_wann (i.e. DO l=1,num_wann). This is because
      ! cz.cd.cd^-1 is a matrix ndimwin(nkp) X num_wann, identical to the
      ! first num_wann columns of the ndimwin(nkp) X ndimwin(nkp) matrix cz
      !
      ! same for ca: s is a ndimwin(nkp) X num_wann matrix that is
      ! zero everywhere but for its num_wann X num_wann top square part,
      ! that is diagonal. Multiplying cz by the s matrix is equivalent
      ! to moltiplying the first num_wann columns of cz, each by the correspondin
      ! diagonal element of s, that is s(L)
      ! I'm not sure why we reconstruct ca in what follows - in one explicit t
      ! [ aam: it is because a_matrix is overwritten by ZGESVD ]
      ! it seemed to be identical to the input ca (as it should be)

      u_matrix_opt(:, :, nkp) = cmplx_0
      a_matrix(:, :, nkp) = cmplx_0
      do j = 1, num_wann
        do i = 1, ndimwin(nkp)
          do l = 1, num_wann
            u_matrix_opt(i, j, nkp) = u_matrix_opt(i, j, nkp) + cz(i, l)*cvdag(l, j)
            a_matrix(i, j, nkp) = a_matrix(i, j, nkp) + cz(i, l)*svals(l)*cvdag(l, j)
          end do
        end do
      end do

      !
      ! CHECK UNITARITY
      !
      ! note that cu.transpose(cu) is *NOT* an identity ndimwin(nkp) by ndimwi
      ! matrix, but transpose(cu).cu is a num_wann by num_wann identity matrix.
      ! I have once checked the former statement, now I will just leave here t
      ! for the latter (what this means is that the columns of cu are orthonor
      ! vectors).
      do i = 1, num_wann
        do j = 1, num_wann
          ctmp2 = cmplx_0
          do m = 1, ndimwin(nkp)
            ctmp2 = ctmp2 + u_matrix_opt(m, j, nkp)*conjg(u_matrix_opt(m, i, nkp))
          end do
          if ((i .eq. j) .and. (abs(ctmp2 - cmplx_1) .gt. eps5)) then
            if (on_root) write (stdout, *) ' ERROR: unitarity of initial U'
            if (on_root) write (stdout, '(1x,a,i2)') 'nkp= ', nkp
            if (on_root) write (stdout, '(1x,a,i2,2x,a,i2)') 'i= ', i, 'j= ', j
            if (on_root) write (stdout, '(1x,a,f12.6,1x,f12.6)') &
              '[u_matrix_opt.transpose(u_matrix_opt)]_ij= ', &
              real(ctmp2, dp), aimag(ctmp2)
            call set_error_fatal(error, 'dis_project: Error in unitarity of initial U in dis_project', comm)
            return
          end if
          if ((i .ne. j) .and. (abs(ctmp2) .gt. eps5)) then
            if (on_root) write (stdout, *) ' ERROR: unitarity of initial U'
            if (on_root) write (stdout, '(1x,a,i2)') 'nkp= ', nkp
            if (on_root) write (stdout, '(1x,a,i2,2x,a,i2)') 'i= ', i, 'j= ', j
            if (on_root) write (stdout, '(1x,a,f12.6,1x,f12.6)') &
              '[u_matrix_opt.transpose(u_matrix_opt)]_ij= ', &
              real(ctmp2, dp), aimag(ctmp2)
            call set_error_fatal(error, 'dis_project: Error in unitarity of initial U in dis_project', comm)
            return
          end if
        end do
      end do
    end do
    ! NKP

    deallocate (cwork, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cwork in dis_project', comm)
      return
    end if
    deallocate (cz, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cz in dis_project', comm)
      return
    end if
    deallocate (cvdag, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cvdag in dis_project', comm)
      return
    end if
    deallocate (rwork, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating rwork in dis_project', comm)
      return
    end if
    deallocate (svals, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating svals in dis_project', comm)
      return
    end if

    if (iprint > 0) write (stdout, '(a)') ' done'

    if (timing_level > 1) call io_stopwatch_stop('dis: project', timer)

    return
    !================================================!
  end subroutine dis_project

  subroutine dis_proj_froz(u_matrix_opt, indxfroz, ndimfroz, ndimwin, iprint, num_bands, num_kpts, &
                           num_wann, timing_level, lfrozen, on_root, timer, error, stdout, comm)
    !================================================!
    !
    !! COMPUTES THE LEADING EIGENVECTORS OF Q_froz . P_s . Q_froz,
    !! WHERE P_s PROJECTOR OPERATOR ONTO THE SUBSPACE S OF THE PROJECTED
    !! GAUSSIANS, P_f THE PROJECTOR ONTO THE FROZEN STATES, AND
    !! Q_froz = 1 - P_froz, ALL EXP IN THE BASIS OF THE BLOCH
    !! EIGENSTATES INSIDE THE OUTER ENERGY WINDOW
    !! (See Eq. (27) in Sec. III.G of SMV)
    !
    !================================================!
    use w90_constants, only: dp, cmplx_0, cmplx_1, eps8
    use w90_error
    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
    type(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: timing_level, iprint
    integer, intent(in) :: stdout
    integer, intent(in) :: num_bands, num_kpts, num_wann
    integer, intent(in) :: ndimwin(:)
    integer, intent(in) :: ndimfroz(:)
    integer, intent(in) :: indxfroz(:, :)

    complex(kind=dp), intent(inout) :: u_matrix_opt(:, :, :)

    logical, intent(in) :: on_root, lfrozen(:, :)

    ! INPUT: num_wann,ndimwin,ndimfroz,indxfroz,lfrozen
    ! MODIFIED: u_matrix_opt (At input it contains the gaussians projected onto
    !             the window states in the routine project.f. At output
    !             the entries with the second index from 1 to ndimfroz(nkp)
    !             contain the frozen (inner window) states, while those
    !             from ndimfroz(nkp)+1 to num_wann have been replaced by
    !             the new trial states outside the inner window.)

    ! *********************************************************
    ! VARIABLES USED BY LAPACK'S ZHPEVX DIAGONALIZATION ROUTINE
    ! *********************************************************
    integer, allocatable :: iwork(:)
    integer, allocatable :: ifail(:)

    real(kind=dp), allocatable :: w(:)
    real(kind=dp), allocatable :: rwork(:)

    complex(kind=dp), allocatable :: cap(:)
    complex(kind=dp), allocatable :: cwork(:)
    complex(kind=dp), allocatable :: cz(:, :)

    ! *********
    ! INTERNAL:
    ! *********
    !
    ! CP_S(M,N)      PROJECTION OPERATOR ONTO THE SUBSPACE OF THE PROJEC
    !                  GAUSSIANS, EXPRESSED IN THE BASIS OF THE ORIGINAL BL
    !                  EIGENSTATES INSIDE THE OUTER WINDOW (FOR THE PRESENT
    !                  K-POINT)
    ! CQ_FROZ(M,N)   PROJECTION OPERATOR ONTO THE SUBSPACE OF THE STATES
    !                  THE SPACE OF FROZEN STATES (BUT INSIDE THE OUTER WIN
    !                  EXPRESSED IN THE BASIS OF THE ORIGINAL BLOCH EIGENST
    !                  INSIDE THE OUTER WINDOW (FOR THE PRESENT K-POINT)
    ! CPQ(M,N)       THE MATRIX cp_s . cq_froz FOR THE PRESENT K-POINT
    ! CQPQ(M,N)      THE MATRIX cq_froz . cp_s . cq_froz FOR THE PRESENT
    !

    integer :: goods, il, iu, nkp, l, j, n, m, info, ierr
    integer :: counter, loop_f, loop_v, vmap(num_bands)
    integer :: nzero

    complex(kind=dp) :: ctmp
    complex(kind=dp), allocatable :: cp_s(:, :)
    complex(kind=dp), allocatable :: cq_froz(:, :)
    complex(kind=dp), allocatable :: cpq(:, :)
    complex(kind=dp), allocatable :: cqpq(:, :)

    logical :: take

    character(len=4) :: rep

    if (timing_level > 1) call io_stopwatch_start('dis: proj_froz', timer)

    if (iprint > 0) write (stdout, '(3x,a)', advance='no') 'In dis_proj_froz...'

    allocate (iwork(5*num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating iwork in dis_proj_froz', comm)
      return
    end if
    allocate (ifail(num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating ifail in dis_proj_froz', comm)
      return
    end if
    allocate (w(num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating w in dis_proj_froz', comm)
      return
    end if
    allocate (rwork(7*num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating rwork in dis_proj_froz', comm)
      return
    end if
    allocate (cap((num_bands*(num_bands + 1))/2), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cap in dis_proj_froz', comm)
      return
    end if
    allocate (cwork(2*num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cwork in dis_proj_froz', comm)
      return
    end if
    allocate (cz(num_bands, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cz in dis_proj_froz', comm)
      return
    end if
    allocate (cp_s(num_bands, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cp_s in dis_proj_froz', comm)
      return
    end if
    allocate (cq_froz(num_bands, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cq_froz in dis_proj_froz', comm)
      return
    end if
    allocate (cpq(num_bands, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cpq in dis_proj_froz', comm)
      return
    end if
    allocate (cqpq(num_bands, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cqpq in dis_proj_froz', comm)
      return
    end if

    do nkp = 1, num_kpts

      ! aam: this should be done at the end, otherwise important
      !      projection info is lost
!~         ! Put the frozen states in the lowest columns of u_matrix_opt
!~         if (ndimfroz(nkp).gt.0) then
!~            do l = 1, ndimfroz(nkp)
!~               u_matrix_opt(:,l,nkp)=cmplx_0
!~               u_matrix_opt(indxfroz(l,nkp),l,nkp) = cmplx_1
!~            enddo
!~         endif

      ! If there are non-frozen states, compute the num_wann-ndimfroz(nkp) leadin
      ! eigenvectors of cqpq
      if (num_wann .gt. ndimfroz(nkp)) then
        cq_froz = cmplx_0
        cp_s = cmplx_0
        do n = 1, ndimwin(nkp)
          do m = 1, ndimwin(nkp)
            do l = 1, num_wann
              cp_s(m, n) = cp_s(m, n) + u_matrix_opt(m, l, nkp)*conjg(u_matrix_opt(n, l, nkp))
            end do
          end do
          if (.not. lfrozen(n, nkp)) cq_froz(n, n) = cmplx_1
        end do

        cpq = cmplx_0
        do n = 1, ndimwin(nkp)
          do m = 1, ndimwin(nkp)
            do l = 1, ndimwin(nkp)
              cpq(m, n) = cpq(m, n) + cp_s(m, l)*cq_froz(l, n)
            end do
          end do
        end do

        cqpq = cmplx_0
        do n = 1, ndimwin(nkp)
          do m = 1, ndimwin(nkp)
            do l = 1, ndimwin(nkp)
              cqpq(m, n) = cqpq(m, n) + cq_froz(m, l)*cpq(l, n)
            end do
          end do
        end do

        ! DEBUG
        ! check hermiticity of cqpq
        do n = 1, ndimwin(nkp)
          do m = 1, n
            if (abs(cqpq(m, n) - conjg(cqpq(n, m))) .gt. eps8) then
              if (on_root) write (stdout, *) ' matrix CQPQ is not hermitian'
              if (on_root) write (stdout, *) ' k-point ', nkp
              call set_error_fatal(error, 'dis_proj_froz: error', comm)
              return
            end if
          end do
        end do
        ! ENDDEBUG

        cap = cmplx_0
        do n = 1, ndimwin(nkp)
          do m = 1, n
            cap(m + (n - 1)*n/2) = cqpq(m, n)
          end do
        end do
        il = ndimwin(nkp) - (num_wann - ndimfroz(nkp)) + 1
        iu = ndimwin(nkp)
        call zhpevx('V', 'A', 'U', ndimwin(nkp), cap, 0.0_dp, 0.0_dp, il, iu, -1.0_dp, m, w, cz, &
                    num_bands, cwork, rwork, iwork, ifail, info)

!~            write(stdout,*) 'w:'
!~            do n=1,ndimwin(nkp)
!~               write(stdout,'(f14.10)') w(n)
!~            enddo
!~            write(stdout,*) 'cz:'
!~            do n=1,ndimwin(nkp)
!~               write(stdout,'(6f12.8)') cz(n,il), cz(n,iu)
!~            enddo

        ! DEBUG
        if (info .lt. 0) then
          if (on_root) write (stdout, *) ' *** ERROR *** ZHPEVX WHILE DIAGONALIZING CQPQ MATRIX'
          if (on_root) write (stdout, *) ' THE ', -info, ' ARGUMENT OF ZHPEVX HAD AN ILLEGAL VALUE'
          call set_error_fatal(error, 'dis_proj_froz: error', comm)
          return
        elseif (info .gt. 0) then
          if (on_root) write (stdout, *) ' *** ERROR *** ZHPEVX WHILE DIAGONALIZING CQPQ MATRIX'
          if (on_root) write (stdout, *) info, 'EIGENVECTORS FAILED TO CONVERGE'
          call set_error_fatal(error, 'dis_proj_froz: error', comm)
          return
        end if
        ! ENDDEBUG

        ! DEBUG
        if (m .ne. ndimwin(nkp)) then
          if (on_root) write (stdout, *) ' *** ERROR *** in dis_proj_froz'
          if (on_root) write (stdout, *) ' Number of eigenvalues/vectors obtained is', &
            m, ' not equal to the number asked,', ndimwin(nkp)
          call set_error_fatal(error, 'dis_proj_froz: error', comm)
          return
        end if
        ! ENDDEBUG

        ! DEBUG
        ! check that the eigenvalues are between 0 and 1
        if (iprint > 2) then
          if (on_root) write (stdout, '(/a,i3,a,i3,a,i3,a)') ' K-point ', nkp, ' ndimwin: ', &
            ndimwin(nkp), ' we want the', num_wann - ndimfroz(nkp), &
            ' leading eigenvector(s) of QPQ'
        end if
        do j = 1, ndimwin(nkp)
          if (iprint > 2 .and. on_root) write (stdout, '(a,i3,a,f16.12)') '  lambda(', j, ')=', w(j)
!~[aam]        if ( (w(j).lt.eps8).or.(w(j).gt.1.0_dp + eps8) ) then
          if ((w(j) .lt. -eps8) .or. (w(j) .gt. 1.0_dp + eps8)) then
            call set_error_fatal(error, 'dis_proj_froz: error - Eigenvalues not between 0 and 1', comm)
            return
          end if
        end do
        ! ENDDEBUG

        ! [ aam: sometimes the leading eigenvalues form a degenerate set that is
        !        of higher dimensionality than (num_wann - ndimfroz). May need to
        !        fix this at some point. ]

        ! For certain cases we have found that one of the required eigenvectors of cqpq
        ! has a zero eigenvalue (ie it forms a degenerate set with the frozen states).
        ! It depends on floating point math as whether this eigenvalue is positive
        ! or negative (ie +/- 1e-17). If it's positive everything is ok. If negative we
        ! can end up putting one of the frozen states into U_opt (and failing the later
        ! orthogonality check).
        ! This fix detects this situation. If applies we choose the eigenvectors by
        ! checking their orthogonality to the frozen states.
        ! === For version 1.0.1 we make this the default ===

        !if (index(devel_flag, 'no-orth-fix') == 0) then
        nzero = 0; goods = 0
        do j = ndimwin(nkp), ndimwin(nkp) - (num_wann - ndimfroz(nkp)) + 1, -1
          if (w(j) < eps8) then
            nzero = nzero + 1
          else
            goods = goods + 1
          end if
        end do
        if (nzero > 0) then
          if (iprint > 2 .and. on_root) then
            write (stdout, *) ' '
            write (stdout, '(1x,a,i0,a)') 'An eigenvalue of QPQ is close to zero at kpoint ', &
              nkp, '. Using safety check.'
            write (stdout, '(1x,a,i4,a,i4)') 'We must find ', nzero, &
              ' eigenvectors with zero eigenvalues out of a set of ', ndimwin(nkp) - goods
          end if
          !First lets put the 'good' states into vamp
          vmap = 0
          counter = 1
          do j = ndimwin(nkp), ndimwin(nkp) - goods + 1, -1
            vmap(counter) = j
            counter = counter + 1
          end do

          if (iprint > 2 .and. on_root) then
            do loop_f = 1, ndimwin(nkp)
              write (stdout, '(1x,a,i4,a,es13.6)') 'Eigenvector number', loop_f, &
                '    Eigenvalue: ', w(loop_f)
              do loop_v = 1, ndimwin(nkp)
                write (stdout, '(20x,2f12.8)') cz(loop_v, loop_f)
              end do
              write (stdout, *)
            end do
          end if

          ! We need to find nzero vectors out of the remining ndimwin(nkp)-goods vectors

          do loop_f = 1, nzero
            do loop_v = ndimwin(nkp), 1, -1 !loop backwards for efficiency only
              if (any(vmap == loop_v)) cycle
              !check to see if vector is orthogonal to frozen states in u_matrix_opt
              take = .true.
              do m = 1, ndimfroz(nkp)
                ctmp = cmplx_0
                do j = 1, ndimwin(nkp)
                  ctmp = ctmp + conjg(u_matrix_opt(j, m, nkp))*cz(j, loop_v)
                end do
                if (abs(ctmp) .gt. eps8) then
                  take = .false.
                end if
              end do
              if (take) then !vector is good and we add it to vmap
                vmap(goods + loop_f) = loop_v
                exit
              end if
            end do
          end do

          if (iprint > 2 .and. on_root) then
            write (rep, '(i4)') num_wann - ndimfroz(nkp)
            write (stdout, '(1x,a,'//trim(rep)//'(i0,1x))') &
              'We use the following eigenvectors: ', vmap(1:(num_wann - ndimfroz(nkp)))
          end if
          do l = 1, num_wann - ndimfroz(nkp)
            if (vmap(l) == 0) then
              call set_error_fatal(error, 'dis_proj_froz: Ortho-fix failed to find enough vectors', comm)
              return
            end if
          end do

          ! put the correct eigenvectors into u_matrix_opt, and we're all done!
          counter = 1
          do l = ndimfroz(nkp) + 1, num_wann
            u_matrix_opt(1:ndimwin(nkp), l, nkp) = cz(1:ndimwin(nkp), vmap(counter))
            counter = counter + 1
          end do

        else ! we don't need to use the fix

          do l = ndimfroz(nkp) + 1, num_wann
            u_matrix_opt(1:ndimwin(nkp), l, nkp) = cz(1:ndimwin(nkp), il)
            il = il + 1
          end do

          if (il - 1 .ne. iu) then
            call set_error_fatal(error, 'dis_proj_froz: error -  il-1.ne.iu  (in ortho-fix)', comm)
            return
          end if

        end if

        ! PICK THE num_wann-nDIMFROZ(NKP) LEADING EIGENVECTORS AS TRIAL STATES
        ! and PUT THEM RIGHT AFTER THE FROZEN STATES IN u_matrix_opt
        !do l = ndimfroz(nkp) + 1, num_wann
        !  if (on_root) write (stdout, *) 'il=', il
        !  u_matrix_opt(1:ndimwin(nkp), l, nkp) = cz(1:ndimwin(nkp), il)
        !  il = il + 1
        !enddo

        ! DEBUG
        !if (il - 1 .ne. iu) then
        !  call io_error('dis_proj_frozen: error -  il-1.ne.iu', stdout, seedname)
        !endif
        ! ENDDEBUG

        !end if

      end if   ! num_wann>nDIMFROZ(NKP)

      ! Put the frozen states in the lowest columns of u_matrix_opt
      if (ndimfroz(nkp) .gt. 0) then
        do l = 1, ndimfroz(nkp)
          u_matrix_opt(:, l, nkp) = cmplx_0
          u_matrix_opt(indxfroz(l, nkp), l, nkp) = cmplx_1
        end do
      end if
    end do   ! NKP

    deallocate (cqpq, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cqpq in dis_proj_froz', comm)
      return
    end if
    deallocate (cpq, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cpq in dis_proj_froz', comm)
      return
    end if
    deallocate (cq_froz, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cq_froz in dis_proj_froz', comm)
      return
    end if
    deallocate (cp_s, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cp_s in dis_proj_froz', comm)
      return
    end if
    deallocate (cz, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cz in dis_proj_froz', comm)
      return
    end if
    deallocate (cwork, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cwork in dis_proj_froz', comm)
      return
    end if
    deallocate (cap, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating cap in dis_proj_froz', comm)
      return
    end if
    deallocate (rwork, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating rwork in dis_proj_froz', comm)
      return
    end if
    deallocate (w, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating w in dis_proj_froz', comm)
      return
    end if
    deallocate (ifail, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating ifail in dis_proj_froz', comm)
      return
    end if
    deallocate (iwork, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating iwork in dis_proj_froz', comm)
      return
    end if

    if (iprint > 0) write (stdout, '(a)') ' done'

    if (timing_level > 1) call io_stopwatch_stop('dis: proj_froz', timer)

    return
    !================================================!
  end subroutine dis_proj_froz

  subroutine dis_extract(dis_control, kmesh_info, sitesym, print_output, dis_manifold, &
                         m_matrix_orig_local, u_matrix_opt, eigval_opt, omega_invariant, &
                         indxnfroz, ndimfroz, my_node_id, num_bands, num_kpts, num_wann, &
                         lsitesymmetry, timer, ranknk, global_k, error, stdout, comm)
    !================================================!
    !
    !! Extracts an num_wann-dimensional subspace at each k by
    !! minimizing Omega_I
    !
    !================================================!

    ! MODIFIED:
    !           u_matrix_opt (At input it contains the initial guess for the optima
    ! subspace (expressed in terms of the original states inside the window)
    ! output it contains the  states that diagonalize the hamiltonian inside
    ! optimal subspace (again expressed in terms of the original window stat
    ! Giving out states that diagonalize the hamiltonian inside the optimal
    ! subspace (instead of the eigenstates of the Z matrix) is useful for
    ! performing the Wannier interpolation of the energy bands as described
    ! Sec. III.F of SMV)
    !
    !           eigval (At input: original energy eigenvalues.
    ! At output: eigenvalues of the hamiltonian inside optimal subspace)

    ! ----------------------------------------------------------------------
    ! TO DO: The complement subspace is computed but is not saved anywhere!
    ! (Check what was done with it in original code space.f)
    ! Diagonalize Z matrix only at those k points where ndimwin>num_wann?
    ! ----------------------------------------------------------------------

    ! *******************
    ! SHELLS OF K-VECTORS
    ! *******************
    ! nshells           number of shells of k-points to be used in the
    !                   finite-difference formulas for the k-derivatives
    ! aam: wb is now wb(1:nntot) 09/04/2006
    ! wb(nkp,nnx)       weight of the nnx-th b-vector (ordered along shells
    !                   of increasing length) associated with the nkp-th k-p
    ! wbtot             sum of the weights of all b-vectors associated with
    !                   given k-point (k-point 1 is used in calculation)
    ! nnlist(nkp,nnx)   vkpt(1:3,nnlist(nkp,nnx)) is the nnx-th neighboring
    !                   k-point of the nkp-th k-point vkpt(1:3,nkp) (or its
    !                   periodic image in the "home Brillouin zone")
    ! cm(n,m,nkp,nnx)   Overlap matrix <u_nk|u_{m,k+b}>
    use w90_comms, only: comms_bcast, comms_allreduce, w90_comm_type, mpirank
    use w90_constants, only: dp, cmplx_0, cmplx_1
    use w90_error
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop, io_wallclocktime
    use w90_sitesym, only: sitesym_symmetrize_u_matrix, sitesym_symmetrize_zmatrix, &
                           sitesym_dis_extract_symmetry
    use w90_types, only: dis_manifold_type, kmesh_info_type, print_output_type, timer_list_type
    use w90_wannier90_types, only: dis_control_type, dis_spheres_type, sitesym_type

    implicit none

    ! arguments
    type(dis_control_type), intent(in) :: dis_control
    type(dis_manifold_type), intent(in) :: dis_manifold
    type(kmesh_info_type), intent(in) :: kmesh_info
    type(print_output_type), intent(in) :: print_output
    type(sitesym_type), intent(in) :: sitesym
    type(timer_list_type), intent(inout) :: timer
    type(w90_comm_type), intent(in) :: comm
    type(w90_error_type), allocatable, intent(out) :: error

    integer, intent(in) :: my_node_id
    integer, intent(in) :: stdout
    integer, intent(in) :: num_bands, num_kpts, num_wann
    integer, intent(in) :: ndimfroz(:) ! (num_kpts)
    integer, intent(in) :: indxnfroz(:, :) ! (num_bands,num_kpts)
    integer, intent(in) :: ranknk, global_k(:)

    real(kind=dp), intent(inout) :: eigval_opt(:, :)
    real(kind=dp), intent(out) :: omega_invariant

    complex(kind=dp), intent(inout) :: m_matrix_orig_local(:, :, :, :)
    complex(kind=dp), intent(inout) :: u_matrix_opt(:, :, :)

    logical, intent(in) :: lsitesymmetry

    ! Internal variables
    integer :: i, j, l, m, n, nn, nkp, nkp2, info, ierr, ndimk, p
    integer :: icompflag, iter, ndiff
    real(kind=dp) :: womegai, wkomegai, womegai1, rsum, delta_womegai
    real(kind=dp), allocatable :: wkomegai1(:)
    real(kind=dp), allocatable :: history(:)
    real(kind=dp), allocatable :: rwork(:)
    real(kind=dp), allocatable :: w(:)
    real(kind=dp), allocatable :: wkomegai1_loc(:)

    complex(kind=dp), allocatable :: camp_loc(:, :, :)
    complex(kind=dp), allocatable :: u_matrix_opt_loc(:, :, :)
    complex(kind=dp), allocatable :: ceamp(:, :, :) ! (alloc on root rank only)
    complex(kind=dp), allocatable :: camp(:, :, :)
    complex(kind=dp), allocatable :: czmat_in(:, :, :)
    complex(kind=dp), allocatable :: czmat_out(:, :, :)
    ! the z-matrices are now stored in local arrays
    complex(kind=dp), allocatable :: czmat_in_loc(:, :, :)
    complex(kind=dp), allocatable :: czmat_out_loc(:, :, :)
    complex(kind=dp), allocatable :: cham(:, :, :) ! (alloc on root rank only)

    complex(kind=dp), allocatable :: cap(:)
    complex(kind=dp), allocatable :: cwb(:, :), cww(:, :), cbw(:, :)
    complex(kind=dp), allocatable :: cwork(:)
    complex(kind=dp), allocatable :: cz(:, :)
    complex(kind=dp) :: lambda(num_wann, num_wann) !RS:

    integer, allocatable :: ifail(:)
    integer, allocatable :: iwork(:)
    integer :: nkp_loc

    logical :: dis_converged
    logical :: on_root = .false.

    on_root = (my_node_id == 0)

    if (print_output%timing_level > 1) call io_stopwatch_start('dis: extract', timer)

    if (print_output%iprint > 0) write (stdout, '(/1x,a)') &
      '                  Extraction of optimally-connected subspace                  '
    if (print_output%iprint > 0) write (stdout, '(1x,a)') &
      '                  ------------------------------------------                  '

    allocate (cwb(num_wann, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cwb in dis_extract', comm)
      return
    end if
    allocate (cww(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cww in dis_extract', comm)
      return
    end if
    allocate (cbw(num_bands, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cbw in dis_extract', comm)
      return
    end if
    allocate (iwork(5*num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating iwork in dis_extract', comm)
      return
    end if
    allocate (ifail(num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating ifail in dis_extract', comm)
      return
    end if
    allocate (w(num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating w in dis_extract', comm)
      return
    end if
    allocate (rwork(7*num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating rwork in dis_extract', comm)
      return
    end if
    allocate (cap((num_bands*(num_bands + 1))/2), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cap in dis_extract', comm)
      return
    end if
    allocate (cwork(2*num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cwork in dis_extract', comm)
      return
    end if
    allocate (cz(num_bands, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cz in dis_extract', comm)
      return
    end if
    allocate (u_matrix_opt_loc(num_bands, num_wann, ranknk), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating u_matrix_opt_loc in dis_extract', comm)
      return
    end if
    allocate (wkomegai1_loc(ranknk), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating wkomegai1_loc in dis_extract', comm)
      return
    end if
    allocate (czmat_in_loc(num_bands, num_bands, ranknk), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating czmat_in_loc in dis_extract', comm)
      return
    end if
    allocate (czmat_out_loc(num_bands, num_bands, ranknk), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating czmat_out_loc in dis_extract', comm)
      return
    end if
    allocate (wkomegai1(num_kpts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating wkomegai1 in dis_extract', comm)
      return
    end if
    if (lsitesymmetry) then !we only need these large arrays for the symmetry code
      allocate (czmat_in(num_bands, num_bands, num_kpts), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error allocating czmat_in in dis_extract', comm)
        return
      end if
      allocate (czmat_out(num_bands, num_bands, num_kpts), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error allocating czmat_out in dis_extract', comm)
        return
      end if
    end if
    allocate (history(dis_control%conv_window), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating history in dis_extract', comm)
      return
    end if

    cwb = cmplx_0; cww = cmplx_0; cbw = cmplx_0

    ! Copy matrix elements from global U matrix to local U matrix
    do nkp_loc = 1, ranknk
      nkp = global_k(nkp_loc)
      u_matrix_opt_loc(:, :, nkp_loc) = u_matrix_opt(:, :, nkp)
    end do

    ! ********************************************
    ! ENERGY WINDOWS AND SUBSPACES AT EACH K-POINT
    ! ********************************************
    ! num_wann             dimensionality of the subspace at each k-point
    !                   (number of Wannier functions per unit cell that we w
    ! NDIMWIN(NKP)      number of bands at the nkp-th k-point that fall
    !                   within the outer energy window
    ! NDIMFROZ(NKP)     number of frozen bands at the nkp-th k-point
    ! INDXNFROZ(I,NKP)  INDEX (BETWEEN 1 AND NDIMWIN(NKP)) OF THE I-TH NON-F
    !                   ORIGINAL BAND STATE AT THE NKP-TH K-POINT
    ! U_MATRIX_OPT(J,L,NKP)    AMPLITUDE OF THE J-TH ENERGY EIGENVECTOR INSIDE THE
    !                   ENERGY WINDOW AT THE NKP-TH K-POINT IN THE EXPANSION
    !                   THE L-TH LEADING RLAMBDA EIGENVECTOR AT THE SAME K-P
    !                   If there are M_k frozen states, they occupy the lowe
    !                   entries of the second index of u_matrix_opt, and the leadin
    !                   nabnds-M_k eigenvectors of the Z matrix occupy the
    !                   remaining slots
    ! CAMP(J,L,NKP)     SAME AS U_MATRIX_OPT, BUT FOR THE COMPLEMENT SUBSPACE INSID
    !                   ENERGY WINDOW (I.E., THE NON-LEADING RLAMBDA EIGENVE
    ! CEAMP(J,L,NKPTS)  SAME AS U_MATRIX_OPT, BUT INSTEAD OF RLAMBDA EIGENVECTOR, I
    !                   FOR THE ENERGY EIGENVECTOR OBTAINED BY DIAGONALIZING
    !                   HAMILTONIAN IN THE OPTIMIZED SUBSPACE
    ! CZMAT_IN(M,N,NKP) Z-MATRIX [Eq. (21) SMV]
    ! CZMAT_OUT(M,N,NKP) OUTPUT Z-MATRIX FROM THE PRESENT ITERATION
    ! RLAMBDA           An eigenvalue of the Z matrix
    ! womegai           Gauge-invariant Wannier spread, computed usinf all s
    !                   from current iteration
    ! wkomegai1(NKP)    Eq. (18) of SMV
    ! womegai1          Eq.(11) of SMV (like wowmegai, but neighboring state
    !                   for computing overlaps are from previous iteration.
    !                   become equal at self-consistency)
    ! alphafixe         mixing parameter for the iterative procedure
    ! nitere            total number of iterations

    ! DEBUG
    if (print_output%iprint > 2) then
      if (on_root) then
        write (stdout, '(a,/)') '  Original eigenvalues inside outer window:'
        do nkp = 1, num_kpts
          write (stdout, '(a,i3,3x,20(f9.5,1x))') '  K-point ', nkp, &
            (eigval_opt(i, nkp), i=1, dis_manifold%ndimwin(nkp))
        end do
      end if
    end if
    ! ENDDEBUG

    ! TO DO: Check if this is the best place to initialize icompflag
    icompflag = 0

    if (print_output%iprint > 0) write (stdout, '(1x,a)') &
      '+---------------------------------------------------------------------+<-- DIS'
    if (print_output%iprint > 0) write (stdout, '(1x,a)') &
      '|  Iter     Omega_I(i-1)      Omega_I(i)      Delta (frac.)    Time   |<-- DIS'
    if (print_output%iprint > 0) write (stdout, '(1x,a)') &
      '+---------------------------------------------------------------------+<-- DIS'

    dis_converged = .false.

    ! ------------------
    ! BIG ITERATION LOOP
    ! ------------------
    do iter = 1, dis_control%num_iter

      if (print_output%timing_level > 1) call io_stopwatch_start('dis: extract_1', timer)

      if (iter .eq. 1) then
        ! Initialize Z matrix at k points w/ non-frozen states
        do nkp_loc = 1, ranknk
          nkp = global_k(nkp_loc)
          if (num_wann .gt. ndimfroz(nkp)) then
            call internal_zmatrix(cbw, czmat_in_loc(:, :, nkp_loc), m_matrix_orig_local, &
                                  u_matrix_opt, kmesh_info%wb, indxnfroz, ndimfroz, &
                                  dis_manifold%ndimwin, kmesh_info%nnlist, nkp, nkp_loc, &
                                  kmesh_info%nntot, num_bands, num_wann, print_output%timing_level, &
                                  timer)
          end if
        end do

        if (lsitesymmetry) then
          czmat_in = cmplx_0
          do nkp_loc = 1, ranknk
            nkp = global_k(nkp_loc)
            czmat_in(:, :, nkp) = czmat_in_loc(:, :, nkp_loc)
          end do
          call comms_allreduce(czmat_in(1, 1, 1), num_bands*num_bands*num_kpts, 'SUM', error, comm)
          if (allocated(error)) return
          call sitesym_symmetrize_zmatrix(sitesym, czmat_in, num_bands, num_kpts, &
                                          dis_manifold%lwindow)
          do nkp_loc = 1, ranknk
            nkp = global_k(nkp_loc)
            czmat_in_loc(:, :, nkp_loc) = czmat_in(:, :, nkp)
          end do
        end if

      else
        ! [iter.ne.1]
        ! Update Z matrix at k points with non-frozen states, using a mixing sch
        do nkp_loc = 1, ranknk
          nkp = global_k(nkp_loc)
          if (lsitesymmetry) then
            if (sitesym%ir2ik(sitesym%ik2ir(nkp)) .ne. nkp) cycle
          end if
          if (num_wann .gt. ndimfroz(nkp)) then
            ndimk = dis_manifold%ndimwin(nkp) - ndimfroz(nkp)
            do i = 1, ndimk
              do j = 1, i
                czmat_in_loc(j, i, nkp_loc) = &
                  cmplx(dis_control%mix_ratio, 0.0_dp, dp)*czmat_out_loc(j, i, nkp_loc) &
                  + cmplx(1.0_dp - dis_control%mix_ratio, 0.0_dp, dp)*czmat_in_loc(j, i, nkp_loc)
                ! hermiticity
                czmat_in_loc(i, j, nkp_loc) = conjg(czmat_in_loc(j, i, nkp_loc))
              end do
            end do
          end if
        end do
      end if

      if (print_output%timing_level > 1) call io_stopwatch_stop('dis: extract_1', timer)
      if (print_output%timing_level > 1) call io_stopwatch_start('dis: extract_2', timer)

      womegai1 = 0.0_dp
      ! wkomegai1 is defined by Eq. (18) of SMV.
      ! Contribution to wkomegai1 from frozen states should be calculated now
      ! every k (before updating any k), so that for iter>1 overlaps are with
      ! non-frozen neighboring states from the previous iteration
      wkomegai1 = real(num_wann, dp)*kmesh_info%wbtot

      if (lsitesymmetry) then
        do nkp = 1, sitesym%nkptirr
          wkomegai1(sitesym%ir2ik(nkp)) = wkomegai1(sitesym%ir2ik(nkp))* &
                                          sitesym%nsymmetry/count(sitesym%kptsym(:, nkp) .eq. sitesym%ir2ik(nkp))
        end do
      end if
      do nkp_loc = 1, ranknk
        nkp = global_k(nkp_loc)
        wkomegai1_loc(nkp_loc) = wkomegai1(nkp)
      end do

      do nkp_loc = 1, ranknk
        nkp = global_k(nkp_loc)
        if (ndimfroz(nkp) .gt. 0) then
          if (lsitesymmetry) then
            call set_error_fatal(error, 'not implemented in symmetry-adapted mode', comm)
            return
          end if
          do nn = 1, kmesh_info%nntot
            nkp2 = kmesh_info%nnlist(nkp, nn)
            call zgemm('C', 'N', ndimfroz(nkp), dis_manifold%ndimwin(nkp2), dis_manifold%ndimwin(nkp), &
                       cmplx_1, u_matrix_opt(:, :, nkp), num_bands, &
                       m_matrix_orig_local(:, :, nn, nkp_loc), num_bands, cmplx_0, cwb, num_wann)
            call zgemm('N', 'N', ndimfroz(nkp), num_wann, dis_manifold%ndimwin(nkp2), cmplx_1, cwb, &
                       num_wann, u_matrix_opt(:, :, nkp2), num_bands, cmplx_0, cww, num_wann)
            rsum = 0.0_dp
            do n = 1, num_wann
              do m = 1, ndimfroz(nkp)
                rsum = rsum + real(cww(m, n), dp)**2 + aimag(cww(m, n))**2
              end do
            end do
            wkomegai1_loc(nkp_loc) = wkomegai1_loc(nkp_loc) - kmesh_info%wb(nn)*rsum
          end do
        end if
      end do

      if (print_output%timing_level > 1) call io_stopwatch_stop('dis: extract_2', timer)
      if (print_output%timing_level > 1) call io_stopwatch_start('dis: extract_3', timer)

      !! ! send chunks of wkomegai1 to root node
      !! call comms_gatherv(wkomegai1_loc, counts(my_node_id), wkomegai1, counts, displs)
      !! ! send back the whole wkomegai1 array to other nodes
      !! call comms_bcast(wkomegai1(1), num_kpts)

      ! Refine optimal subspace at k points w/ non-frozen states
      do nkp_loc = 1, ranknk
        nkp = global_k(nkp_loc)
        if (lsitesymmetry) then
          if (sitesym%ir2ik(sitesym%ik2ir(nkp)) .ne. nkp) cycle

          call sitesym_dis_extract_symmetry(sitesym, lambda, u_matrix_opt_loc(:, :, nkp_loc), &
                                            czmat_in_loc(:, :, nkp_loc), nkp, &
                                            dis_manifold%ndimwin(nkp), num_bands, num_wann, &
                                            stdout, error, comm)
          if (allocated(error)) return
          do j = 1, num_wann
            wkomegai1_loc(nkp_loc) = wkomegai1_loc(nkp_loc) - real(lambda(j, j), kind=dp)
          end do
        else
          if (num_wann .gt. ndimfroz(nkp)) then
            ! Diagonalize Z matrix
            do j = 1, dis_manifold%ndimwin(nkp) - ndimfroz(nkp)
              do i = 1, j
                cap(i + ((j - 1)*j)/2) = czmat_in_loc(i, j, nkp_loc)
              end do
            end do
            ndiff = dis_manifold%ndimwin(nkp) - ndimfroz(nkp)
            call ZHPEVX('V', 'A', 'U', ndiff, cap, 0.0_dp, 0.0_dp, 0, 0, &
                        -1.0_dp, m, w, cz, num_bands, cwork, rwork, iwork, ifail, info)
            if (info .lt. 0) then
              if (on_root) then
                write (stdout, *) ' *** ERROR *** ZHPEVX WHILE DIAGONALIZING Z MATRIX'
                write (stdout, *) ' THE ', -info, ' ARGUMENT OF ZHPEVX HAD AN ILLEGAL VALUE'
              end if
              call set_error_fatal(error, ' dis_extract: error', comm)
              return
            end if
            if (info .gt. 0) then
              if (on_root) write (stdout, *) ' *** ERROR *** ZHPEVX WHILE DIAGONALIZING Z MATRIX'
              if (on_root) write (stdout, *) info, ' EIGENVECTORS FAILED TO CONVERGE'
              call set_error_fatal(error, ' dis_extract: error', comm)
              return
            end if

            ! Update the optimal subspace by incorporating the num_wann-ndimfroz(nkp) l
            ! eigenvectors of the Z matrix into u_matrix_opt. Also, add contribution from
            ! non-frozen states to wkomegai1(nkp) (minus the corresponding eigenvalu
            m = ndimfroz(nkp)
            do j = dis_manifold%ndimwin(nkp) - num_wann + 1, dis_manifold%ndimwin(nkp) - ndimfroz(nkp)
              m = m + 1
              wkomegai1_loc(nkp_loc) = wkomegai1_loc(nkp_loc) - w(j)
              u_matrix_opt_loc(1:dis_manifold%ndimwin(nkp), m, nkp_loc) = cmplx_0
              ndimk = dis_manifold%ndimwin(nkp) - ndimfroz(nkp)
              do i = 1, ndimk
                p = indxnfroz(i, nkp)
                u_matrix_opt_loc(p, m, nkp_loc) = cz(i, j)
              end do
            end do
          end if
          ! [if num_wann>ndimfroz(nkp)]
        end if !RS:

        ! Now that we have contribs. from both frozen and non-frozen states to
        ! wkomegai1(nkp), add it to womegai1
        womegai1 = womegai1 + wkomegai1_loc(nkp_loc)

        !if (index(print_output%devel_flag, 'compspace') > 0) then

        ! AT THE LAST ITERATION FIND A BASIS FOR THE (NDIMWIN(NKP)-num_wann)-DIMENS
        ! COMPLEMENT SPACE

        !  if (iter .eq. dis_control%num_iter) then
        !    allocate (camp(num_bands, num_bands, num_kpts), stat=ierr)
        !    if (ierr /= 0) then
        !      call set_error_alloc(error, 'Error allocating camp in dis_extract', comm)
        !      return
        !    endif
        !    allocate (camp_loc(num_bands, num_bands, max(1, counts(my_node_id))), stat=ierr)
        !    if (ierr /= 0) then
        !      call set_error_alloc(error, 'Error allocating ucamp_loc in dis_extract', comm)
        !      return
        !    endif
        !    if (dis_manifold%ndimwin(nkp) .gt. num_wann) then
        !      do j = 1, dis_manifold%ndimwin(nkp) - num_wann
        !        if (num_wann .gt. ndimfroz(nkp)) then
        !          ! USE THE NON-LEADING EIGENVECTORS OF THE Z-MATRIX
        !          camp_loc(1:dis_manifold%ndimwin(nkp), j, nkp_loc) = cz(1:dis_manifold%ndimwin(nkp), j)
        !        else
        ! Then num_wann=NDIMFROZ(NKP)
        ! USE THE ORIGINAL NON-FROZEN BLOCH EIGENSTATES
        !          do i = 1, dis_manifold%ndimwin(nkp)
        !            camp_loc(i, j, nkp_loc) = cmplx_0
        !            if (i .eq. indxnfroz(j, nkp)) camp_loc(i, j, nkp_loc) = cmplx_1
        !          enddo
        !        endif
        !      enddo
        !    else
        !      icompflag = 1
        !    endif
        !  endif

        !end if ! index(print_output%devel_flag,'compspace')>0

      end do
      ! [Loop over k points (nkp)]

      !! ! send chunks of wkomegai1 to root node
      !! call comms_gatherv(wkomegai1_loc, counts(my_node_id), wkomegai1, counts, displs)
      !! ! send back the whole wkomegai1 array to other nodes
      !! call comms_bcast(wkomegai1(1), num_kpts)

      call comms_allreduce(womegai1, 1, 'SUM', error, comm)
      if (allocated(error)) return

      u_matrix_opt(:, :, :) = cmplx_0
      do nkp_loc = 1, ranknk
        nkp = global_k(nkp_loc)
        u_matrix_opt(:, :, nkp) = u_matrix_opt_loc(:, :, nkp_loc)
      end do
      call comms_allreduce(u_matrix_opt(1, 1, 1), num_bands*num_wann*num_kpts, 'SUM', error, comm)
      if (allocated(error)) return

      if (lsitesymmetry) then
        call sitesym_symmetrize_u_matrix(sitesym, u_matrix_opt, num_bands, num_bands, num_kpts, &
                                         num_wann, stdout, error, comm, dis_manifold%lwindow)
        if (allocated(error)) return
        do nkp_loc = 1, ranknk
          nkp = global_k(nkp_loc)
          u_matrix_opt_loc(:, :, nkp_loc) = u_matrix_opt(:, :, nkp)
        end do
      end if

      if (print_output%timing_level > 1) call io_stopwatch_stop('dis: extract_3', timer)

      womegai1 = womegai1/real(num_kpts, dp)

      ! DEBUG
      ! Orthonormality check
      !         do nkp=1,nkpts
      !           write(*,*) ' '
      !           write(*,'(a8,i4)') 'k-point ',nkp
      !           do l=1,num_wann
      !           do m=1,l
      !             ctmp=czero
      !             do j=1,ndimwin(nkp)
      !               ctmp=ctmp+conjg(u_matrix_opt(j,m,nkp))*u_matrix_opt(j,l,nkp)
      !             enddo
      !             write(*,'(i2,2x,i2,f16.12,1x,f16.12)') l,m,ctmp
      !             if(l.eq.m) then
      !               if(abs(ctmp-cmplx(1.0d0,0.0d0)).gt.1.0e-8) then
      !                 write(*,'(a49,i4)')
      !     1           '*** ERROR *** with iterative subspace at k-point ',
      !     2           nkp
      !                 write(*,*) 'vectors in u_matrix_opt not orthonormal'
      !                 stop
      !               endif
      !             else
      !               if(abs(ctmp).gt.1.0e-8) then
      !                 write(*,'(a49,i4)')
      !     1           '*** ERROR *** with iterative subspace at k-point ',
      !     2           nkp
      !                 write(*,*) 'vectors in u_matrix_opt not orthonormal'
      !                 stop
      !               endif
      !             endif
      !           enddo
      !           enddo
      !         enddo
      ! ENDDEBUG

      ! Compute womegai  using the updated subspaces at all k, i.e.,
      ! replacing (i-1) by (i) in Eq. (12) SMV
      if (print_output%timing_level > 1) call io_stopwatch_start('dis: extract_4', timer)

      womegai = 0.0_dp
      do nkp_loc = 1, ranknk
        nkp = global_k(nkp_loc)
        wkomegai = 0.0_dp
        do nn = 1, kmesh_info%nntot
          nkp2 = kmesh_info%nnlist(nkp, nn) ! nkp2 here may not be local to this processor hence operate on global u
          call zgemm('C', 'N', num_wann, dis_manifold%ndimwin(nkp2), dis_manifold%ndimwin(nkp), cmplx_1, &
                     u_matrix_opt(:, :, nkp), num_bands, m_matrix_orig_local(:, :, nn, nkp_loc), &
                     num_bands, cmplx_0, cwb, num_wann)
          call zgemm('N', 'N', num_wann, num_wann, dis_manifold%ndimwin(nkp2), cmplx_1, cwb, num_wann, &
                     u_matrix_opt(:, :, nkp2), num_bands, cmplx_0, cww, num_wann)
          rsum = 0.0_dp
          do n = 1, num_wann
            do m = 1, num_wann
              rsum = rsum + real(cww(m, n), dp)**2 + aimag(cww(m, n))**2
            end do
          end do
          wkomegai = wkomegai + kmesh_info%wb(nn)*rsum
        end do
        wkomegai = real(num_wann, dp)*kmesh_info%wbtot - wkomegai
        womegai = womegai + wkomegai
      end do

      call comms_allreduce(womegai, 1, 'SUM', error, comm)
      if (allocated(error)) return
      womegai = womegai/real(num_kpts, dp)
      delta_womegai = womegai1/womegai - 1.0_dp

      if (print_output%timing_level > 1) call io_stopwatch_stop('dis: extract_4', timer)

      if (print_output%iprint > 0) then
        write (stdout, 124) iter, womegai1*print_output%lenconfac**2, &
          womegai*print_output%lenconfac**2, delta_womegai, io_wallclocktime()
      end if
124   format(2x, i6, 3x, f14.8, 3x, f14.8, 6x, es10.3, 2x, f8.2, 4x, '<-- DIS')

      ! Construct the updated Z matrix, CZMAT_OUT, at k points w/ non-frozen s
      do nkp_loc = 1, ranknk
        nkp = global_k(nkp_loc)
        if (num_wann .gt. ndimfroz(nkp)) then
          call internal_zmatrix(cbw, czmat_out_loc(:, :, nkp_loc), m_matrix_orig_local, &
                                u_matrix_opt, kmesh_info%wb, indxnfroz, ndimfroz, &
                                dis_manifold%ndimwin, kmesh_info%nnlist, nkp, nkp_loc, &
                                kmesh_info%nntot, num_bands, num_wann, print_output%timing_level, &
                                timer)
        end if
      end do

      if (lsitesymmetry) then
        czmat_out = cmplx_0
        do nkp_loc = 1, ranknk
          nkp = global_k(nkp_loc)
          czmat_out(:, :, nkp) = czmat_out_loc(:, :, nkp_loc)
        end do
        call comms_allreduce(czmat_out(1, 1, 1), num_bands*num_bands*num_kpts, 'SUM', error, comm)
        if (allocated(error)) return
        call sitesym_symmetrize_zmatrix(sitesym, czmat_out, num_bands, num_kpts, dis_manifold%lwindow)
        do nkp_loc = 1, ranknk
          nkp = global_k(nkp_loc)
          czmat_out_loc(:, :, nkp_loc) = czmat_out(:, :, nkp)
        end do
      end if

      call internal_test_convergence(history, delta_womegai, dis_control%conv_tol, iter, &
                                     dis_control%conv_window, dis_converged, error, comm)
      if (allocated(error)) return

      if (dis_converged) then
        if (print_output%iprint > 0) then
          write (stdout, '(/13x,a,es10.3,a,i2,a)') '<<<      Delta <', dis_control%conv_tol, &
            '  over ', dis_control%conv_window, ' iterations     >>>'
          write (stdout, '(13x,a)') '<<< Disentanglement convergence criteria satisfied >>>'
        end if
        exit
      end if

    end do
    ! [BIG ITERATION LOOP (iter)]

    if (lsitesymmetry) then
      deallocate (czmat_out, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating czmat_out in dis_extract', comm)
        return
      end if
      deallocate (czmat_in, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating czmat_in in dis_extract', comm)
        return
      end if
    end if
    deallocate (czmat_out_loc, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating czmat_out_loc in dis_extract', comm)
      return
    end if
    deallocate (czmat_in_loc, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating czmat_in_loc in dis_extract', comm)
      return
    end if

    if (on_root) then
      allocate (ceamp(num_bands, num_bands, num_kpts), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error allocating ceamp in dis_extract', comm)
        return
      end if
      allocate (cham(num_bands, num_bands, num_kpts), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error allocating cham in dis_extract', comm)
        return
      end if
    end if

    if (.not. dis_converged) then
      if (on_root) then
        write (stdout, '(/5x,a)') &
          '<<< Warning: Maximum number of disentanglement iterations reached >>>'
        write (stdout, '(10x,a)') '<<< Disentanglement convergence criteria not satisfied >>>'
      end if
    end if

    !if (index(print_output%devel_flag, 'compspace') > 0) then

    !  if (icompflag .eq. 1) then
    !    if (print_output%iprint > 2) then
    !      if (on_root) write (stdout, ('(/4x,a)')) &
    !        'WARNING: Complement subspace has zero dimensions at the following k-points:'
    !      i = 0
    !      if (on_root) write (stdout, '(4x)', advance='no')
    !      do nkp = 1, num_kpts
    !        if (dis_manifold%ndimwin(nkp) .eq. num_wann) then
    !          i = i + 1
    !          if (i .le. 12) then
    !            if (on_root) write (stdout, '(i6)', advance='no') nkp
    !          else
    !            i = 1
    !            if (on_root) write (stdout, '(/4x)', advance='no')
    !            if (on_root) write (stdout, '(i6)', advance='no') nkp
    !          endif
    !        endif
    !      enddo
    !    endif
    !  endif

    !endif

    ! Write the final womegai. This should remain unchanged during the
    ! subsequent minimization of Omega_tilde in wannierise.f90
    ! We store it in the checkpoint file as a sanity check
    if (print_output%iprint > 0) write (stdout, '(/8x,a,f14.8,a/)') 'Final Omega_I ', &
      womegai*print_output%lenconfac**2, ' ('//trim(print_output%length_unit)//'^2)'

    ! Set public variable omega_invariant
    omega_invariant = womegai

    ! Currently, this part is not parallelized; thus, we perform the task only on root and then broadcast the result.
    if (on_root) then
      ! DIAGONALIZE THE HAMILTONIAN WITHIN THE OPTIMIZED SUBSPACES
      do nkp = 1, num_kpts

        do j = 1, num_wann
          do i = 1, num_wann
            cham(i, j, nkp) = cmplx_0
            do l = 1, dis_manifold%ndimwin(nkp)
              cham(i, j, nkp) = cham(i, j, nkp) + conjg(u_matrix_opt(l, i, nkp)) &
                                *u_matrix_opt(l, j, nkp)*eigval_opt(l, nkp)
            end do
          end do
        end do

        do j = 1, num_wann
          do i = 1, j
            cap(i + ((j - 1)*j)/2) = cham(i, j, nkp)
          end do
        end do

        call ZHPEVX('V', 'A', 'U', num_wann, cap, 0.0_dp, 0.0_dp, 0, 0, -1.0_dp, m, w, cz, &
                    num_bands, cwork, rwork, iwork, ifail, info)

        if (info .lt. 0) then
          if (on_root) write (stdout, *) ' *** ERROR *** ZHPEVX WHILE DIAGONALIZING HAMILTONIAN'
          if (on_root) write (stdout, *) ' THE ', -info, ' ARGUMENT OF ZHPEVX HAD AN ILLEGAL VALUE'
          call set_error_fatal(error, ' dis_extract: error', comm)
          return
        end if
        if (info .gt. 0) then
          if (on_root) write (stdout, *) ' *** ERROR *** ZHPEVX WHILE DIAGONALIZING HAMILTONIAN'
          if (on_root) write (stdout, *) info, 'EIGENVECTORS FAILED TO CONVERGE'
          call set_error_fatal(error, ' dis_extract: error', comm)
          return
        end if

        ! Store the energy eigenvalues of the optimal subspace (used in wann_ban
        eigval_opt(1:num_wann, nkp) = w(1:num_wann)

        ! CALCULATE AMPLITUDES OF THE CORRESPONDING ENERGY EIGENVECTORS IN TERMS
        ! THE ORIGINAL ("WINDOW SPACE") ENERGY EIGENVECTORS
        do j = 1, num_wann
          do i = 1, dis_manifold%ndimwin(nkp)
            ceamp(i, j, nkp) = cmplx_0
            do l = 1, num_wann
              ceamp(i, j, nkp) = ceamp(i, j, nkp) + cz(l, j)*u_matrix_opt(i, l, nkp)
            end do
          end do
        end do
        ! NKP
      end do

      if (print_output%iprint > 2) then
        if (on_root) write (stdout, '(/,a,/)') '  Eigenvalues inside optimal subspace:'
        do nkp = 1, num_kpts
          if (on_root) write (stdout, '(a,i3,2x,20(f9.5,1x))') '  K-point ', &
            nkp, (eigval_opt(i, nkp), i=1, num_wann)
        end do
      end if

      ! Replace u_matrix_opt by ceamp. Both span the
      ! same space, but the latter is more convenient for the purpose of obtai
      ! an optimal Fourier-interpolated band structure: see Sec. III.E of SMV.
      if (.not. lsitesymmetry) then                                                                         !YN:
        do nkp = 1, num_kpts
          do j = 1, num_wann
            u_matrix_opt(1:dis_manifold%ndimwin(nkp), j, nkp) = ceamp(1:dis_manifold%ndimwin(nkp), j, nkp)
          end do
        end do
        !else                                                                                                        !YN:
        ! Above is skipped as we require Uopt(Rk) to be related to Uopt(k)                                        !YN: RS:
        !write(stdout,"(a)")  &                                                                                   !YN: RS:
        !  'Note(symmetry-adapted mode): u_matrix_opt are no longer the eigenstates of the subspace Hamiltonian.' !RS:
      end if                                                                                                        !YN:
    end if
    call comms_bcast(eigval_opt(1, 1), num_bands*num_kpts, error, comm)
    if (allocated(error)) return
    call comms_bcast(u_matrix_opt(1, 1, 1), num_bands*num_wann*num_kpts, error, comm)
    if (allocated(error)) return

    !if (index(print_output%devel_flag, 'compspace') > 0) then

    !  if (icompflag .eq. 1) then
    !    if (print_output%iprint > 2) then
    !      if (on_root) then
    !        write (stdout, *) 'AT SOME K-POINT(S) COMPLEMENT SUBSPACE HAS ZERO DIMENSIONALITY'
    !        write (stdout, *) '=> DID NOT CREATE FILE COMPSPACE.DAT'
    !      endif
    !    endif
    !  else
    ! DIAGONALIZE THE HAMILTONIAN IN THE COMPLEMENT SUBSPACE, WRITE THE
    ! CORRESPONDING EIGENFUNCTIONS AND ENERGY EIGENVALUES
    !    do nkp = 1, num_kpts
    !      do j = 1, dis_manifold%ndimwin(nkp) - num_wann
    !        do i = 1, dis_manifold%ndimwin(nkp) - num_wann
    !          cham(i, j, nkp) = cmplx_0
    !          do l = 1, dis_manifold%ndimwin(nkp)
    !            cham(i, j, nkp) = cham(i, j, nkp) + conjg(camp(l, i, nkp)) &
    !                              *camp(l, j, nkp)*eigval_opt(l, nkp)
    !          enddo
    !        enddo
    !      enddo
    !      do j = 1, dis_manifold%ndimwin(nkp) - num_wann
    !        do i = 1, j
    !          cap(i + ((j - 1)*j)/2) = cham(i, j, nkp)
    !        enddo
    !      enddo
    !      ndiff = dis_manifold%ndimwin(nkp) - num_wann
    !      call ZHPEVX('V', 'A', 'U', ndiff, cap, 0.0_dp, 0.0_dp, 0, 0, &
    !                  -1.0_dp, m, w, cz, num_bands, cwork, rwork, iwork, ifail, info)
    !      if (info .lt. 0) then
    !        if (on_root) write (stdout, *) '*** ERROR *** ZHPEVX WHILE DIAGONALIZING HAMILTONIAN'
    !        if (on_root) write (stdout, *) 'THE ', -info, ' ARGUMENT OF ZHPEVX HAD AN ILLEGAL VALUE'
    !        call io_error(' dis_extract: error', stdout, seedname)
    !      endif
    !      if (info .gt. 0) then
    !        if (on_root) write (stdout, *) '*** ERROR *** ZHPEVX WHILE DIAGONALIZING HAMILTONIAN'
    !        if (on_root) write (stdout, *) info, 'EIGENVECTORS FAILED TO CONVERGE'
    !        call io_error(' dis_extract: error', stdout, seedname)
    !      endif
    ! CALCULATE AMPLITUDES OF THE ENERGY EIGENVECTORS IN THE COMPLEMENT SUBS
    ! TERMS OF THE ORIGINAL ENERGY EIGENVECTORS
    !      do j = 1, dis_manifold%ndimwin(nkp) - num_wann
    !        do i = 1, dis_manifold%ndimwin(nkp)
    !          camp(i, j, nkp) = cmplx_0
    !          do l = 1, dis_manifold%ndimwin(nkp) - num_wann
!write(stdout,*) 'i=',i,'   j=',j,'   l=',l
!write(stdout,*) '           camp(i,j,nkp)=',camp(i,j,nkp)
!write(stdout,*) '           cz(l,j)=',cz(l,j)
!write(stdout,*) '           u_matrix_opt(i,l,nkp)=',u_matrix_opt(i,l,nkp)

! aam: 20/10/2006 -- the second dimension of u_matrix_opt is out of bounds (allocated as num_wann)!
! commenting this line out.
!                     camp(i,j,nkp) = camp(i,j,nkp) + cz(l,j) * u_matrix_opt(i,l,nkp)
    !         enddo
    !       enddo
    !     enddo
    !   enddo   ! [loop over k points (nkp)]

    ! endif   ! [if icompflag=1]

    !endif     ![if(index(devel_flag,'compspace')>0)]

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

    if (on_root) then
      deallocate (cham, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating cham in dis_extract', comm)
        return
      end if
    end if
    if (allocated(camp)) then
      deallocate (camp, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating camp in dis_extract', comm)
        return
      end if
    end if
    if (allocated(camp_loc)) then
      deallocate (camp_loc, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating camp_loc in dis_extract', comm)
        return
      end if
    end if
    if (on_root) then
      deallocate (ceamp, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating ceamp in dis_extract', comm)
        return
      end if
    end if
    deallocate (u_matrix_opt_loc, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating u_matrix_opt_loc in dis_extract', comm)
      return
    end if
    deallocate (wkomegai1_loc, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating wkomegai1_loc in dis_extract', comm)
      return
    end if
    deallocate (wkomegai1, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating wkomegai1 in dis_extract', comm)
      return
    end if

    deallocate (cz, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cz in dis_extract', comm)
      return
    end if
    deallocate (cwork, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cwork in dis_extract', comm)
      return
    end if
    deallocate (cap, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cap in dis_extract', comm)
      return
    end if
    deallocate (rwork, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating rwork in dis_extract', comm)
      return
    end if
    deallocate (w, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating w in dis_extract', comm)
      return
    end if
    deallocate (ifail, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating ifail in dis_extract', comm)
      return
    end if
    deallocate (iwork, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating iwork in dis_extract', comm)
      return
    end if

    deallocate (cbw, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cbw in dis_extract', comm)
      return
    end if
    deallocate (cww, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cww in dis_extract', comm)
      return
    end if
    deallocate (cwb, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cwb in dis_extract', comm)
      return
    end if

    if (print_output%iprint > 0) write (stdout, '(1x,a/)') &
      '+----------------------------------------------------------------------------+'

    if (print_output%timing_level > 1) call io_stopwatch_stop('dis: extract', timer)

    return
    !================================================!
  end subroutine dis_extract

  subroutine internal_test_convergence(history, delta_womegai, dis_conv_tol, iter, &
                                       dis_conv_window, dis_converged, error, comm)
    !================================================!
    !
    !! Check if we have converged
    !
    !================================================!
    use w90_constants, only: dp
    use w90_error
    use w90_types, only: timer_list_type

    implicit none

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

    integer, intent(in) :: iter, dis_conv_window

    real(kind=dp), intent(inout) :: history(:)
    real(kind=dp), intent(in) :: delta_womegai, dis_conv_tol

    logical, intent(inout) :: dis_converged

    ! local variables
    integer :: ierr
    real(kind=dp), allocatable :: temp_hist(:)

    allocate (temp_hist(dis_conv_window), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating temp_hist in dis_extract: test_convergence', comm)
      return
    end if

    if (iter .le. dis_conv_window) then
      history(iter) = delta_womegai
    else
      temp_hist = eoshift(history, 1, delta_womegai)
      history = temp_hist
    end if

    dis_converged = .false.
    if (iter .ge. dis_conv_window) then
      dis_converged = all(abs(history) .lt. dis_conv_tol)
    end if

    deallocate (temp_hist, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating temp_hist in dis_extract: test_convergence', comm)
      return
    end if

    return
    !================================================!
  end subroutine internal_test_convergence

  subroutine internal_zmatrix(cbw, cmtrx, m_matrix_orig_local, u_matrix_opt, wb, indxnfroz, &
                              ndimfroz, ndimwin, nnlist, nkp, nkp_loc, nntot, num_bands, num_wann, &
                              timing_level, timer)
    !================================================!
    !
    !! Compute the Z-matrix
    !
    !================================================!
    use w90_constants, only: dp, cmplx_0, cmplx_1
    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, num_wann
    integer, intent(in) :: timing_level
    integer, intent(in) :: ndimwin(:)
    integer, intent(in) :: nntot, nnlist(:, :)
    integer, intent(in) :: indxnfroz(:, :)
    integer, intent(in) :: ndimfroz(:)
    integer, intent(in) :: nkp
    integer, intent(in) :: nkp_loc

    real(kind=dp), intent(in) :: wb(:)

    complex(kind=dp), intent(in) :: u_matrix_opt(:, :, :)
    complex(kind=dp), intent(in) :: m_matrix_orig_local(:, :, :, :)
    complex(kind=dp), intent(inout) :: cbw(:, :)
    complex(kind=dp), intent(out) :: cmtrx(:, :)
    !! (M,N)-TH ENTRY IN THE (NDIMWIN(NKP)-NDIMFROZ(NKP)) x (NDIMWIN(NKP)-NDIMFRO
    !! HERMITIAN MATRIX AT THE NKP-TH K-POINT

    ! local variables
    integer          :: l, m, n, p, q, nn, nkp2, ndimk
    complex(kind=dp) :: csum

    if (timing_level > 1) call io_stopwatch_start('dis: extract: zmatrix', timer)

    cmtrx = cmplx_0
    ndimk = ndimwin(nkp) - ndimfroz(nkp)
    do nn = 1, nntot
      nkp2 = nnlist(nkp, nn)
      call zgemm('N', 'N', num_bands, num_wann, ndimwin(nkp2), cmplx_1, &
                 m_matrix_orig_local(:, :, nn, nkp_loc), num_bands, u_matrix_opt(:, :, nkp2), &
                 num_bands, cmplx_0, cbw, num_bands)
      do n = 1, ndimk
        q = indxnfroz(n, nkp)
        do m = 1, n
          p = indxnfroz(m, nkp)
          csum = cmplx_0
          do l = 1, num_wann
            csum = csum + cbw(p, l)*conjg(cbw(q, l))
          end do
          cmtrx(m, n) = cmtrx(m, n) + cmplx(wb(nn), 0.0_dp, kind=dp)*csum
          cmtrx(n, m) = conjg(cmtrx(m, n))
        end do
      end do
    end do

    if (timing_level > 1) call io_stopwatch_stop('dis: extract: zmatrix', timer)

    return
    !================================================!
  end subroutine internal_zmatrix

  subroutine dis_extract_gamma(dis_control, kmesh_info, print_output, dis_manifold, &
                               m_matrix_orig_local, u_matrix_opt, eigval_opt, omega_invariant, &
                               indxnfroz, ndimfroz, num_bands, num_kpts, num_wann, timer, error, &
                               stdout, comm)
    !================================================!
    !
    !! Extracts an num_wann-dimensional subspace at each k by
    !! minimizing Omega_I (Gamma point version)
    !
    !================================================!
    ! this routine is not parallelised
    use w90_comms, only: w90_comm_type
    use w90_constants, only: dp, cmplx_0, cmplx_1
    use w90_error
    use w90_io, only: io_time, io_stopwatch_start, io_stopwatch_stop
    use w90_types, only: dis_manifold_type, kmesh_info_type, print_output_type, timer_list_type
    use w90_types, only: timer_list_type
    use w90_wannier90_types, only: dis_control_type

    implicit none

    ! arguments
    type(dis_control_type), intent(in) :: dis_control
    type(dis_manifold_type), intent(in) :: dis_manifold
    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_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

    integer, intent(in) :: stdout
    integer, intent(in) :: num_bands, num_kpts, num_wann
    integer, intent(in) :: ndimfroz(:)
    integer, intent(in) :: indxnfroz(:, :)

    real(kind=dp), intent(inout) :: eigval_opt(:, :)
    real(kind=dp), intent(out) :: omega_invariant

    complex(kind=dp), intent(inout) :: m_matrix_orig_local(:, :, :, :)
    complex(kind=dp), intent(inout) :: u_matrix_opt(:, :, :)

    ! MODIFIED:
    !           u_matrix_opt (At input it contains the initial guess for the optimal
    ! subspace (expressed in terms of the original states inside the window). At
    ! output it contains the  states that diagonalize the hamiltonian inside the
    ! optimal subspace (again expressed in terms of the original window states).
    ! Giving out states that diagonalize the hamiltonian inside the optimal
    ! subspace (instead of the eigenstates of the Z matrix) is useful for
    ! performing the Wannier interpolation of the energy bands as described in
    ! Sec. III.F of SMV)
    !
    !           eigval (At input: original energy eigenvalues.
    ! At output: eigenvalues of the hamiltonian inside optimal subspace)

    ! ----------------------------------------------------------------------
    ! TO DO: The complement subspace is computed but is not saved anywhere!
    ! (Check what was done with it in original code space.f)
    ! Diagonalize Z matrix only at those k points where ndimwin>num_wann?
    ! ----------------------------------------------------------------------

    ! *******************
    ! SHELLS OF K-VECTORS
    ! *******************
    ! nshells           number of shells of k-points to be used in the
    !                   finite-difference formulas for the k-derivatives
    ! aam: wb is now wb(1:nntot) 09/04/2006
    ! wb(nkp,nnx)       weight of the nnx-th b-vector (ordered along shells
    !                   of increasing length) associated with the nkp-th k-p
    ! wbtot             sum of the weights of all b-vectors associated with
    !                   given k-point (k-point 1 is used in calculation)
    ! nnlist(nkp,nnx)   vkpt(1:3,nnlist(nkp,nnx)) is the nnx-th neighboring
    !                   k-point of the nkp-th k-point vkpt(1:3,nkp) (or its
    !                   periodic image in the "home Brillouin zone")
    ! cm(n,m,nkp,nnx)   Overlap matrix <u_nk|u_{m,k+b}>

    ! Internal variables
    integer, allocatable :: ifail(:)
    integer, allocatable :: iwork(:)
    integer :: icompflag, iter, ndiff
    integer :: i, j, l, m, n, nn, nkp, nkp2, info, ierr, ndimk, p

    complex(kind=dp), allocatable :: camp(:, :, :)
    complex(kind=dp), allocatable :: ceamp(:, :, :)
    complex(kind=dp), allocatable :: cham(:, :, :)
    complex(kind=dp), allocatable :: cwb(:, :), cww(:, :), cbw(:, :)
    complex(kind=dp), allocatable :: cz(:, :)

    real(kind=dp), allocatable :: cap_r(:)
    real(kind=dp), allocatable :: history(:)
    real(kind=dp), allocatable :: rz(:, :)
    real(kind=dp), allocatable :: rzmat_in(:, :, :)
    real(kind=dp), allocatable :: rzmat_out(:, :, :)
    real(kind=dp), allocatable :: w(:)
    real(kind=dp), allocatable :: wkomegai1(:)
    real(kind=dp), allocatable :: work(:)
    real(kind=dp) :: womegai, wkomegai, womegai1, rsum, delta_womegai

    logical :: dis_converged

    if (print_output%timing_level > 1) call io_stopwatch_start('dis: extract', timer)

    write (stdout, '(/1x,a)') &
      '                  Extraction of optimally-connected subspace                  '
    write (stdout, '(1x,a)') &
      '                  ------------------------------------------                  '

    allocate (cwb(num_wann, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cwb in dis_extract_gamma', comm)
      return
    end if
    allocate (cww(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cww in dis_extract_gamma', comm)
      return
    end if
    allocate (cbw(num_bands, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cbw in dis_extract_gamma', comm)
      return
    end if

    cwb = cmplx_0; cww = cmplx_0; cbw = cmplx_0

    allocate (iwork(5*num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating iwork in dis_extract_gamma', comm)
      return
    end if
    allocate (ifail(num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating ifail in dis_extract_gamma', comm)
      return
    end if
    allocate (w(num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating w in dis_extract_gamma', comm)
      return
    end if
    allocate (cz(num_bands, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cz in dis_extract_gamma', comm)
      return
    end if
    allocate (work(8*num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating work in dis_extract_gamma', comm)
      return
    end if
    allocate (cap_r((num_bands*(num_bands + 1))/2), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cap_r in dis_extract_gamma', comm)
      return
    end if
    allocate (rz(num_bands, num_bands), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating rz in dis_extract_gamma', comm)
      return
    end if
    allocate (wkomegai1(num_kpts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating wkomegai1 in dis_extract_gamma', comm)
      return
    end if
    allocate (rzmat_in(num_bands, num_bands, num_kpts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating rzmat_in indis_extract_gamma', comm)
      return
    end if
    allocate (rzmat_out(num_bands, num_bands, num_kpts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating rzmat_out indis_extract_gamma', comm)
      return
    end if
    allocate (history(dis_control%conv_window), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating history indis_extract_gamma', comm)
      return
    end if

    ! ********************************************
    ! ENERGY WINDOWS AND SUBSPACES AT EACH K-POINT
    ! ********************************************
    ! num_wann             dimensionality of the subspace at each k-point
    !                   (number of Wannier functions per unit cell that we w
    ! NDIMWIN(NKP)      number of bands at the nkp-th k-point that fall
    !                   within the outer energy window
    ! NDIMFROZ(NKP)     number of frozen bands at the nkp-th k-point
    ! INDXNFROZ(I,NKP)  INDEX (BETWEEN 1 AND NDIMWIN(NKP)) OF THE I-TH NON-F
    !                   ORIGINAL BAND STATE AT THE NKP-TH K-POINT
    ! U_MATRIX_OPT(J,L,NKP) AMPLITUDE OF THE J-TH ENERGY EIGENVECTOR INSIDE THE
    !                   ENERGY WINDOW AT THE NKP-TH K-POINT IN THE EXPANSION OF
    !                   THE L-TH LEADING RLAMBDA EIGENVECTOR AT THE SAME K-POINT
    !                   If there are M_k frozen states, they occupy the lowest
    !                   entries of the second index of u_matrix_opt, and the leading
    !                   nbands-M_k eigenvectors of the Z matrix occupy the
    !                   remaining slots
    ! CAMP(J,L,NKP)     SAME AS U_MATRIX_OPT, BUT FOR THE COMPLEMENT SUBSPACE INSIDE THE
    !                   ENERGY WINDOW (I.E., THE NON-LEADING RLAMBDA EIGENVECTORS)
    ! CEAMP(J,L,NKPTS)  SAME AS U_MATRIX_OPT, BUT INSTEAD OF RLAMBDA EIGENVECTOR, I
    !                   FOR THE ENERGY EIGENVECTOR OBTAINED BY DIAGONALIZING
    !                   HAMILTONIAN IN THE OPTIMIZED SUBSPACE
    ! RZMAT_IN(M,N,NKP) Z-MATRIX [Eq. (21) SMV]
    ! RZMAT_OUT(M,N,NKP) OUTPUT Z-MATRIX FROM THE PRESENT ITERATION
    ! RLAMBDA           An eigenvalue of the Z matrix
    ! womegai           Gauge-invariant Wannier spread, computed usinf all s
    !                   from current iteration
    ! wkomegai1(NKP)    Eq. (18) of SMV
    ! womegai1          Eq.(11) of SMV (like wowmegai, but neighboring state
    !                   for computing overlaps are from previous iteration.
    !                   become equal at self-consistency)
    ! alphafixe         mixing parameter for the iterative procedure
    ! nitere            total number of iterations

    ! DEBUG
    if (print_output%iprint > 2) then
      write (stdout, '(a,/)') '  Original eigenvalues inside outer window:'
      do nkp = 1, num_kpts
        write (stdout, '(a,i3,3x,20(f9.5,1x))') '  K-point ', nkp, &
          (eigval_opt(i, nkp), i=1, dis_manifold%ndimwin(nkp))
      end do
    end if
    ! ENDDEBUG

    ! TO DO: Check if this is the best place to initialize icompflag
    icompflag = 0

    write (stdout, '(1x,a)') &
      '+---------------------------------------------------------------------+<-- DIS'
    write (stdout, '(1x,a)') &
      '|  Iter     Omega_I(i-1)      Omega_I(i)      Delta (frac.)    Time   |<-- DIS'
    write (stdout, '(1x,a)') &
      '+---------------------------------------------------------------------+<-- DIS'

    dis_converged = .false.
    womegai = 0 ! unitialised if zero iterations sought

    ! ------------------
    ! BIG ITERATION LOOP
    ! ------------------
    do iter = 1, dis_control%num_iter

      if (iter .eq. 1) then
        ! Initialize Z matrix at k points w/ non-frozen states
        do nkp = 1, num_kpts
          if (num_wann .gt. ndimfroz(nkp)) then
            call internal_zmatrix_gamma(cbw, m_matrix_orig_local, u_matrix_opt, rzmat_in(:, :, nkp), &
                                        kmesh_info%wb, indxnfroz, ndimfroz, dis_manifold%ndimwin, &
                                        kmesh_info%nnlist, nkp, kmesh_info%nntot, num_bands, &
                                        num_wann, print_output%timing_level, timer)
          end if
        end do
      else
        ! [iter.ne.1]
        ! Update Z matrix at k points with non-frozen states, using a mixing sch
        do nkp = 1, num_kpts
          if (num_wann .gt. ndimfroz(nkp)) then
            ndimk = dis_manifold%ndimwin(nkp) - ndimfroz(nkp)
            do i = 1, ndimk
              do j = 1, i
                rzmat_in(j, i, nkp) = &
                  dis_control%mix_ratio*rzmat_out(j, i, nkp) &
                  + (1.0_dp - dis_control%mix_ratio)*rzmat_in(j, i, nkp)
                ! hermiticity
                rzmat_in(i, j, nkp) = rzmat_in(j, i, nkp)
              end do
            end do
          end if
        end do
      end if
      ! [if iter=1]

      womegai1 = 0.0_dp
      ! wkomegai1 is defined by Eq. (18) of SMV.
      ! Contribution to wkomegai1 from frozen states should be calculated now
      ! every k (before updating any k), so that for iter>1 overlaps are with
      ! non-frozen neighboring states from the previous iteration

      wkomegai1 = real(num_wann, dp)*kmesh_info%wbtot
      do nkp = 1, num_kpts
        if (ndimfroz(nkp) .gt. 0) then
          do nn = 1, kmesh_info%nntot
            nkp2 = kmesh_info%nnlist(nkp, nn)
            call zgemm('C', 'N', ndimfroz(nkp), dis_manifold%ndimwin(nkp2), dis_manifold%ndimwin(nkp), &
                       cmplx_1, u_matrix_opt(:, :, nkp), num_bands, m_matrix_orig_local(:, :, nn, nkp), &
                       num_bands, cmplx_0, cwb, num_wann)
            call zgemm('N', 'N', ndimfroz(nkp), num_wann, dis_manifold%ndimwin(nkp2), cmplx_1, &
                       cwb, num_wann, u_matrix_opt(:, :, nkp2), num_bands, cmplx_0, cww, num_wann)
            rsum = 0.0_dp
            do n = 1, num_wann
              do m = 1, ndimfroz(nkp)
                rsum = rsum + real(cww(m, n), dp)**2 + aimag(cww(m, n))**2
              end do
            end do
            wkomegai1(nkp) = wkomegai1(nkp) - kmesh_info%wb(nn)*rsum
          end do
        end if
      end do

      ! Refine optimal subspace at k points w/ non-frozen states
      do nkp = 1, num_kpts
        if (num_wann .gt. ndimfroz(nkp)) then
          ! Diagonalize Z matrix
          do j = 1, dis_manifold%ndimwin(nkp) - ndimfroz(nkp)
            do i = 1, j
              cap_r(i + ((j - 1)*j)/2) = rzmat_in(i, j, nkp)
            end do
          end do
          ndiff = dis_manifold%ndimwin(nkp) - ndimfroz(nkp)
          call DSPEVX('V', 'A', 'U', ndiff, cap_r, 0.0_dp, 0.0_dp, 0, 0, &
                      -1.0_dp, m, w, rz, num_bands, work, iwork, ifail, info)
          if (info .lt. 0) then
            write (stdout, *) ' *** ERROR *** DSPEVX WHILE DIAGONALIZING Z MATRIX'
            write (stdout, *) ' THE ', -info, ' ARGUMENT OF DSPEVX HAD AN ILLEGAL VALUE'
            call set_error_fatal(error, ' dis_extract_gamma: error', comm)
            return
          end if
          if (info .gt. 0) then
            write (stdout, *) ' *** ERROR *** DSPEVX WHILE DIAGONALIZING Z MATRIX'
            write (stdout, *) info, ' EIGENVECTORS FAILED TO CONVERGE'
            call set_error_fatal(error, '  dis_extract_gamma: error', comm)
            return
          end if
          cz(:, :) = cmplx(rz(:, :), 0.0_dp, dp)
          !
          ! Update the optimal subspace by incorporating the num_wann-ndimfroz(nkp) l
          ! eigenvectors of the Z matrix into u_matrix_opt. Also, add contribution from
          ! non-frozen states to wkomegai1(nkp) (minus the corresponding eigenvalu
          m = ndimfroz(nkp)
          do j = dis_manifold%ndimwin(nkp) - num_wann + 1, dis_manifold%ndimwin(nkp) - ndimfroz(nkp)
            m = m + 1
            wkomegai1(nkp) = wkomegai1(nkp) - w(j)
            u_matrix_opt(1:dis_manifold%ndimwin(nkp), m, nkp) = cmplx_0
            ndimk = dis_manifold%ndimwin(nkp) - ndimfroz(nkp)
            do i = 1, ndimk
              p = indxnfroz(i, nkp)
              u_matrix_opt(p, m, nkp) = cz(i, j)
            end do
          end do
        end if
        ! [if num_wann>ndimfroz(nkp)]

        ! Now that we have contribs. from both frozen and non-frozen states to
        ! wkomegai1(nkp), add it to womegai1
        womegai1 = womegai1 + wkomegai1(nkp)

        ! AT THE LAST ITERATION FIND A BASIS FOR THE (NDIMWIN(NKP)-num_wann)-DIMENS
        ! COMPLEMENT SPACE
        !if (index(print_output%devel_flag, 'compspace') > 0) then

        !  if (iter .eq. dis_control%num_iter) then
        !    allocate (camp(num_bands, num_bands, num_kpts), stat=ierr)
        !    camp = cmplx_0
        !    if (ierr /= 0) then
        !      call set_error_alloc(error, 'Error allocating camp in dis_extract_gamma', comm)
        !      return
        !    endif
        !    if (dis_manifold%ndimwin(nkp) .gt. num_wann) then
        !      do j = 1, dis_manifold%ndimwin(nkp) - num_wann
        !        if (num_wann .gt. ndimfroz(nkp)) then
        !          ! USE THE NON-LEADING EIGENVECTORS OF THE Z-MATRIX
        !          camp(1:dis_manifold%ndimwin(nkp), j, nkp) = cz(1:dis_manifold%ndimwin(nkp), j)
        !        else
        !          ! Then num_wann=NDIMFROZ(NKP)
        !          ! USE THE ORIGINAL NON-FROZEN BLOCH EIGENSTATES
        !          do i = 1, dis_manifold%ndimwin(nkp)
        !            camp(i, j, nkp) = cmplx_0
        !            if (i .eq. indxnfroz(j, nkp)) camp(i, j, nkp) = cmplx_1
        !          enddo
        !        endif
        !      enddo
        !    else
        !      icompflag = 1
        !    endif
        !  endif
        !end if

      end do
      ! [Loop over k points (nkp)]

      womegai1 = womegai1/real(num_kpts, dp)

      ! DEBUG
      ! Orthonormality check
      !         do nkp=1,nkpts
      !           write(*,*) ' '
      !           write(*,'(a8,i4)') 'k-point ',nkp
      !           do l=1,num_wann
      !           do m=1,l
      !             ctmp=czero
      !             do j=1,ndimwin(nkp)
      !               ctmp=ctmp+conjg(u_matrix_opt(j,m,nkp))*u_matrix_opt(j,l,nkp)
      !             enddo
      !             write(*,'(i2,2x,i2,f16.12,1x,f16.12)') l,m,ctmp
      !             if(l.eq.m) then
      !               if(abs(ctmp-cmplx(1.0d0,0.0d0)).gt.1.0e-8) then
      !                 write(*,'(a49,i4)')
      !     1           '*** ERROR *** with iterative subspace at k-point ',
      !     2           nkp
      !                 write(*,*) 'vectors in u_matrix_opt not orthonormal'
      !                 stop
      !               endif
      !             else
      !               if(abs(ctmp).gt.1.0e-8) then
      !                 write(*,'(a49,i4)')
      !     1           '*** ERROR *** with iterative subspace at k-point ',
      !     2           nkp
      !                 write(*,*) 'vectors in u_matrix_opt not orthonormal'
      !                 stop
      !               endif
      !             endif
      !           enddo
      !           enddo
      !         enddo
      ! ENDDEBUG

      ! Compute womegai  using the updated subspaces at all k, i.e.,
      ! replacing (i-1) by (i) in Eq. (12) SMV

      womegai = 0.0_dp
      do nkp = 1, num_kpts
        wkomegai = 0.0_dp
        do nn = 1, kmesh_info%nntot
          nkp2 = kmesh_info%nnlist(nkp, nn)
          call zgemm('C', 'N', num_wann, dis_manifold%ndimwin(nkp2), dis_manifold%ndimwin(nkp), &
                     cmplx_1, u_matrix_opt(:, :, nkp), num_bands, m_matrix_orig_local(:, :, nn, nkp), &
                     num_bands, cmplx_0, cwb, num_wann)
          call zgemm('N', 'N', num_wann, num_wann, dis_manifold%ndimwin(nkp2), cmplx_1, cwb, &
                     num_wann, u_matrix_opt(:, :, nkp2), num_bands, cmplx_0, cww, num_wann)
          rsum = 0.0_dp
          do n = 1, num_wann
            do m = 1, num_wann
              rsum = rsum + real(cww(m, n), dp)**2 + aimag(cww(m, n))**2
            end do
          end do
          wkomegai = wkomegai + kmesh_info%wb(nn)*rsum
        end do
        wkomegai = real(num_wann, dp)*kmesh_info%wbtot - wkomegai
        womegai = womegai + wkomegai
      end do
      womegai = womegai/real(num_kpts, dp)
      ! [Loop over k (nkp)]

      delta_womegai = womegai1/womegai - 1.0_dp

      write (stdout, 124) iter, womegai1*print_output%lenconfac**2, &
        womegai*print_output%lenconfac**2, delta_womegai, io_time()

124   format(2x, i6, 3x, f14.8, 3x, f14.8, 6x, es10.3, 2x, f8.2, 4x, '<-- DIS')

      ! Construct the updated Z matrix, CZMAT_OUT, at k points w/ non-frozen s
      do nkp = 1, num_kpts
        if (num_wann .gt. ndimfroz(nkp)) then
          call internal_zmatrix_gamma(cbw, m_matrix_orig_local, u_matrix_opt, rzmat_out(:, :, nkp), &
                                      kmesh_info%wb, indxnfroz, ndimfroz, dis_manifold%ndimwin, &
                                      kmesh_info%nnlist, nkp, kmesh_info%nntot, num_bands, &
                                      num_wann, print_output%timing_level, timer)
        end if
      end do

      call internal_test_convergence(history, delta_womegai, dis_control%conv_tol, iter, &
                                     dis_control%conv_window, dis_converged, error, comm)
      if (allocated(error)) return
      if (dis_converged) then
        write (stdout, '(/13x,a,es10.3,a,i2,a)') &
          '<<<      Delta <', dis_control%conv_tol, &
          '  over ', dis_control%conv_window, ' iterations     >>>'
        write (stdout, '(13x,a)') '<<< Disentanglement convergence criteria satisfied >>>'
        exit
      end if

    end do
    ! [BIG ITERATION LOOP (iter)]

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

    allocate (ceamp(num_bands, num_bands, num_kpts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating ceamp in dis_extract_gamma', comm)
      return
    end if
    allocate (cham(num_bands, num_bands, num_kpts), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error allocating cham in dis_extract_gamma', comm)
      return
    end if

    if (.not. dis_converged) then
      write (stdout, '(/5x,a)') &
        '<<< Warning: Maximum number of disentanglement iterations reached >>>'
      write (stdout, '(10x,a)') '<<< Disentanglement convergence criteria not satisfied >>>'
    end if

    if (icompflag .eq. 1) then
      if (print_output%iprint > 2) then
        write (stdout, ('(/4x,a)')) &
          'WARNING: Complement subspace has zero dimensions at the following k-points:'
        i = 0
        write (stdout, '(4x)', advance='no')
        do nkp = 1, num_kpts
          if (dis_manifold%ndimwin(nkp) .eq. num_wann) then
            i = i + 1
            if (i .le. 12) then
              write (stdout, '(i6)', advance='no') nkp
            else
              i = 1
              write (stdout, '(/4x)', advance='no')
              write (stdout, '(i6)', advance='no') nkp
            end if
          end if
        end do
      end if
    end if

    ! Write the final womegai. This should remain unchanged during the
    ! subsequent minimization of Omega_tilde in wannierise.f90
    ! We store it in the checkpoint file as a sanity check
    write (stdout, '(/8x,a,f14.8,a/)') 'Final Omega_I ', &
      womegai*print_output%lenconfac**2, ' ('//trim(print_output%length_unit)//'^2)'

    ! Set public variable omega_invariant
    omega_invariant = womegai

    ! DIAGONALIZE THE HAMILTONIAN WITHIN THE OPTIMIZED SUBSPACES
    do nkp = 1, num_kpts

      do j = 1, num_wann
        do i = 1, num_wann
          cham(i, j, nkp) = cmplx_0
          do l = 1, dis_manifold%ndimwin(nkp)
            cham(i, j, nkp) = cham(i, j, nkp) + conjg(u_matrix_opt(l, i, nkp)) &
                              *u_matrix_opt(l, j, nkp)*eigval_opt(l, nkp)
          end do
        end do
      end do
      do j = 1, num_wann
        do i = 1, j
          cap_r(i + ((j - 1)*j)/2) = real(cham(i, j, nkp), dp)
        end do
      end do

      call DSPEVX('V', 'A', 'U', num_wann, cap_r, 0.0_dp, 0.0_dp, 0, 0, -1.0_dp, &
                  m, w, rz, num_bands, work, iwork, ifail, info)

      if (info .lt. 0) then
        write (stdout, *) ' *** ERROR *** DSPEVX WHILE DIAGONALIZING HAMILTONIAN'
        write (stdout, *) ' THE ', -info, ' ARGUMENT OF DSPEVX HAD AN ILLEGAL VALUE'
        call set_error_fatal(error, ' dis_extract_gamma: error', comm)
        return
      end if
      if (info .gt. 0) then
        write (stdout, *) ' *** ERROR *** DSPEVX WHILE DIAGONALIZING HAMILTONIAN'
        write (stdout, *) info, 'EIGENVECTORS FAILED TO CONVERGE'
        call set_error_fatal(error, ' dis_extract_gamma: error', comm)
        return
      end if

      cz = cmplx_0
      cz(1:num_wann, 1:num_wann) = cmplx(rz(1:num_wann, 1:num_wann), 0.0_dp, dp)

      ! Store the energy eigenvalues of the optimal subspace (used in wann_ban
      eigval_opt(1:num_wann, nkp) = w(1:num_wann)

      ! CALCULATE AMPLITUDES OF THE CORRESPONDING ENERGY EIGENVECTORS IN TERMS
      ! THE ORIGINAL ("WINDOW SPACE") ENERGY EIGENVECTORS
      do j = 1, num_wann
        do i = 1, dis_manifold%ndimwin(nkp)
          ceamp(i, j, nkp) = cmplx_0
          do l = 1, num_wann
            ceamp(i, j, nkp) = ceamp(i, j, nkp) + cz(l, j)*u_matrix_opt(i, l, nkp)
          end do
        end do
      end do
      ! NKP
    end do
    ! DEBUG
    if (print_output%iprint > 2) then
      write (stdout, '(/,a,/)') '  Eigenvalues inside optimal subspace:'
      do nkp = 1, num_kpts
        write (stdout, '(a,i3,2x,20(f9.5,1x))') '  K-point ', &
          nkp, (eigval_opt(i, nkp), i=1, num_wann)
      end do
    end if
    ! ENDDEBUG

    ! Replace u_matrix_opt by ceamp. Both span the
    ! same space, but the latter is more convenient for the purpose of obtai
    ! an optimal Fourier-interpolated band structure: see Sec. III.E of SMV.
    do nkp = 1, num_kpts
      do j = 1, num_wann
        u_matrix_opt(1:dis_manifold%ndimwin(nkp), j, nkp) = ceamp(1:dis_manifold%ndimwin(nkp), j, nkp)
      end do
    end do

    ! aam: 01/05/2009: added devel_flag if statement as the complementary
    !      subspace code was causing catastrophic seg-faults
    !if (index(print_output%devel_flag, 'compspace') > 0) then

    ! The compliment subspace code needs work: jry
    !  if (icompflag .eq. 1) then
    !    if (print_output%iprint > 2) then
    !      write (stdout, *) 'AT SOME K-POINT(S) COMPLEMENT SUBSPACE HAS ZERO DIMENSIONALITY'
    !      write (stdout, *) '=> DID NOT CREATE FILE COMPSPACE.DAT'
    !    endif
    !  else
    ! DIAGONALIZE THE HAMILTONIAN IN THE COMPLEMENT SUBSPACE, WRITE THE
    ! CORRESPONDING EIGENFUNCTIONS AND ENERGY EIGENVALUES
    !    do nkp = 1, num_kpts
    !      do j = 1, dis_manifold%ndimwin(nkp) - num_wann
    !        do i = 1, dis_manifold%ndimwin(nkp) - num_wann
    !          cham(i, j, nkp) = cmplx_0
    !          do l = 1, dis_manifold%ndimwin(nkp)
    !            cham(i, j, nkp) = cham(i, j, nkp) + conjg(camp(l, i, nkp)) &
    !                              *camp(l, j, nkp)*eigval_opt(l, nkp)
    !          enddo
    !        enddo
    !      enddo
    !      do j = 1, dis_manifold%ndimwin(nkp) - num_wann
    !        do i = 1, j
    !          cap_r(i + ((j - 1)*j)/2) = real(cham(i, j, nkp), dp)
    !        enddo
    !      enddo
    !      ndiff = dis_manifold%ndimwin(nkp) - num_wann

    !     call DSPEVX('V', 'A', 'U', ndiff, cap_r, 0.0_dp, 0.0_dp, 0, 0, -1.0_dp, &
    !                  m, w, rz, num_bands, work, iwork, ifail, info)

    !      if (info .lt. 0) then
    !        write (stdout, *) '*** ERROR *** DSPEVX WHILE DIAGONALIZING HAMILTONIAN'
    !        write (stdout, *) 'THE ', -info, ' ARGUMENT OF DSPEVX HAD AN ILLEGAL VALUE'
    !        call io_error(' dis_extract_gamma: error', stdout, seedname)
    !      endif
    !      if (info .gt. 0) then
    !        write (stdout, *) '*** ERROR *** DSPEVX WHILE DIAGONALIZING HAMILTONIAN'
    !        write (stdout, *) info, 'EIGENVECTORS FAILED TO CONVERGE'
    !        call io_error(' dis_extract_gamma: error', stdout, seedname)
    !      endif

    !      cz = cmplx_0
    !      cz(1:ndiff, 1:ndiff) = cmplx(rz(1:ndiff, 1:ndiff), 0.0_dp, dp)

    ! CALCULATE AMPLITUDES OF THE ENERGY EIGENVECTORS IN THE COMPLEMENT SUBS
    ! TERMS OF THE ORIGINAL ENERGY EIGENVECTORS
    !      do j = 1, dis_manifold%ndimwin(nkp) - num_wann
    !        do i = 1, dis_manifold%ndimwin(nkp)
    !          camp(i, j, nkp) = cmplx_0
    !          do l = 1, dis_manifold%ndimwin(nkp) - num_wann
!write(stdout,*) 'i=',i,'   j=',j,'   l=',l
!write(stdout,*) '           camp(i,j,nkp)=',camp(i,j,nkp)
!write(stdout,*) '           cz(l,j)=',cz(l,j)
!write(stdout,*) '           u_matrix_opt(i,l,nkp)=',u_matrix_opt(i,l,nkp)

! aam: 20/10/2006 -- the second dimension of u_matrix_opt is out of bounds (allocated as num_wann)!
! commenting this line out.
!                     camp(i,j,nkp) = camp(i,j,nkp) + cz(l,j) * u_matrix_opt(i,l,nkp)
    !          enddo
    !        enddo
    !      enddo
    !    enddo
    ! [loop over k points (nkp)]

    !  endif
    ! [if icompflag=1]

    !endif
    ! [if index(devel_flag,'compspace')>0]

    deallocate (history, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating history in dis_extract_gamma', comm)
      return
    end if
    deallocate (cham, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cham in dis_extract_gamma', comm)
      return
    end if
    if (allocated(camp)) then
      deallocate (camp, stat=ierr)
      if (ierr /= 0) then
        call set_error_dealloc(error, 'Error deallocating camp in dis_extract_gamma', comm)
        return
      end if
    end if
    deallocate (ceamp, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating ceamp in dis_extract_gamma', comm)
      return
    end if
    deallocate (wkomegai1, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating wkomegai1 in dis_extract_gamma', comm)
      return
    end if
    deallocate (rz, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating rz in dis_extract_gamma', comm)
      return
    end if
    deallocate (cap_r, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cap_r in dis_extract_gamma', comm)
      return
    end if
    deallocate (work, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating work in dis_extract_gamma', comm)
      return
    end if
    deallocate (cz, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cz in dis_extract_gamma', comm)
      return
    end if
    deallocate (w, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating w in dis_extract_gamma', comm)
      return
    end if
    deallocate (ifail, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating ifail in dis_extract_gamma', comm)
      return
    end if
    deallocate (iwork, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating iwork in dis_extract_gamma', comm)
      return
    end if
    deallocate (cbw, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cbw in dis_extract_gamma', comm)
      return
    end if
    deallocate (cww, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cww in dis_extract_gamma', comm)
      return
    end if
    deallocate (cwb, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error deallocating cwb in dis_extract_gamma', comm)
      return
    end if

    write (stdout, '(1x,a/)') &
      '+----------------------------------------------------------------------------+'

    if (print_output%timing_level > 1) call io_stopwatch_stop('dis: extract_gamma', timer)

    return
    !================================================!
  end subroutine dis_extract_gamma

  subroutine internal_zmatrix_gamma(cbw, m_matrix_orig_local, u_matrix_opt, rmtrx, wb, indxnfroz, &
                                    ndimfroz, ndimwin, nnlist, nkp, nntot, num_bands, num_wann, &
                                    timing_level, timer)
    !================================================!
    !
    !! Compute Z-matrix (Gamma point routine)
    !
    !================================================!
    use w90_constants, only: dp, cmplx_0, cmplx_1
    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) :: timing_level
    integer, intent(in) :: num_bands, num_wann, nkp, nntot
    integer, intent(in) :: ndimwin(:)
    integer, intent(in) :: nnlist(:, :)
    integer, intent(in) :: ndimfroz(:)
    integer, intent(in) :: indxnfroz(:, :)

    real(kind=dp), intent(in) :: wb(:)
    real(kind=dp), intent(out) :: rmtrx(:, :)

    complex(kind=dp), intent(inout) :: cbw(:, :)
    complex(kind=dp), intent(in) :: m_matrix_orig_local(:, :, :, :)
    complex(kind=dp), intent(inout) :: u_matrix_opt(:, :, :)

    ! Internal variables
    integer :: l, m, n, p, q, nn, nkp2, ndimk
    complex(kind=dp) :: csum

    if (timing_level > 1) call io_stopwatch_start('dis: extract_gamma: zmatrix_gamma', timer)

    rmtrx = 0.0_dp
    ndimk = ndimwin(nkp) - ndimfroz(nkp)
    do nn = 1, nntot
      nkp2 = nnlist(nkp, nn)
      call zgemm('N', 'N', num_bands, num_wann, ndimwin(nkp2), cmplx_1, &
                 m_matrix_orig_local(:, :, nn, nkp), num_bands, u_matrix_opt(:, :, nkp2), num_bands, &
                 cmplx_0, cbw, num_bands)
      do n = 1, ndimk
        q = indxnfroz(n, nkp)
        do m = 1, n
          p = indxnfroz(m, nkp)
          csum = cmplx_0
          do l = 1, num_wann
            csum = csum + cbw(p, l)*conjg(cbw(q, l))
          end do
          rmtrx(m, n) = rmtrx(m, n) + wb(nn)*real(csum, dp)
          rmtrx(n, m) = rmtrx(m, n)
        end do
      end do
    end do

    if (timing_level > 1) call io_stopwatch_stop('dis: extract_gamma: zmatrix_gamma', timer)

    return
    !================================================!
  end subroutine internal_zmatrix_gamma

  !================================================!
  subroutine dis_otsu_thresholds(values, lower_bound, upper_bound, nbins, nclasses, thr, &
                                 nclasses_eff, degenerate)
    !================================================!
    !! Multi-class Otsu thresholding of a scalar distribution in
    !! [`lower_bound`, `upper_bound`].
    !! Histograms `values` into `nbins` equal-width bins over that FIXED range
    !! and finds the `nclasses`-1 ascending thresholds that maximise the between-class
    !! variance (exhaustive search, strict tie-break so the first-found maximum in
    !! ascending enumeration wins). Returns bin-centre thresholds in thr(1:nclasses_eff-1).
    !! The number of classes cannot exceed the number of populated bins (a cut
    !! must fall in a gap between clusters), so the effective class count is
    !! `nclasses_eff` = min(nclasses, populated bins); the caller is expected to
    !! note any reduction. Sets `degenerate` = .true. (thresholds undefined) only
    !! when fewer than three bins are populated.
    !================================================!
    implicit none

    real(kind=dp), intent(in) :: values(:)
    real(kind=dp), intent(in) :: lower_bound, upper_bound
    integer, intent(in) :: nbins, nclasses
    real(kind=dp), intent(out) :: thr(nclasses - 1)
    integer, intent(out) :: nclasses_eff
    logical, intent(out) :: degenerate

    integer :: n, i, k, npop, ncut, a, b
    integer, allocatable :: counts(:)
    real(kind=dp), allocatable :: pcum(:), scum(:), hmat(:, :)
    integer, allocatable :: cuts(:), best_cuts(:)
    real(kind=dp) :: d, denom, best_var

    degenerate = .false.
    nclasses_eff = 0
    thr = 0.0_dp

    n = size(values)
    d = (upper_bound - lower_bound)/real(nbins, dp)

    ! Fixed-range histogram; a value at upper_bound lands in the last bin.
    allocate (counts(0:nbins - 1))
    counts = 0
    do i = 1, n
      k = int((values(i) - lower_bound)/d)
      if (k < 0) k = 0
      if (k > nbins - 1) k = nbins - 1
      counts(k) = counts(k) + 1
    end do

    npop = count(counts > 0)
    if (npop < 3) then
      degenerate = .true.
      return
    end if

    ! Cannot resolve more classes than there are populated bins.
    nclasses_eff = min(nclasses, npop)
    ncut = nclasses_eff - 1

    ! Shortcut: as many classes as populated bins -> centres of the first
    ! nclasses_eff-1 populated bins.
    if (npop == nclasses_eff) then
      i = 0
      do k = 0, nbins - 1
        if (counts(k) > 0) then
          i = i + 1
          if (i > ncut) exit
          thr(i) = lower_bound + (real(k, dp) + 0.5_dp)*d
        end if
      end do
      return
    end if

    ! Cumulative count and index-moment (0-based bin index weighting), indexed
    ! from -1 so that segment [a,b] uses pcum(b)-pcum(a-1).
    allocate (pcum(-1:nbins - 1), scum(-1:nbins - 1))
    pcum(-1) = 0.0_dp
    scum(-1) = 0.0_dp
    do k = 0, nbins - 1
      pcum(k) = pcum(k - 1) + real(counts(k), dp)
      scum(k) = scum(k - 1) + real(k, dp)*real(counts(k), dp)
    end do

    ! Between-class variance table for every bin segment [a,b].
    allocate (hmat(0:nbins - 1, 0:nbins - 1))
    hmat = 0.0_dp
    do a = 0, nbins - 1
      do b = a, nbins - 1
        denom = pcum(b) - pcum(a - 1)
        if (denom > 0.0_dp) hmat(a, b) = (scum(b) - scum(a - 1))**2/denom
      end do
    end do

    allocate (cuts(ncut), best_cuts(ncut))
    best_var = -1.0_dp
    best_cuts = 0
    call search(1, 0)

    do i = 1, ncut
      thr(i) = lower_bound + (real(best_cuts(i), dp) + 0.5_dp)*d
    end do

  contains

    recursive subroutine search(level, lo)
      !! Enumerate ascending cut indices; `cuts(level)` is the last bin of the
      !! class below cut `level`. Objective = sum of between-class variance over
      !! the class segments; strict > keeps the first ascending maximum.
      integer, intent(in) :: level, lo
      integer :: idx, seg, aa, bb
      real(kind=dp) :: sigma

      if (level <= ncut) then
        do idx = lo, nbins - ncut + level - 2
          cuts(level) = idx
          call search(level + 1, idx + 1)
        end do
      else
        sigma = 0.0_dp
        aa = 0
        do seg = 1, ncut
          bb = cuts(seg)
          sigma = sigma + hmat(aa, bb)
          aa = cuts(seg) + 1
        end do
        sigma = sigma + hmat(aa, nbins - 1)
        if (sigma > best_var) then
          best_var = sigma
          best_cuts = cuts
        end if
      end if
    end subroutine search

  end subroutine dis_otsu_thresholds

end module w90_disentangle_mod