boltzwann.F90 Source File


This file depends on

sourcefile~~boltzwann.f90~~EfferentGraph sourcefile~boltzwann.f90 boltzwann.F90 sourcefile~comms.f90 comms.F90 sourcefile~boltzwann.f90->sourcefile~comms.f90 sourcefile~constants.f90 constants.F90 sourcefile~boltzwann.f90->sourcefile~constants.f90 sourcefile~dos.f90 dos.F90 sourcefile~boltzwann.f90->sourcefile~dos.f90 sourcefile~error.f90 error.F90 sourcefile~boltzwann.f90->sourcefile~error.f90 sourcefile~get_oper.f90 get_oper.F90 sourcefile~boltzwann.f90->sourcefile~get_oper.f90 sourcefile~io.f90 io.F90 sourcefile~boltzwann.f90->sourcefile~io.f90 sourcefile~postw90_types.f90 postw90_types.F90 sourcefile~boltzwann.f90->sourcefile~postw90_types.f90 sourcefile~readwrite.f90 readwrite.F90 sourcefile~boltzwann.f90->sourcefile~readwrite.f90 sourcefile~spin.f90 spin.F90 sourcefile~boltzwann.f90->sourcefile~spin.f90 sourcefile~types.f90 types.F90 sourcefile~boltzwann.f90->sourcefile~types.f90 sourcefile~utility.f90 utility.F90 sourcefile~boltzwann.f90->sourcefile~utility.f90 sourcefile~wan_ham.f90 wan_ham.F90 sourcefile~boltzwann.f90->sourcefile~wan_ham.f90 sourcefile~comms.f90->sourcefile~constants.f90 sourcefile~error_base.f90 error_base.F90 sourcefile~comms.f90->sourcefile~error_base.f90 sourcefile~dos.f90->sourcefile~comms.f90 sourcefile~dos.f90->sourcefile~constants.f90 sourcefile~dos.f90->sourcefile~error.f90 sourcefile~dos.f90->sourcefile~get_oper.f90 sourcefile~dos.f90->sourcefile~io.f90 sourcefile~dos.f90->sourcefile~postw90_types.f90 sourcefile~dos.f90->sourcefile~spin.f90 sourcefile~dos.f90->sourcefile~types.f90 sourcefile~dos.f90->sourcefile~utility.f90 sourcefile~dos.f90->sourcefile~wan_ham.f90 sourcefile~postw90_common.f90 postw90_common.F90 sourcefile~dos.f90->sourcefile~postw90_common.f90 sourcefile~error.f90->sourcefile~comms.f90 sourcefile~error.f90->sourcefile~error_base.f90 sourcefile~get_oper.f90->sourcefile~comms.f90 sourcefile~get_oper.f90->sourcefile~constants.f90 sourcefile~get_oper.f90->sourcefile~error.f90 sourcefile~get_oper.f90->sourcefile~io.f90 sourcefile~get_oper.f90->sourcefile~postw90_types.f90 sourcefile~get_oper.f90->sourcefile~types.f90 sourcefile~get_oper.f90->sourcefile~utility.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~postw90_types.f90->sourcefile~comms.f90 sourcefile~postw90_types.f90->sourcefile~constants.f90 sourcefile~readwrite.f90->sourcefile~comms.f90 sourcefile~readwrite.f90->sourcefile~constants.f90 sourcefile~readwrite.f90->sourcefile~error.f90 sourcefile~readwrite.f90->sourcefile~io.f90 sourcefile~readwrite.f90->sourcefile~types.f90 sourcefile~readwrite.f90->sourcefile~utility.f90 sourcefile~spin.f90->sourcefile~comms.f90 sourcefile~spin.f90->sourcefile~constants.f90 sourcefile~spin.f90->sourcefile~error.f90 sourcefile~spin.f90->sourcefile~get_oper.f90 sourcefile~spin.f90->sourcefile~postw90_types.f90 sourcefile~spin.f90->sourcefile~types.f90 sourcefile~spin.f90->sourcefile~utility.f90 sourcefile~spin.f90->sourcefile~postw90_common.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~wan_ham.f90->sourcefile~comms.f90 sourcefile~wan_ham.f90->sourcefile~constants.f90 sourcefile~wan_ham.f90->sourcefile~error.f90 sourcefile~wan_ham.f90->sourcefile~get_oper.f90 sourcefile~wan_ham.f90->sourcefile~postw90_types.f90 sourcefile~wan_ham.f90->sourcefile~types.f90 sourcefile~wan_ham.f90->sourcefile~utility.f90 sourcefile~wan_ham.f90->sourcefile~postw90_common.f90 sourcefile~postw90_common.f90->sourcefile~comms.f90 sourcefile~postw90_common.f90->sourcefile~constants.f90 sourcefile~postw90_common.f90->sourcefile~error.f90 sourcefile~postw90_common.f90->sourcefile~io.f90 sourcefile~postw90_common.f90->sourcefile~postw90_types.f90 sourcefile~postw90_common.f90->sourcefile~types.f90 sourcefile~postw90_common.f90->sourcefile~utility.f90 sourcefile~ws_distance.f90 ws_distance.F90 sourcefile~postw90_common.f90->sourcefile~ws_distance.f90 sourcefile~ws_distance.f90->sourcefile~comms.f90 sourcefile~ws_distance.f90->sourcefile~constants.f90 sourcefile~ws_distance.f90->sourcefile~error.f90 sourcefile~ws_distance.f90->sourcefile~io.f90 sourcefile~ws_distance.f90->sourcefile~types.f90 sourcefile~ws_distance.f90->sourcefile~utility.f90

Files dependent on this one

sourcefile~~boltzwann.f90~~AfferentGraph sourcefile~boltzwann.f90 boltzwann.F90 sourcefile~postw90.f90 postw90.F90 sourcefile~postw90.f90->sourcefile~boltzwann.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_boltzwann: Boltzman transport                         !
!                                                            !
!------------------------------------------------------------!

module w90_boltzwann
  !================================================!
  !! Compute Boltzman tranport properties
  !!
  !! BoltzWann routines by
  !! G. Pizzi, D. Volja, B. Kozinsky, M. Fornari and N. Marzari
  !! August, 2012
  !!
  !! Affiliations:
  !! THEOS, EPFL, Station 12, 1015 Lausanne (Switzerland)
  !! DMSE, MIT, 77 Massachusetts Ave, Cambridge, MA, 02139
  !! Central Michigan University, Mount Pleasant, MI 48859
  !! Robert Bosch LLC, Cambridge, MA, 02139
  !!
  !!
  !! Please cite the following paper when publishing results
  !! obtained using the BoltzWann module:
  !!
  !![1] G. Pizzi, D. Volja, B. Kozinsky, M. Fornari, N. Marzari
  !!    Comp. Phys. Comm. 185, 422 (2014)
  !!    DOI: 10.1016/j.cpc.2013.09.015    (arXiv:1305.1587)
  !================================================!

  use w90_comms, only: mpisize, mpirank, comms_gatherv, comms_array_split, comms_reduce, &
                       comms_allreduce, w90_comm_type
  use w90_constants, only: dp, pw90_physical_constants_type, min_smearing_binwidth_ratio
  use w90_dos, only: dos_get_k, dos_get_levelspacing
  use w90_utility, only: utility_inv3, utility_inv2
  use w90_error, only: w90_error_type, set_error_alloc, set_error_dealloc, set_error_fatal, &
                       set_error_input, set_error_fatal, set_error_file

  implicit none

  private

  public :: boltzwann_main

  ! Constants to identify the six components of a tensor when it is stored in packed form
  integer, parameter :: XX = 1
  integer, parameter :: XY = 2
  integer, parameter :: YY = 3
  integer, parameter :: XZ = 4
  integer, parameter :: YZ = 5
  integer, parameter :: ZZ = 6

  character(len=74), parameter :: pub_string_1 = &
                                  "Please cite the following paper when publishing results obtained using    "
  character(len=74), parameter :: pub_string_2 = &
                                  "the BoltzWann module:                                                     "
  character(len=74), parameter :: pub_string_3 = &
                                  "G. Pizzi, D. Volja, B. Kozinsky, M. Fornari, and N. Marzari,              "
  character(len=74), parameter :: pub_string_4 = &
                                  "Comp. Phys. Comm. 185, 422 (2014); DOI:10.1016/j.cpc.2013.09.015          "

contains

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

  subroutine boltzwann_main(pw90_boltzwann, dis_manifold, pw90_dos, kpt_latt, &
                            pw90_band_deriv_degen, postw90_oper, pw90_spin, physics, ws_region, &
                            w90_system, wannier_data, ws_distance, wigner_seitz, print_output, &
                            HH_R, SS_R, v_matrix, u_matrix, eigval, real_lattice, scissors_shift, &
                            mp_grid, num_wann, num_bands, num_kpts, effective_model, &
                            have_disentangled, spin_decomp, seedname, stdout, timer, error, comm)
    !================================================!
    !! This is the main routine of the BoltzWann module.
    !! It calculates the transport coefficients using the Boltzmann transport equation.
    !!
    !! It produces six files that contain:
    !!
    !!  1. the Transport Distribution function (TDF) in units of 1/hbar^2 * eV*fs/angstrom
    !!  2. the electrical conductivity in SI units (1/Ohm/m)
    !!  3. the tensor sigma*S (sigma=el.cond., S=seebeck) in SI units (Ampere/meter/K)
    !!  4. the Seebeck coefficient in SI units (V/K)
    !!  5. the thermal conductivity in SI units (W/meter/K)
    !!  6. if requested, the density of states
    !!
    !! Files from 2 to 4 are output on a grid of (mu,T) points, where mu is the chemical potential in eV and
    !! T is the temperature in Kelvin. The grid is defined in the input.
    !================================================!

    use w90_constants, only: dp
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_comms, only: comms_bcast, w90_comm_type, mpirank
    use w90_types, only: dis_manifold_type, print_output_type, wannier_data_type, &
                         ws_region_type, w90_system_type, ws_distance_type, timer_list_type
    use w90_postw90_types, only: pw90_boltzwann_type, pw90_spin_mod_type, &
                                 pw90_band_deriv_degen_type, pw90_dos_mod_type, pw90_oper_read_type, wigner_seitz_type

    implicit none

    ! arguments
    type(pw90_boltzwann_type), intent(in) :: pw90_boltzwann
    type(dis_manifold_type), intent(in) :: dis_manifold
    type(pw90_dos_mod_type), intent(in) :: pw90_dos
    type(pw90_band_deriv_degen_type), intent(in) :: pw90_band_deriv_degen
    type(pw90_oper_read_type), intent(in) :: postw90_oper
    type(pw90_spin_mod_type), intent(in) :: pw90_spin
    type(print_output_type), intent(in) :: print_output
    type(pw90_physical_constants_type), intent(in) :: physics
    type(ws_region_type), intent(in) :: ws_region
    type(w90_comm_type), intent(in) :: comm
    type(w90_system_type), intent(in) :: w90_system
    type(wannier_data_type), intent(in) :: wannier_data
    type(wigner_seitz_type), intent(inout) :: wigner_seitz
    type(ws_distance_type), intent(inout) :: ws_distance
    type(timer_list_type), intent(inout) :: timer
    type(w90_error_type), allocatable, intent(out) :: error

    complex(kind=dp), allocatable, intent(inout) :: HH_R(:, :, :) !  <0n|r|Rm>
    complex(kind=dp), allocatable, intent(inout) :: SS_R(:, :, :, :) ! <0n|sigma_x,y,z|Rm>
    complex(kind=dp), intent(in) :: v_matrix(:, :, :), u_matrix(:, :, :)

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

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

    character(len=50), intent(in) :: seedname
    logical, intent(in) :: have_disentangled
    logical, intent(in) :: spin_decomp
    logical, intent(in) :: effective_model

    ! local variables
    integer :: TempNumPoints, MuNumPoints, TDFEnergyNumPoints
    integer :: i, j, ierr, EnIdx, TempIdx, MuIdx
    real(kind=dp), allocatable :: TempArray(:), MuArray(:), KTArray(:)
    real(kind=dp), allocatable :: TDF(:, :, :) ! (coordinate,Energy)
    real(kind=dp), allocatable :: TDFEnergyArray(:)
    real(kind=dp), allocatable :: IntegrandArray(:, :) ! (coordinate, Energy) at a given T and mu
    real(kind=dp) :: SigmaS_FP(3, 3), ThisElCond(3, 3), ElCondInverse(3, 3), ThisSeebeck(3, 3)
    real(kind=dp) :: ThisElCond2d(2, 2), ElCondInverse2d(2, 2)
    !real(kind=dp), dimension(6) :: ElCondTimesSeebeck
    real(kind=dp), allocatable :: ElCond(:, :, :) ! (coordinate,Temp, mu)
    real(kind=dp), allocatable :: SigmaS(:, :, :) ! (coordinate,Temp, mu)
    real(kind=dp), allocatable :: Seebeck(:, :, :) ! (coordinate,Temp, mu)
    real(kind=dp), allocatable :: Kappa(:, :, :) ! (coordinate,Temp, mu)
    real(kind=dp), allocatable :: LocalElCond(:, :) ! (coordinate,Temp+mu combined index)
    real(kind=dp), allocatable :: LocalSigmaS(:, :) ! (coordinate,Temp+mu combined index)
    real(kind=dp), allocatable :: LocalSeebeck(:, :) ! (coordinate,Temp+mu combined index)
    real(kind=dp), allocatable :: LocalKappa(:, :) ! (coordinate,Temp+mu combined index)
    real(kind=dp) :: Determinant
    integer :: tdf_unit, elcond_unit, sigmas_unit, seebeck_unit, kappa_unit, ndim

    integer :: LocalIdx, GlobalIdx
    ! I also add 3 times the smearing on each side of the TDF energy array to take into account also possible smearing effects
    real(kind=dp), parameter :: TDF_exceeding_energy_times_smr = 3._dp
    real(kind=dp) :: TDF_exceeding_energy
    real(kind=dp) :: cell_volume
    integer :: NumberZeroDet

    integer, allocatable :: counts(:), displs(:)
    integer :: my_node_id, num_nodes
    logical :: on_root = .false.

    my_node_id = mpirank(comm)
    num_nodes = mpisize(comm)
    if (my_node_id == 0) on_root = .true.
    allocate (counts(0:num_nodes - 1))
    allocate (displs(0:num_nodes - 1))

    cell_volume = real_lattice(1, 1)*(real_lattice(2, 2)*real_lattice(3, 3) - real_lattice(3, 2)*real_lattice(2, 3)) + &
                  real_lattice(1, 2)*(real_lattice(2, 3)*real_lattice(3, 1) - real_lattice(3, 3)*real_lattice(2, 1)) + &
                  real_lattice(1, 3)*(real_lattice(2, 1)*real_lattice(3, 2) - real_lattice(3, 1)*real_lattice(2, 2))

    if (print_output%iprint > 0 .and. print_output%timing_level > 0) call io_stopwatch_start('boltzwann_main', timer)

    if (print_output%iprint > 0) then
      write (stdout, *)
      write (stdout, '(1x,a)') '*---------------------------------------------------------------------------*'
      write (stdout, '(1x,a)') '|                   Boltzmann Transport (BoltzWann module)                  |'
      write (stdout, '(1x,a)') '*---------------------------------------------------------------------------*'
      write (stdout, '(1x,a)') '| '//pub_string_1//'|'
      write (stdout, '(1x,a)') '| '//pub_string_2//'|'
      write (stdout, '(1x,a)') '| '//pub_string_3//'|'
      write (stdout, '(1x,a)') '| '//pub_string_4//'|'
      write (stdout, '(1x,a)') '*---------------------------------------------------------------------------*'
      write (stdout, *)
    end if

    if (print_output%iprint > 0) then
      if (pw90_boltzwann%dir_num_2d /= 0) then
        write (stdout, '(1x,a)') '>                                                                           <'
        write (stdout, '(1x,a)') '> NOTE! Using the 2D version for the calculation of the Seebeck             <'
        if (pw90_boltzwann%dir_num_2d == 1) then
          write (stdout, '(1x,a)') '>       coefficient, where the non-periodic direction is x.                 <'
        elseif (pw90_boltzwann%dir_num_2d == 2) then
          write (stdout, '(1x,a)') '>       coefficient, where the non-periodic direction is y.                 <'
        elseif (pw90_boltzwann%dir_num_2d == 3) then
          write (stdout, '(1x,a)') '>       coefficient, where the non-periodic direction is z.                 <'
        end if
        write (stdout, '(1x,a)') '>                                                                           <'
        write (stdout, '(1x,a)') ''
      end if
    end if

    ! separate error condition from info printout above (which may be avoided entirely in lib mode?)
    if (pw90_boltzwann%dir_num_2d < 0 .or. pw90_boltzwann%dir_num_2d > 3) then
      call set_error_input(error, 'Unrecognized value of pw90_boltzwann_2d_dir_num', comm)
      return
    end if

    ! I precalculate the TempArray and the MuArray
    TempNumPoints = int(floor((pw90_boltzwann%temp_max - pw90_boltzwann%temp_min)/pw90_boltzwann%temp_step)) + 1
    allocate (TempArray(TempNumPoints), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating TempArray in boltzwann_main', comm)
      return
    end if
    do i = 1, TempNumPoints
      TempArray(i) = pw90_boltzwann%temp_min + real(i - 1, dp)*pw90_boltzwann%temp_step
    end do

    ! This array contains the same temperatures of the TempArray, but multiplied by k_boltzmann, in units of eV
    allocate (KTArray(TempNumPoints), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating KTArray in boltzwann_main', comm)
      return
    end if
    ! (k_B in eV/kelvin is equal to k_B_SI / elem_charge_SI)
    KTArray = TempArray*physics%k_B_SI/physics%elem_charge_SI

    MuNumPoints = int(floor((pw90_boltzwann%mu_max - pw90_boltzwann%mu_min)/pw90_boltzwann%mu_step)) + 1
    allocate (MuArray(MuNumPoints), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating MuArray in boltzwann_main', comm)
      return
    end if
    do i = 1, MuNumPoints
      MuArray(i) = pw90_boltzwann%mu_min + real(i - 1, dp)*pw90_boltzwann%mu_step
    end do

    if (pw90_boltzwann%tdf_smearing%use_adaptive) then
      call set_error_input(error, 'Adaptive smearing not allowed in Boltzwann TDF', comm)
      return
    end if
    ! I precalculate the TDFEnergyArray
    ! I assume that dis_win_min and dis_win_max are set to sensible values, related to the max and min energy
    ! This is true if the .eig file is present. I can assume its presence since we need it to interpolate the
    ! bands.
    ! I also add 3 times the smearing on each side of the TDF energy array to take into account also possible smearing effects,
    ! or at least 0.2 eV
    TDF_exceeding_energy = max(TDF_exceeding_energy_times_smr*pw90_boltzwann%tdf_smearing%fixed_width, 0.2_dp)
    TDFEnergyNumPoints = int(floor((dis_manifold%win_max - dis_manifold%win_min &
                                    + 2._dp*TDF_exceeding_energy)/pw90_boltzwann%tdf_energy_step)) + 1
    if (TDFEnergyNumPoints .eq. 1) TDFEnergyNumPoints = 2
    allocate (TDFEnergyArray(TDFEnergyNumPoints), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating TDFEnergyArray in boltzwann_main', comm)
      return
    end if
    do i = 1, TDFEnergyNumPoints
      TDFEnergyArray(i) = dis_manifold%win_min - TDF_exceeding_energy &
                          + real(i - 1, dp)*pw90_boltzwann%tdf_energy_step
    end do

    if (spin_decomp) then
      ndim = 3
    else
      ndim = 1
    end if

    ! I allocate the array for the TDF
    allocate (TDF(6, TDFEnergyNumPoints, ndim), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating TDF in boltzwann_main', comm)
      return
    end if

    ! I call the subroutine that calculates the Transport Distribution Function
    call calcTDFandDOS(pw90_boltzwann, dis_manifold, pw90_dos, kpt_latt, postw90_oper, &
                       pw90_band_deriv_degen, pw90_spin, ws_region, print_output, wannier_data, &
                       ws_distance, wigner_seitz, HH_R, SS_R, u_matrix, v_matrix, eigval, &
                       real_lattice, TDF, TDFEnergyArray, cell_volume, scissors_shift, mp_grid, &
                       num_bands, num_kpts, num_wann, w90_system%num_valence_bands, &
                       w90_system%num_elec_per_state, effective_model, have_disentangled, &
                       spin_decomp, seedname, stdout, timer, error, comm)
    if (allocated(error)) return

    ! The TDF array contains now the TDF, or more precisely
    ! hbar^2 * TDF in units of eV * fs / angstrom

    ! I print on file the TDF
    if (on_root) then
      open (newunit=tdf_unit, file=trim(seedname)//'_tdf.dat')
      write (tdf_unit, '(A)') "# Written by the BoltzWann module of the Wannier90 code."
      write (tdf_unit, '(A)') "# Transport distribution function (in units of 1/hbar^2 * eV * fs / angstrom)"// &
        " vs energy in eV"
      write (tdf_unit, '(A)') "# Content of the columns:"
      write (tdf_unit, '(A)') "# Energy TDF_xx TDF_xy TDF_yy TDF_xz TDF_yz TDF_zz"
      write (tdf_unit, '(A)') '#   (if spin decomposition is required, 12 further columns are provided, with the 6'
      write (tdf_unit, '(A)') '#    components of the TDF for the spin up, followed by those for the spin down)'
      do i = 1, size(TDFEnergyArray)
        if (ndim .eq. 1) then
          write (tdf_unit, 101) TDFEnergyArray(i), TDF(:, i, 1)
        else
          write (tdf_unit, 102) TDFEnergyArray(i), TDF(:, i, :)
        end if
      end do
      close (tdf_unit)
      if (print_output%iprint > 1) &
        write (stdout, '(3X,A)') "Transport distribution function written on the "//trim(seedname)//"_tdf.dat file."
    end if

    ! *********************************************************************************
    ! I got the TDF and I printed it. Now I use it to calculate the transport properties.

    if (on_root .and. (print_output%timing_level > 0)) call io_stopwatch_start('boltzwann_main: calc_props', timer)

    ! I obtain the counts and displs arrays, which tell how I should partition a big array
    ! on the different nodes.
    call comms_array_split(TempNumPoints*MuNumPoints, counts, displs, comm)

    ! I allocate the arrays for the spectra
    ! Allocate at least 1 entry
    allocate (LocalElCond(6, max(1, counts(my_node_id))), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating LocalElCond in boltzwann_main', comm)
      return
    end if
    allocate (LocalSigmaS(6, max(1, counts(my_node_id))), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating LocalSigmaS in boltzwann_main', comm)
      return
    end if
    allocate (LocalSeebeck(9, max(1, counts(my_node_id))), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating LocalSeebeck in boltzwann_main', comm)
      return
    end if
    allocate (LocalKappa(6, max(1, counts(my_node_id))), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating LocalKappa in boltzwann_main', comm)
      return
    end if
    LocalSigmaS = 0._dp
    LocalElCond = 0._dp
    LocalSeebeck = 0._dp
    LocalKappa = 0._dp

    ! I allocate the array that I will use to store the functions to be integrated
    allocate (IntegrandArray(6, TDFEnergyNumPoints), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating FermiDerivArray in boltzwann_main', comm)
      return
    end if

    NumberZeroDet = 0
    ! Now, I calculate the various spectra for all mu and T values
    do LocalIdx = 1, counts(my_node_id)
      ! GlobalIdx is an index from 0 to TempNumPoints*MuNumPoints-1
      GlobalIdx = displs(my_node_id) + (LocalIdx - 1)
      ! MuIdx goes from 1 to MuNumPoints
      MuIdx = GlobalIdx/TempNumPoints + 1
      ! TempIdx goes from 1 to TempNumPoints
      TempIdx = GlobalIdx - TempNumPoints*(MuIdx - 1) + 1

      ! For the calculation of the properties, I only use the total TDF (i.e., unresolved for spin)
      IntegrandArray = TDF(:, :, 1)
      do EnIdx = 1, TDFEnergyNumPoints
        IntegrandArray(:, EnIdx) = IntegrandArray(:, EnIdx)* &
                                   MinusFermiDerivative(E=TDFEnergyArray(EnIdx), mu=MuArray(MuIdx), KT=KTArray(TempIdx))
      end do
      ! Now, IntegrandArray contains (-dn/dE) * TDF_ij(E), where n is the Fermi distribution function
      ! Its integral is ElCond_ij/e^2
      LocalElCond(:, LocalIdx) = sum(IntegrandArray, DIM=2)*pw90_boltzwann%tdf_energy_step
      ! ElCond contains now (hbar^2/e^2) * sigma in eV*fs/angstrom, where sigma is the conductivity tensor
      ! (note that MinusFermiDerivative is in units of 1/eV, so that when I perform the integration
      ! ElCond has the same units of TDF)

      ! I store in ThisElCond the conductivity tensor in standard format
      ! Store both the upper and lower triangle
      do j = 1, 3
        do i = 1, j
          ThisElCond(i, j) = LocalElCond(i + ((j - 1)*j)/2, LocalIdx)
          ThisElCond(j, i) = LocalElCond(i + ((j - 1)*j)/2, LocalIdx)
        end do
      end do

      ! I calculate the inverse matrix of the conductivity
      if (pw90_boltzwann%dir_num_2d /= 0) then
        ! Invert only the appropriate 2x2 submatrix
        if (pw90_boltzwann%dir_num_2d == 1) then
          ThisElCond2d(1, 1) = ThisElCond(2, 2)
          ThisElCond2d(1, 2) = ThisElCond(2, 3)
          ThisElCond2d(2, 1) = ThisElCond(3, 2)
          ThisElCond2d(2, 2) = ThisElCond(3, 3)
        elseif (pw90_boltzwann%dir_num_2d == 2) then
          ThisElCond2d(1, 1) = ThisElCond(1, 1)
          ThisElCond2d(1, 2) = ThisElCond(1, 3)
          ThisElCond2d(2, 1) = ThisElCond(3, 1)
          ThisElCond2d(2, 2) = ThisElCond(3, 3)
        elseif (pw90_boltzwann%dir_num_2d == 3) then
          ThisElCond2d(1, 1) = ThisElCond(1, 1)
          ThisElCond2d(1, 2) = ThisElCond(1, 2)
          ThisElCond2d(2, 1) = ThisElCond(2, 1)
          ThisElCond2d(2, 2) = ThisElCond(2, 2)
          ! I do not do the else case because this was already checked at the beginning
          ! of this routine
        end if
        call utility_inv2(ThisElCond2d, ElCondInverse2d, Determinant)
        ElCondInverse = 0._dp ! Other elements must be set to zero
        if (pw90_boltzwann%dir_num_2d == 1) then
          ElCondInverse(2, 2) = ElCondInverse2d(1, 1)
          ElCondInverse(2, 3) = ElCondInverse2d(1, 2)
          ElCondInverse(3, 2) = ElCondInverse2d(2, 1)
          ElCondInverse(3, 3) = ElCondInverse2d(2, 2)
        elseif (pw90_boltzwann%dir_num_2d == 2) then
          ElCondInverse(1, 1) = ElCondInverse2d(1, 1)
          ElCondInverse(1, 3) = ElCondInverse2d(1, 2)
          ElCondInverse(3, 1) = ElCondInverse2d(2, 1)
          ElCondInverse(3, 3) = ElCondInverse2d(2, 2)
        elseif (pw90_boltzwann%dir_num_2d == 3) then
          ElCondInverse(1, 1) = ElCondInverse2d(1, 1)
          ElCondInverse(1, 2) = ElCondInverse2d(1, 2)
          ElCondInverse(2, 1) = ElCondInverse2d(2, 1)
          ElCondInverse(2, 2) = ElCondInverse2d(2, 2)
          ! I do not do the else case because this was already checked at the beginning
          ! of this routine
        end if
      else
        call utility_inv3(ThisElCond, ElCondInverse, Determinant)
      end if
      if (Determinant .eq. 0._dp) then
        NumberZeroDet = NumberZeroDet + 1
        ! If the determinant is zero (i.e., zero conductivity along a given direction)
        ! I set the Inverse to zero, so that the Seebeck is zero. I will also issue
        ! a warning.
        ElCondInverse = 0._dp
      else
        ! The routine returns the adjoint of ThisElCond; in order to get
        ! the inverse, I have to calculate ElCondInverse / Determinant
        ElCondInverse = ElCondInverse/Determinant
      end if

      ! Now, I multiply IntegrandArray by (E-mu): then, IntegrandArray contains
      ! (-dn/dE) * TDF_ij(E) * (E-mu) and its integral is (ElCond*Seebeck)_ij * T / e, where
      ! T is the temperature
      do EnIdx = 1, TDFEnergyNumPoints
        IntegrandArray(:, EnIdx) = IntegrandArray(:, EnIdx)*(TDFEnergyArray(EnIdx) - MuArray(MuIdx))
      end do

      ! I store in SigmaS_FP the product of the two tensors in full-packed format
      LocalSigmaS(:, LocalIdx) = sum(IntegrandArray, DIM=2)*pw90_boltzwann%tdf_energy_step/TempArray(TempIdx)
      do j = 1, 3
        do i = 1, j
          ! Both upper and lower diagonal
          SigmaS_FP(i, j) = LocalSigmaS(i + ((j - 1)*j)/2, LocalIdx)
          SigmaS_FP(j, i) = LocalSigmaS(i + ((j - 1)*j)/2, LocalIdx)
        end do
      end do
      ! Now, LocalSigmaS (and SigmaS_FP) contain
      ! [ElCond*Seebeck] * hbar^2/e in units of eV^2*fs/angstrom/kelvin

      ! I calculate ElCond^(-1) . (LocalSigmaS) = Seebeck in fully-packed format and then
      ! store it in the LocalSeebeck array (not in packed format because, unless S and sigma
      ! commute, there is no a-priori reason for which S should be symmetric - even if
      ! probably one can find physical reasons).

      ! I invert the sign because the electron charge is < 0
      ThisSeebeck = -matmul(ElCondInverse, SigmaS_FP)
      ! Reshuffle in 1D; order: xx, xy, xz, yx, yy, yz, zx, zy, zz
      ! Note that this is different
      do j = 1, 3
        do i = 1, 3
          LocalSeebeck((i - 1)*3 + j, LocalIdx) = ThisSeebeck(i, j)
        end do
      end do

      ! Now, Seebeck contains the Seebeck coefficient in volt / Kelvin. In fact:
      ! - ElCond contains (hbar^2/e^2) * sigma in eV*fs/angstrom, where sigma is the value of the
      !   conductivity, i.e. it is the conductivity in units of (e^2/hbar^2) * eV * fs / angstrom
      ! - ElCondInverse is in thus in units of (hbar^2/e^2) / eV / fs * angstrom
      ! - ElCondTimesSeebeck is in units of  e / hbar^2 * eV^2 * fs / angstrom / Kelvin
      ! therefore ThisSeebeck, which has the units of ElCondInverse * ElCondTimesSeebeck, i.e.
      ! [(hbar^2/e^2) / eV / fs * angstrom] * [ e / hbar^2 * eV^2 * fs / angstrom / kelvin] =
      ! eV/e/kelvin = volt/kelvin

      ! Now, I multiply IntegrandArray by (E-mu): then, IntegrandArray contains
      ! (-dn/dE) * TDF_ij(E) * (E-mu)^2 and its integral is (Kappa)_ij * T
      do EnIdx = 1, TDFEnergyNumPoints
        IntegrandArray(:, EnIdx) = IntegrandArray(:, EnIdx)*(TDFEnergyArray(EnIdx) - MuArray(MuIdx))
      end do
      LocalKappa(:, LocalIdx) = sum(IntegrandArray, DIM=2)*pw90_boltzwann%tdf_energy_step/TempArray(TempIdx)
      ! Kappa contains now the thermal conductivity in units of
      ! 1/hbar^2 * eV^3*fs/angstrom/kelvin
    end do
    ! I check if there were (mu,T) pairs for which we got sigma = 0
    call comms_reduce(NumberZeroDet, 1, 'SUM', error, comm)
    if (allocated(error)) return
    if (on_root) then
      if ((NumberZeroDet .gt. 0)) then
        write (stdout, '(1X,A,I0,A)') "> WARNING! There are ", NumberZeroDet, " (mu,T) pairs for which the electrical"
        write (stdout, '(1X,A)') ">          conductivity has zero determinant."
        write (stdout, '(1X,A)') ">          Seebeck coefficient set to zero for those pairs."
        write (stdout, '(1X,A)') ">          Check if this is physical or not."
        write (stdout, '(1X,A)') ">          (If you are dealing with a 2D system, set the pw90_boltzwann_2d_dir flag.)"
        write (stdout, '(1X,A)') ""
      end if
    end if

    ! Now, I multiply by the correct factors to obtain the tensors in SI units

    ! **** Electrical conductity ****
    ! Now, ElCond is in units of (e^2/hbar^2) * eV * fs / angstrom
    ! I want the conductivity in units of 1/Ohm/meter (i.e., SI units). The conversion factor is then
    ! e^2/hbar^2 * eV*fs/angstrom * Ohm * meter; we use the constants in constants.F90 by noting that:
    ! Ohm = V/A = V*s/C, where A=ampere, C=coulomb
    ! Then we have: (e/C)^3/hbar^2 * V*s^2 * V * C^2 * (meter / angstrom)  * (fs / s)
    ! Now: e/C = elem_charge_SI; CV=Joule, CV/s=Watt, hbar/Watt = hbar_SI;
    ! moreover meter / angstrom = 1e10, fs / s = 1e-15 so that we finally get the
    ! CONVERSION FACTOR: elem_charge_SI**3 / (hbar_SI**2) * 1.e-5_dp
    LocalElCond = LocalElCond*physics%elem_charge_SI**3/(physics%hbar_SI**2)*1.e-5_dp
    ! THIS IS NOW THE ELECTRICAL CONDUCTIVITY IN SI UNITS, i.e. in 1/Ohm/meter

    ! *** Sigma * S ****
    ! Again, as above or below for Kappa, the conversion factor is
    ! * elem_charge_SI**3 / (hbar_SI**2) * 1.e-5_dp
    ! and brings the result to Ampere/m/K
    LocalSigmaS = LocalSigmaS*physics%elem_charge_SI**3/(physics%hbar_SI**2)*1.e-5_dp

    ! **** Seebeck coefficient ****
    ! THE SEEECK COEFFICIENTS IS ALREADY IN volt/kelvin, so nothing has to be done

    ! **** K coefficient (approx. the Thermal conductivity) ****
    ! Now, Kappa is in units of 1/hbar^2 * eV^3*fs/angstrom/K
    ! I want it to be in units of W/m/K (i.e., SI units). Then conversion factor is then
    ! 1/hbar^2 * eV^3 * fs/angstrom/K / W * m * K =
    ! 1/hbar^2 * eV^3 * fs / W * (m/angstrom) =
    ! 1/hbar^2 * C^3 * V^3 * s / W * [e/C]^3 * (m/angstrom) * (fs / s) =
    ! 1/hbar^2 * J^2 * [e/C]^3 * (m/angstrom) * (fs / s) =
    ! elem_charge_SI**3 / (hbar_SI**2) * 1.e-5_dp, i.e. the same conversion factor as above
    LocalKappa = LocalKappa*physics%elem_charge_SI**3/(physics%hbar_SI**2)*1.e-5_dp
    ! THIS IS NOW THE THERMAL CONDUCTIVITY IN SI UNITS, i.e. in W/meter/K

    ! Now I send the different pieces to the local node
    if (on_root) then
      allocate (ElCond(6, TempNumPoints, MuNumPoints), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating ElCond in boltzwann_main', comm)
        return
      end if
      allocate (SigmaS(6, TempNumPoints, MuNumPoints), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating SigmaS in boltzwann_main', comm)
        return
      end if
      allocate (Seebeck(9, TempNumPoints, MuNumPoints), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating Seebeck in boltzwann_main', comm)
        return
      end if
      allocate (Kappa(6, TempNumPoints, MuNumPoints), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating Kappa in boltzwann_main', comm)
        return
      end if
    else
      ! In principle, this should not be needed, because we use ElCond,
      ! Seebeck and Kappa only on the root node. However, since all
      ! processors call comms_gatherv a few lines below, and one argument
      ! is ElCond(1,1,1), some compilers complain.
      allocate (ElCond(1, 1, 1), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating ElCond in boltzwann_main (2)', comm)
        return
      end if
      allocate (SigmaS(1, 1, 1), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating SigmaS in boltzwann_main (2)', comm)
        return
      end if
      allocate (Seebeck(1, 1, 1), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating Seebeck in boltzwann_main (2)', comm)
        return
      end if
      allocate (Kappa(1, 1, 1), stat=ierr)
      if (ierr /= 0) then
        call set_error_alloc(error, 'Error in allocating Kappa in boltzwann_main (2)', comm)
        return
      end if
    end if

    ! The 6* factors are due to the fact that for each (T,mu) pair we have 6 components (xx,xy,yy,xz,yz,zz)
    ! NOTE THAT INSTEAD SEEBECK IS A FULL MATRIX AND HAS 9 COMPONENTS!
    call comms_gatherv(LocalElCond, 6*counts(my_node_id), ElCond, 6*counts, 6*displs, error, comm)
    if (allocated(error)) return
    call comms_gatherv(LocalSigmaS, 6*counts(my_node_id), SigmaS, 6*counts, 6*displs, error, comm)
    if (allocated(error)) return
    call comms_gatherv(LocalSeebeck, 9*counts(my_node_id), Seebeck, 9*counts, 9*displs, error, comm)
    if (allocated(error)) return
    call comms_gatherv(LocalKappa, 6*counts(my_node_id), Kappa, 6*counts, 6*displs, error, comm)
    if (allocated(error)) return

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

    ! Open files and print
    if (on_root) then
      open (newunit=elcond_unit, file=trim(seedname)//'_elcond.dat')
      write (elcond_unit, '(A)') "# Written by the BoltzWann module of the Wannier90 code."
      write (elcond_unit, '(A)') "# [Electrical conductivity in SI units, i.e. in 1/Ohm/m]"
      write (elcond_unit, '(A)') "# Mu(eV) Temp(K) ElCond_xx ElCond_xy ElCond_yy ElCond_xz ElCond_yz ElCond_zz"
      do MuIdx = 1, MuNumPoints
        do TempIdx = 1, TempNumPoints
          write (elcond_unit, 103) MuArray(MuIdx), TempArray(TempIdx), ElCond(:, TempIdx, MuIdx)
        end do
      end do
      close (elcond_unit)
      if (print_output%iprint > 1) &
        write (stdout, '(3X,A)') "Electrical conductivity written on the "//trim(seedname)//"_elcond.dat file."

      open (newunit=sigmas_unit, file=trim(seedname)//'_sigmas.dat')
      write (sigmas_unit, '(A)') "# Written by the BoltzWann module of the Wannier90 code."
      write (sigmas_unit, '(A)') "# [(Electrical conductivity * Seebeck coefficient) in SI units, i.e. in Ampere/m/K]"
      write (sigmas_unit, '(A)') "# Mu(eV) Temp(K) (Sigma*S)_xx (Sigma*S)_xy (Sigma*S)_yy (Sigma*S)_xz (Sigma*S)_yz (Sigma*S)_zz"
      do MuIdx = 1, MuNumPoints
        do TempIdx = 1, TempNumPoints
          write (sigmas_unit, 103) MuArray(MuIdx), TempArray(TempIdx), SigmaS(:, TempIdx, MuIdx)
        end do
      end do
      close (sigmas_unit)
      if (print_output%iprint > 1) write (stdout, '(3X,A)') &
        "sigma*S (sigma=el. conductivity, S=Seebeck coeff.) written on the "//trim(seedname)//"_sigmas.dat file."

      open (newunit=seebeck_unit, file=trim(seedname)//'_seebeck.dat')
      write (seebeck_unit, '(A)') "# Written by the BoltzWann module of the Wannier90 code."
      write (seebeck_unit, '(A)') "# [Seebeck coefficient in SI units, i.e. in V/K]"
      write (seebeck_unit, '(A)') &
        "# Mu(eV) Temp(K) Seebeck_xx Seebeck_xy Seebeck_xz Seebeck_yx Seebeck_yy Seebeck_yz Seebeck_zx Seebeck_zy Seebeck_zz"
      do MuIdx = 1, MuNumPoints
        do TempIdx = 1, TempNumPoints
          write (seebeck_unit, 104) MuArray(MuIdx), TempArray(TempIdx), Seebeck(:, TempIdx, MuIdx)
        end do
      end do
      close (seebeck_unit)
      if (print_output%iprint > 1) &
        write (stdout, '(3X,A)') "Seebeck coefficient written on the "//trim(seedname)//"_seebeck.dat file."

      open (newunit=kappa_unit, file=trim(seedname)//'_kappa.dat')
      write (kappa_unit, '(A)') "# Written by the BoltzWann module of the Wannier90 code."
      write (kappa_unit, '(A)') "# [K coefficient in SI units, i.e. in W/m/K]"
      write (kappa_unit, '(A)') "# [the K coefficient is defined in the documentation, and is an ingredient of"
      write (kappa_unit, '(A)') "#  the thermal conductivity. See the docs for further information.]"
      write (kappa_unit, '(A)') "# Mu(eV) Temp(K) Kappa_xx Kappa_xy Kappa_yy Kappa_xz Kappa_yz Kappa_zz"
      do MuIdx = 1, MuNumPoints
        do TempIdx = 1, TempNumPoints
          write (kappa_unit, 103) MuArray(MuIdx), TempArray(TempIdx), Kappa(:, TempIdx, MuIdx)
        end do
      end do
      close (kappa_unit)
      if (print_output%iprint > 1) &
        write (stdout, '(3X,A)') "K coefficient written on the "//trim(seedname)//"_kappa.dat file."
    end if

    if (on_root) then
      write (stdout, '(3X,A)') "Transport properties calculated."
      write (stdout, *)
      write (stdout, '(1x,a)') '*---------------------------------------------------------------------------*'
      write (stdout, '(1x,a)') '|                        End of the BoltzWann module                        |'
      write (stdout, '(1x,a)') '*---------------------------------------------------------------------------*'
      write (stdout, *)
    end if

    ! Before ending, I deallocate memory
    deallocate (TempArray, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating TempArray in boltzwann_main', comm)
      return
    end if
    deallocate (KTArray, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating KTArray in boltzwann_main', comm)
      return
    end if
    deallocate (MuArray, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating MuArray in boltzwann_main', comm)
      return
    end if
    deallocate (TDFEnergyArray, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating TDFEnergyArray in boltzwann_main', comm)
      return
    end if
    deallocate (TDF, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating TDF in boltzwann_main', comm)
      return
    end if
    deallocate (LocalElCond, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating LocalElCond in boltzwann_main', comm)
      return
    end if
    deallocate (LocalSigmaS, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating LocalSigmaS in boltzwann_main', comm)
      return
    end if
    deallocate (LocalSeebeck, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating LocalSeebeck in boltzwann_main', comm)
      return
    end if
    deallocate (LocalKappa, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating LocalKappa in boltzwann_main', comm)
      return
    end if

    deallocate (ElCond, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating ElCond in boltzwann_main', comm)
      return
    end if
    deallocate (SigmaS, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating SigmaS in boltzwann_main', comm)
      return
    end if
    deallocate (Seebeck, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating Seebeck in boltzwann_main', comm)
      return
    end if
    deallocate (Kappa, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating Kappa in boltzwann_main', comm)
      return
    end if

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

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

101 FORMAT(7G18.10)
102 FORMAT(19G18.10)
103 FORMAT(8G18.10)
104 FORMAT(11G18.10)

  end subroutine boltzwann_main

  !================================================!
  subroutine calcTDFandDOS(pw90_boltzwann, dis_manifold, pw90_dos, kpt_latt, postw90_oper, &
                           pw90_band_deriv_degen, pw90_spin, ws_region, print_output, &
                           wannier_data, ws_distance, wigner_seitz, HH_R, SS_R, u_matrix, &
                           v_matrix, eigval, real_lattice, TDF, TDFEnergyArray, cell_volume, &
                           scissors_shift, mp_grid, num_bands, num_kpts, num_wann, &
                           num_valence_bands, num_elec_per_state, effective_model, &
                           have_disentangled, spin_decomp, seedname, stdout, timer, error, comm)
    !================================================!
    !! This routine calculates the Transport Distribution Function $$\sigma_{ij}(\epsilon)$$ (TDF)
    !! in units of 1/hbar^2 * eV*fs/angstrom, and possibly the DOS.
    !!
    !! The TDFEnergyArray must be already allocated and initialized with the
    !! energies in eV before calling this routine.
    !!
    !!  The TDF array must be already allocated with dimensions 6 * size(TDFEnergyArray) * ndim, before calling
    !! this routine, where ndim=1 if spin_decomp==false, or ndim=3 if spin_decomp==true. This is not checked.
    !!
    !!  If run in parallel, at the end each processor will have a copy of the full TDF array
    !!
    !!  We assume that the TDFEnergyArray is uniformely spaced and that it has at least
    !!       two elements (no checks are performed on this).
    !!
    !! Note that the order of indices of TDF is different w.r.t. the DOS array (the energy is not the first but
    !!       the second index)
    !!
    !!  If the input flag pw90_boltzwann_bandshift is set to .true., the code will also shift the
    !!       conduction bands by a given amount, as defined by the pw90_boltzwann_bandshift_energyshift
    !!       and pw90_boltzwann_bandshift_firstband input flags.
    !!
    !================================================!

    use w90_constants, only: dp
    use w90_comms, only: comms_bcast, w90_comm_type, mpirank
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_utility, only: utility_recip_lattice_base
    use w90_get_oper, only: get_HH_R, get_SS_R
    use w90_types, only: print_output_type, wannier_data_type, dis_manifold_type, &
                         ws_region_type, ws_distance_type, timer_list_type
    use w90_postw90_types, only: pw90_boltzwann_type, pw90_spin_mod_type, &
                                 pw90_band_deriv_degen_type, pw90_dos_mod_type, pw90_oper_read_type, wigner_seitz_type
    use w90_readwrite, only: w90_readwrite_get_smearing_type
    use w90_wan_ham, only: wham_get_eig_deleig

    implicit none

    ! arguments
    type(pw90_boltzwann_type), intent(in) :: pw90_boltzwann
    type(dis_manifold_type), intent(in) :: dis_manifold
    type(pw90_dos_mod_type), intent(in) :: pw90_dos
    real(kind=dp), intent(in) :: kpt_latt(:, :)
    type(pw90_band_deriv_degen_type), intent(in) :: pw90_band_deriv_degen
    type(pw90_oper_read_type), intent(in) :: postw90_oper
    type(pw90_spin_mod_type), intent(in) :: pw90_spin
    type(print_output_type), intent(in) :: print_output
    type(ws_region_type), intent(in) :: ws_region
    type(w90_comm_type), intent(in) :: comm
    type(wannier_data_type), intent(in) :: wannier_data
    type(wigner_seitz_type), intent(inout) :: wigner_seitz
    type(ws_distance_type), intent(inout) :: ws_distance
    type(timer_list_type), intent(inout) :: timer
    type(w90_error_type), allocatable, intent(out) :: error

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

    real(kind=dp), intent(out) :: TDF(:, :, :) ! (coordinate,Energy,spin)
    !! The TDF(i,EnIdx,spin) output array, where:
    !!        - i is an index from 1 to 6 giving the component of the symmetric tensor
    !!          $$ Sigma_{ij}(\eps) $$,
    !!          where 1=xx, 2=xy, 3=yy, 4=xz, 5=yz, 6=zz
    !!          as defined by the module constants XX, XY, ...
    !!          (in this way the mapping (i,j) -> i+((j-1)*j)/2 is satisfied for the packed storage of the
    !!          upper triangle [i<=j]).
    !!        - EnIdx is the index of the energies; the corresponding energy is given by
    !!          TDFEnergyArray(EndIdx) array (in eV).
    !!        - Spin may be only 1 if spin_decomp=.false. If it is instead true, 1 contains the total TDF,
    !!          2 the spin-up component and 3 the spin-up component
    real(kind=dp), intent(in) :: TDFEnergyArray(:)
    !! TDFEnergyArray The array with the energies for which the TDF is calculated, in eV
    ! Comments:
    ! issue warnings if going outside of the energy window
    ! check that we actually get hbar*velocity in eV*angstrom
    real(kind=dp), intent(in) :: eigval(:, :)
    real(kind=dp), intent(in) :: real_lattice(3, 3)
    real(kind=dp), intent(in) :: cell_volume
    real(kind=dp), intent(in) :: scissors_shift

    complex(kind=dp), intent(in) :: v_matrix(:, :, :), u_matrix(:, :, :)
    complex(kind=dp), allocatable, intent(inout) :: HH_R(:, :, :) !  <0n|r|Rm>
    complex(kind=dp), allocatable, intent(inout) :: SS_R(:, :, :, :) ! <0n|sigma_x,y,z|Rm>

    character(len=50), intent(in) :: seedname
    logical, intent(in) :: have_disentangled
    logical, intent(in) :: spin_decomp
    logical, intent(in) :: effective_model

    ! local variables
    real(kind=dp) :: recip_lattice(3, 3), volume
    real(kind=dp) :: kpt(3), orig_kpt(3)
    integer :: loop_tot, loop_x, loop_y, loop_z, ierr

    complex(kind=dp), allocatable :: HH(:, :)
    complex(kind=dp), allocatable :: delHH(:, :, :)
    complex(kind=dp), allocatable :: UU(:, :)
    real(kind=dp) :: del_eig(num_wann, 3)
    real(kind=dp) :: eig(num_wann), levelspacing_k(num_wann)

    real(kind=dp), allocatable :: DOS_EnergyArray(:)
    real(kind=dp), allocatable :: DOS_k(:, :), TDF_k(:, :, :)
    real(kind=dp), allocatable :: DOS_all(:, :)
    real(kind=dp) :: kweight
    integer :: ndim, DOS_NumPoints, i, j, k, EnIdx

    character(len=20) :: numfieldsstr
    integer :: boltzdos_unit, NumPtsRefined

    real(kind=dp), parameter :: SPACING_THRESHOLD = 1.e-3
    real(kind=dp) :: min_spacing, max_spacing

    integer :: my_node_id, num_nodes
    logical :: on_root = .false.

    my_node_id = mpirank(comm)
    num_nodes = mpisize(comm)
    if (my_node_id == 0) on_root = .true.

    if (print_output%iprint > 0 .and. (print_output%timing_level > 0)) call io_stopwatch_start('calcTDF', timer)
    if (print_output%iprint > 0) then
      if (pw90_boltzwann%calc_also_dos) then
        write (stdout, '(3X,A)') "Calculating Transport Distribution function (TDF) and DOS..."
      else
        write (stdout, '(3X,A)') "Calculating Transport Distribution function (TDF)..."
      end if
    end if

    ! I call once the routine to calculate the Hamiltonian in real-space <0n|H|Rm>

    call get_HH_R(dis_manifold, kpt_latt, print_output, wigner_seitz, HH_R, u_matrix, v_matrix, &
                  eigval, real_lattice, scissors_shift, num_bands, num_kpts, num_wann, &
                  num_valence_bands, effective_model, have_disentangled, seedname, ws_distance, ws_region, &
                  stdout, timer, error, comm)
    if (allocated(error)) return

    if (spin_decomp) then
      ndim = 3

      call get_SS_R(dis_manifold, kpt_latt, print_output, postw90_oper, SS_R, v_matrix, eigval, &
                    wigner_seitz, ws_distance, ws_region, num_bands, num_kpts, num_wann, &
                    have_disentangled, seedname, stdout, timer, error, comm)
      if (allocated(error)) return

    else
      ndim = 1
    end if

    ! Some initial checks
    if (size(TDF, 1) /= 6 .or. size(TDF, 2) /= size(TDFEnergyArray) .or. size(TDF, 3) /= ndim) then
      call set_error_input(error, 'Wrong size for the TDF array in calcTDF', comm)
      return
    end if

    ! I zero the TDF array before starting
    TDF = 0._dp

    allocate (TDF_k(6, size(TDFEnergyArray), ndim), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating TDF_k in calcTDF', comm)
      return
    end if

    allocate (HH(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating HH in calcTDF', comm)
      return
    end if
    allocate (delHH(num_wann, num_wann, 3), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating delHH in calcTDF', comm)
      return
    end if
    allocate (UU(num_wann, num_wann), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating UU in calcTDF', comm)
      return
    end if

    DOS_NumPoints = int(floor((pw90_boltzwann%dos_energy_max - pw90_boltzwann%dos_energy_min)/pw90_boltzwann%dos_energy_step)) + 1
    if (DOS_NumPoints .eq. 1) DOS_NumPoints = 2
    allocate (DOS_EnergyArray(DOS_NumPoints), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating DOS_EnergyArray in calcTDF', comm)
      return
    end if
    do i = 1, DOS_NumPoints
      DOS_EnergyArray(i) = pw90_boltzwann%dos_energy_min + real(i - 1, dp)*pw90_boltzwann%dos_energy_step
    end do

    allocate (DOS_k(size(DOS_EnergyArray), ndim), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating DOS_k in calcTDF', comm)
      return
    end if
    allocate (DOS_all(size(DOS_EnergyArray), ndim), stat=ierr)
    if (ierr /= 0) then
      call set_error_alloc(error, 'Error in allocating DOS_all in calcTDF', comm)
      return
    end if
    dos_all = 0.0_dp

    ! I open the output files
    if (pw90_boltzwann%calc_also_dos .and. on_root) then
      open (newunit=boltzdos_unit, file=trim(seedname)//'_boltzdos.dat')
    end if

    if (pw90_boltzwann%calc_also_dos .and. on_root .and. (print_output%iprint > 1)) then
      write (stdout, '(5X,A)') "Smearing for DOS: "
      if (pw90_boltzwann%dos_smearing%use_adaptive) then
        write (stdout, '(7X,A)') trim(w90_readwrite_get_smearing_type(pw90_boltzwann%dos_smearing%type_index))//", adaptive"
      else
        if (pw90_boltzwann%dos_smearing%fixed_width/(DOS_EnergyArray(2) - DOS_EnergyArray(1)) < &
            min_smearing_binwidth_ratio) then
          write (stdout, '(7X,A)') "Unsmeared (use smearing width larger than bin width to smear)"
        else
          write (stdout, '(7X,A,G18.10)') trim(w90_readwrite_get_smearing_type(pw90_boltzwann%dos_smearing%type_index))// &
            ", non-adaptive, width (eV) =", pw90_boltzwann%dos_smearing%fixed_width
        end if
      end if
    end if
    if (pw90_boltzwann%calc_also_dos .and. pw90_boltzwann%dos_smearing%use_adaptive .and. &
        (pw90_boltzwann%dos_smearing%fixed_width .ne. 0._dp) .and. on_root) then
      write (stdout, '(5X,A)') "*** WARNING! pw90_boltzwann_dos_smr_fixed_en_width ignored since you chose"
      write (stdout, '(5X,A)') "             an adaptive smearing."
    end if

    if (on_root .and. (print_output%iprint > 1)) then
      if (pw90_boltzwann%tdf_smearing%fixed_width/(TDFEnergyArray(2) - TDFEnergyArray(1)) &
          < min_smearing_binwidth_ratio) then
        write (stdout, '(5X,A)') "Smearing for TDF: "
        write (stdout, '(7X,A)') "Unsmeared (use smearing width larger than bin width to smear)"
      else
        write (stdout, '(5X,A)') "Smearing for TDF: "
        write (stdout, '(7X,A,G18.10)') &
          trim(w90_readwrite_get_smearing_type(pw90_boltzwann%tdf_smearing%type_index))//", non-adaptive, width (eV) =", &
          pw90_boltzwann%tdf_smearing%fixed_width
      end if
    end if

    if (on_root) then
      write (stdout, '(5X,A,I0,A,I0,A,I0)') "k-grid used for band interpolation in BoltzWann: ", &
        pw90_boltzwann%kmesh%mesh(1), 'x', pw90_boltzwann%kmesh%mesh(2), 'x', pw90_boltzwann%kmesh%mesh(3)
      write (stdout, '(5X,A,I1)') "Number of electrons per state: ", num_elec_per_state
      write (stdout, '(5X,A,G18.10)') "Relaxation time (fs): ", pw90_boltzwann%relax_time
      if (print_output%iprint > 1) then
        write (stdout, '(5X,A,G18.10)') "Energy step for TDF (eV): ", pw90_boltzwann%tdf_energy_step
      end if
    end if
    kweight = 1.0_dp/real(PRODUCT(pw90_boltzwann%kmesh%mesh), kind=dp)

    if (pw90_boltzwann%bandshift .and. on_root) then
      write (stdout, '(5X,A,I0,A,G18.10,A)') "Shifting energy bands with index >= ", pw90_boltzwann%bandshift_firstband, " by ", &
        pw90_boltzwann%bandshift_energyshift, " eV."
    end if

    call utility_recip_lattice_base(real_lattice, recip_lattice, volume)
    NumPtsRefined = 0
    min_spacing = 1.e10_dp ! very large initial value
    max_spacing = 0.e0_dp
    ! I loop over all kpoints
    do loop_tot = my_node_id, PRODUCT(pw90_boltzwann%kmesh%mesh) - 1, num_nodes

      ! I get the coordinates for the x,y,z components starting from a single loop variable
      ! (which is better for parallelization purposes)
      ! Important! This works only if loop_tot starts from ZERO and ends with
      !            PRODUCT(pw90_boltzwann_kmesh)-1, so be careful when parallelizing
      loop_x = loop_tot/(pw90_boltzwann%kmesh%mesh(2)*pw90_boltzwann%kmesh%mesh(3))
      loop_y = (loop_tot - loop_x*(pw90_boltzwann%kmesh%mesh(2)*pw90_boltzwann%kmesh%mesh(3)))/pw90_boltzwann%kmesh%mesh(3)
      loop_z = loop_tot - loop_x*(pw90_boltzwann%kmesh%mesh(2)*pw90_boltzwann%kmesh%mesh(3)) - loop_y*pw90_boltzwann%kmesh%mesh(3)

      ! kpt(i) is in in the [0,d-1]/d range, with d=pw90_boltzwann_kmesh(i)
      kpt(1) = (real(loop_x, dp)/real(pw90_boltzwann%kmesh%mesh(1), dp))
      kpt(2) = (real(loop_y, dp)/real(pw90_boltzwann%kmesh%mesh(2), dp))
      kpt(3) = (real(loop_z, dp)/real(pw90_boltzwann%kmesh%mesh(3), dp))

      ! Here I get the band energies and the velocities
      call wham_get_eig_deleig(dis_manifold, kpt_latt, pw90_band_deriv_degen, ws_region, print_output, wannier_data, &
                               ws_distance, wigner_seitz, delHH, HH, HH_R, u_matrix, UU, v_matrix, &
                               del_eig, eig, eigval, kpt, real_lattice, scissors_shift, mp_grid, &
                               num_bands, num_kpts, num_wann, num_valence_bands, effective_model, &
                               have_disentangled, seedname, stdout, timer, error, comm)
      if (allocated(error)) return

      call dos_get_levelspacing(del_eig, pw90_boltzwann%kmesh%mesh, levelspacing_k, num_wann, recip_lattice)

      ! Here I apply a scissor operator to the conduction bands, if required in the input
      if (pw90_boltzwann%bandshift) then
        eig(pw90_boltzwann%bandshift_firstband:) = eig(pw90_boltzwann%bandshift_firstband:) + pw90_boltzwann%bandshift_energyshift
      end if

      call TDF_kpt(pw90_boltzwann, ws_region, pw90_spin, wannier_data, ws_distance, wigner_seitz, &
                   HH_R, SS_R, del_eig, eig, TDFEnergyArray, kpt, real_lattice, TDF_k, mp_grid, &
                   num_wann, num_elec_per_state, spin_decomp, error, comm)
      if (allocated(error)) return

      ! As above, the sum of TDF_k * kweight amounts to calculate
      ! spin_degeneracy * V_cell/(2*pi)^3 * \int_BZ d^3k
      ! so that we divide by the cell_volume (in Angstrom^3) to have
      ! the correct integral
      TDF = TDF + TDF_k*kweight/cell_volume

      ! DOS part !

      if (pw90_boltzwann%calc_also_dos) then
        if (pw90_boltzwann%dos_smearing%use_adaptive) then

          ! This may happen if at least one band has zero derivative (along all three directions)
          ! Then I substitute this point with its 8 neighbors (+/- 1/4 of the spacing with the next point on the grid
          ! on each of the three directions)
          min_spacing = min(min_spacing, minval(abs(levelspacing_k)))
          max_spacing = max(max_spacing, maxval(abs(levelspacing_k)))
          if (any(abs(levelspacing_k) < SPACING_THRESHOLD)) then
            orig_kpt = kpt
            NumPtsRefined = NumPtsRefined + 1
            do i = -1, 1, 2
              do j = -1, 1, 2
                do k = -1, 1, 2
                  kpt = orig_kpt + &
                        (/real(i, kind=dp)/real(pw90_boltzwann%kmesh%mesh(1), dp)/4._dp, &
                          real(j, kind=dp)/real(pw90_boltzwann%kmesh%mesh(2), dp)/4._dp, &
                          real(k, kind=dp)/real(pw90_boltzwann%kmesh%mesh(3), dp)/4._dp/)
                  call wham_get_eig_deleig(dis_manifold, kpt_latt, pw90_band_deriv_degen, &
                                           ws_region, print_output, wannier_data, ws_distance, &
                                           wigner_seitz, delHH, HH, HH_R, u_matrix, UU, v_matrix, &
                                           del_eig, eig, eigval, kpt, real_lattice, &
                                           scissors_shift, mp_grid, num_bands, num_kpts, num_wann, &
                                           num_valence_bands, effective_model, have_disentangled, &
                                           seedname, stdout, timer, error, comm)
                  if (allocated(error)) return

                  call dos_get_levelspacing(del_eig, pw90_boltzwann%kmesh%mesh, levelspacing_k, &
                                            num_wann, recip_lattice)
                  call dos_get_k(num_elec_per_state, ws_region, kpt, DOS_EnergyArray, eig, dos_k, &
                                 num_wann, wannier_data, real_lattice, mp_grid, pw90_dos, &
                                 spin_decomp, pw90_spin, ws_distance, wigner_seitz, HH_R, SS_R, &
                                 pw90_boltzwann%dos_smearing, error, comm, &
                                 levelspacing_k=levelspacing_k)
                  if (allocated(error)) return

                  ! I divide by 8 because I'm substituting a point with its 8 neighbors
                  dos_all = dos_all + dos_k*kweight/8.
                end do
              end do
            end do
          else
            call dos_get_k(num_elec_per_state, ws_region, kpt, DOS_EnergyArray, eig, dos_k, &
                           num_wann, wannier_data, real_lattice, mp_grid, pw90_dos, spin_decomp, &
                           pw90_spin, ws_distance, wigner_seitz, HH_R, SS_R, &
                           pw90_boltzwann%dos_smearing, error, comm, levelspacing_k=levelspacing_k)
            if (allocated(error)) return

            dos_all = dos_all + dos_k*kweight
          end if
        else
          call dos_get_k(num_elec_per_state, ws_region, kpt, DOS_EnergyArray, eig, dos_k, &
                         num_wann, wannier_data, real_lattice, mp_grid, pw90_dos, spin_decomp, &
                         pw90_spin, ws_distance, wigner_seitz, HH_R, SS_R, &
                         pw90_boltzwann%dos_smearing, error, comm)
          if (allocated(error)) return

          ! This sum multiplied by kweight amounts to calculate
          ! spin_degeneracy * V_cell/(2*pi)^3 * \int_BZ d^3k
          ! So that the DOS will be in units of 1/eV, normalized so that
          ! \int_{-\infty}^{\infty} DOS(E) dE = Num.Electrons
          dos_all = dos_all + dos_k*kweight
        end if
      end if

    end do

    ! I sum the results of the calculation for the DOS on the root node only
    ! (I have to print the results only)
    if (pw90_boltzwann%calc_also_dos) then
      call comms_reduce(DOS_all(1, 1), size(DOS_all), 'SUM', error, comm)
      if (allocated(error)) return
      call comms_reduce(NumPtsRefined, 1, 'SUM', error, comm)
      if (allocated(error)) return
      call comms_reduce(min_spacing, 1, 'MIN', error, comm)
      if (allocated(error)) return
      call comms_reduce(max_spacing, 1, 'MAX', error, comm)
      if (allocated(error)) return
    end if
    ! I sum the results of the calculation on all nodes, and I store them on all
    ! nodes (because for the following, each node will do a different calculation,
    ! each of which will require the whole knowledge of the TDF array)
    call comms_allreduce(TDF(1, 1, 1), size(TDF), 'SUM', error, comm)
    if (allocated(error)) return

    if (pw90_boltzwann%calc_also_dos .and. on_root) then
      write (boltzdos_unit, '(A)') "# Written by the BoltzWann module of the Wannier90 code."
      write (boltzdos_unit, '(A)') "# The first column."
      if (pw90_boltzwann%dos_smearing%use_adaptive) then
        write (boltzdos_unit, '(A)') '# The second column is the adaptively-smeared DOS'
        write (boltzdos_unit, '(A)') '# (see Yates et al., PRB 75, 195121 (2007)'
        if (spin_decomp) then
          write (boltzdos_unit, '(A)') '# The third column is the spin-up projection of the DOS'
          write (boltzdos_unit, '(A)') '# The fourth column is the spin-down projection of the DOS'
        end if
        write (boltzdos_unit, '(A,1X,G14.6)') '# Smearing coefficient: ', pw90_boltzwann%dos_smearing%adaptive_prefactor
        write (boltzdos_unit, '(A,I0,A,I0)') '# Number of points refined: ', NumPtsRefined, &
          ' out of ', product(pw90_boltzwann%kmesh%mesh)
        write (boltzdos_unit, '(A,G18.10,A,G18.10,A)') '# (Min spacing: ', min_spacing, &
          ', max spacing: ', max_spacing, ')'
      else
        if (pw90_boltzwann%dos_smearing%fixed_width/(DOS_EnergyArray(2) - DOS_EnergyArray(1)) < min_smearing_binwidth_ratio) then
          write (boltzdos_unit, '(A)') '# The second column is the unsmeared DOS.'
        else
          write (boltzdos_unit, '(A,G14.6,A)') '# The second column is the DOS for a fixed smearing of ', &
            pw90_boltzwann%dos_smearing%fixed_width, ' eV.'
        end if
      end if
      write (boltzdos_unit, '(A,1X,G14.6)') '# Cell volume (ang^3): ', cell_volume

      write (boltzdos_unit, '(A)') '# Energy(eV) DOS [DOS DOS ...]'

      ! I save a string with the number of fields to print
      write (numfieldsstr, '(I0)') 1 + ndim
      do EnIdx = 1, size(DOS_EnergyArray)
        write (boltzdos_unit, '(1X,'//trim(numfieldsstr)//'G18.10)') &
          DOS_EnergyArray(EnIdx), dos_all(EnIdx, :)
      end do
    end if

    if (on_root .and. (print_output%timing_level > 0)) call io_stopwatch_stop('calcTDF', timer)
    if (on_root) then
      if (pw90_boltzwann%calc_also_dos) then
        write (stdout, '(3X,A)') "TDF and DOS calculated."
      else
        write (stdout, '(3X,A)') "TDF calculated."
      end if
    end if
    if (on_root) write (stdout, *)

    if (on_root .and. pw90_boltzwann%calc_also_dos) then
      close (boltzdos_unit)
      if (print_output%iprint > 1) write (stdout, '(3X,A)') "DOS written on the "//trim(seedname)//"_boltzdos.dat file."
    end if

    deallocate (HH, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating HH in calcTDF', comm)
      return
    end if
    deallocate (delHH, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating delHH in calcTDF', comm)
      return
    end if
    deallocate (UU, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating UU in calcTDF', comm)
      return
    end if
    deallocate (DOS_EnergyArray, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating DOS_EnergyArray in calcTDF', comm)
      return
    end if
    deallocate (DOS_k, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating DOS_k in calcTDF', comm)
      return
    end if
    deallocate (DOS_all, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating DOS_all in calcTDF', comm)
      return
    end if
    deallocate (TDF_k, stat=ierr)
    if (ierr /= 0) then
      call set_error_dealloc(error, 'Error in deallocating TDF_k in calcTDF', comm)
      return
    end if

  end subroutine calcTDFandDOS

  !================================================!
  function MinusFermiDerivative(E, mu, KT)
    !================================================!
    !> This function calculates -dn(E)/dE, where n(E) is the Fermi distribution function.
    !>
    !> \param E  Energy at which we want to calculate -dn(E)/dE, in 1/eV
    !> \param mu Chemical potential in eV
    !> \param KT k_Boltzmann * Temperature, in eV
    !================================================!

    real(kind=dp), intent(in) :: E
    real(kind=dp), intent(in) :: mu
    real(kind=dp), intent(in) :: KT
    real(kind=dp) :: MinusFermiDerivative

    ! I do not put stopwatches here because it would slow down the calculation by orders of magnitude

    ! MaxExp is the maximum value to be used for the exp function.
    ! The function is taken to be zero for x>MaxExp. This value is chosen so that
    ! the function is truncated when its value is smaller than about 1.e-16
    real(kind=dp), parameter :: MaxExp = 36._dp
    real(kind=dp) :: MyExp

    MyExp = (E - mu)/KT
    if (abs(MyExp) > MaxExp) then
      MinusFermiDerivative = 0._dp
    else
      MinusFermiDerivative = 1._dp/KT*exp(MyExp)/((exp(MyExp) + 1._dp)**2)
    end if

  end function MinusFermiDerivative

  !================================================!
  subroutine TDF_kpt(pw90_boltzwann, ws_region, pw90_spin, wannier_data, ws_distance, &
                     wigner_seitz, HH_R, SS_R, deleig_k, eig_k, EnergyArray, kpt, real_lattice, &
                     TDF_k, mp_grid, num_wann, num_elec_per_state, spin_decomp, error, comm)
    !================================================!
    !! This subroutine calculates the contribution to the TDF of a single k point
    !!
    !!  This routine does not use the adaptive smearing; in fact, for non-zero temperatures
    !!       one often doesn't even need to smear. It simply uses a standard smearing as defined by
    !!       the variables pw90_boltzwann_TDF_smr_fixed_en_width and pw90_boltzwann_TDF_smr_index
    !!
    !! still to do: adapt spin_get_nk to read in input the UU rotation matrix
    !!
    !! This routine simply provides the dos contribution of a given
    !!       point. This must be externally summed after proper weighting.
    !!       The weight factor (for a full BZ sampling with N^3 points) is 1/N^3/cell_volume
    !!       if we want to calculate 1/(2*pi)^3 * \int_{BZ} d^3 k
    !! The only factor that is included INSIDE this routine is the spin degeneracy
    !!       factor (=2 if spinors is .false., =1 if spinors is .true.)
    !! The EnergyArray is assumed to be evenly spaced (and the energy spacing
    !!       is taken from EnergyArray(2)-EnergyArray(1))
    !! The routine is assuming that EnergyArray has at least two elements.
    !!  The meaning of the three indices of the TDF_k array is different with respect to
    !!       those of the dos_k array returned by the dos_get_k routine
    !! The TDF_k array must have dimensions 6 * size(EnergyArray) * ndim, where
    !!       ndim=1 if spin_decomp==false, or ndim=3 if spin_decomp==true. This is not checked.
    !!
    !================================================!

    use w90_constants, only: dp, smearing_cutoff, min_smearing_binwidth_ratio
    use w90_utility, only: utility_w0gauss
    use w90_types, only: print_output_type, wannier_data_type, ws_region_type, ws_distance_type
    use w90_postw90_types, only: pw90_boltzwann_type, pw90_spin_mod_type, wigner_seitz_type
    use w90_spin, only: spin_get_nk
    use w90_utility, only: utility_w0gauss
    use w90_comms, only: w90_comm_type

    implicit none

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

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

    real(kind=dp), intent(in) :: kpt(3)
    !! the three coordinates of the k point vector whose DOS contribution we
    !! want to calculate (in relative coordinates)
    real(kind=dp), intent(in) :: EnergyArray(:)
    !! array with the energy grid on which to calculate the DOS (in eV)
    !! It must have at least two elements
    real(kind=dp), intent(in) :: eig_k(:)
    !! array with the eigenvalues at the given k point (in eV)
    real(kind=dp), intent(in) :: deleig_k(:, :)
    !! array with the band derivatives at the given k point
    !! (in eV * angstrom / (2pi) as internally given by the code)
    !! already corrected in case of degeneracies, as returned by the
    !! wham_get_deleig_a routine
    real(kind=dp), intent(out) :: TDF_k(:, :, :)
    !! TDF_k array in which the contribution is stored. Three dimensions:
    !! TDF_k(ij, energyidx, spinidx), where:
    !! - ij indexes the components of the TDF (symmetric) tensor (1=XX, 2=XY, ...);
    !!   see the global constants defined in the module
    !! - energyidx is the index of the energies, corresponding to the one
    !!   of the EnergyArray array;
    !!  - spinidx=1 contains the total dos; if if spin_decomp==.true., then
    !!  spinidx=2 and spinidx=3 contain the spin-up and spin-down contributions to the DOS
    real(kind=dp), intent(in) :: real_lattice(3, 3)

    complex(kind=dp), allocatable, intent(inout) :: HH_R(:, :, :) !  <0n|r|Rm>
    complex(kind=dp), allocatable, intent(inout) :: SS_R(:, :, :, :) ! <0n|sigma_x,y,z|Rm>

    logical, intent(in) :: spin_decomp
    integer, intent(in) :: num_elec_per_state

    ! local variables
    real(kind=dp) :: smear, arg ! Adaptive smearing
    real(kind=dp) :: rdum, spn_nk(num_wann), alpha_sq, beta_sq
    real(kind=dp) :: binwidth, r_num_elec_per_state
    integer :: BandIdx, loop_f, min_f, max_f
    logical :: DoSmearing

    r_num_elec_per_state = real(num_elec_per_state, kind=dp)

    ! Get spin projections for every band
    !
    if (spin_decomp) then
      call spin_get_nk(ws_region, pw90_spin, wannier_data, ws_distance, wigner_seitz, HH_R, SS_R, &
                       kpt, real_lattice, spn_nk, mp_grid, num_wann, error, comm)
      if (allocated(error)) return
    end if

    binwidth = EnergyArray(2) - EnergyArray(1)

    TDF_k = 0.0_dp
    do BandIdx = 1, num_wann
      if (spin_decomp) then
        ! Contribution to spin-up DOS of Bloch spinor with component
        ! (alpha,beta) with respect to the chosen quantization axis
        alpha_sq = (1.0_dp + spn_nk(BandIdx))/2.0_dp ! |alpha|^2
        ! Contribution to spin-down DOS
        beta_sq = 1.0_dp - alpha_sq ! |beta|^2 = 1 - |alpha|^2
      end if

      ! Do not use an adaptive smearing here, it would require the knowledge of second derivatives
      ! And typically, when working at not too small temperatures, smearing is not needed

      ! Faster optimization: I precalculate the indices
      ! Value of the smearing in eV; default = 0 eV, i.e. no smearing
      smear = pw90_boltzwann%tdf_smearing%fixed_width
      if (smear/binwidth < min_smearing_binwidth_ratio) then
        min_f = max(nint((eig_k(BandIdx) - EnergyArray(1))/ &
                         (EnergyArray(size(EnergyArray)) - EnergyArray(1)) &
                         *real(size(EnergyArray) - 1, kind=dp)) + 1, 1)
        max_f = min(nint((eig_k(BandIdx) - EnergyArray(1))/ &
                         (EnergyArray(size(EnergyArray)) - EnergyArray(1)) &
                         *real(size(EnergyArray) - 1, kind=dp)) + 1, size(EnergyArray))
        DoSmearing = .false.
      else
        min_f = max(nint((eig_k(BandIdx) - smearing_cutoff*smear - EnergyArray(1))/ &
                         (EnergyArray(size(EnergyArray)) - EnergyArray(1)) &
                         *real(size(EnergyArray) - 1, kind=dp)) + 1, 1)
        max_f = min(nint((eig_k(BandIdx) + smearing_cutoff*smear - EnergyArray(1))/ &
                         (EnergyArray(size(EnergyArray)) - EnergyArray(1)) &
                         *real(size(EnergyArray) - 1, kind=dp)) + 1, size(EnergyArray))
        DoSmearing = .true.
      end if

      do loop_f = min_f, max_f
        if (DoSmearing) then
          arg = (EnergyArray(loop_f) - eig_k(BandIdx))/smear
          rdum = utility_w0gauss(arg, pw90_boltzwann%tdf_smearing%type_index, error, comm)/smear
          if (allocated(error)) return
        else
          rdum = 1._dp/(EnergyArray(2) - EnergyArray(1))
        end if
        !
        ! Contribution to total DOS
        !
        TDF_k(XX, loop_f, 1) = TDF_k(XX, loop_f, 1) + rdum* &
                               r_num_elec_per_state*deleig_k(BandIdx, 1)*deleig_k(BandIdx, 1)
        TDF_k(XY, loop_f, 1) = TDF_k(XY, loop_f, 1) + rdum* &
                               r_num_elec_per_state*deleig_k(BandIdx, 1)*deleig_k(BandIdx, 2)
        TDF_k(YY, loop_f, 1) = TDF_k(YY, loop_f, 1) + rdum* &
                               r_num_elec_per_state*deleig_k(BandIdx, 2)*deleig_k(BandIdx, 2)
        TDF_k(XZ, loop_f, 1) = TDF_k(XZ, loop_f, 1) + rdum* &
                               r_num_elec_per_state*deleig_k(BandIdx, 1)*deleig_k(BandIdx, 3)
        TDF_k(YZ, loop_f, 1) = TDF_k(YZ, loop_f, 1) + rdum* &
                               r_num_elec_per_state*deleig_k(BandIdx, 2)*deleig_k(BandIdx, 3)
        TDF_k(ZZ, loop_f, 1) = TDF_k(ZZ, loop_f, 1) + rdum* &
                               r_num_elec_per_state*deleig_k(BandIdx, 3)*deleig_k(BandIdx, 3)

        ! I don't put num_elec_per_state here below: if we are calculating the spin decomposition,
        ! we should be doing a calcultation with spin-orbit, and thus num_elec_per_state=1!
        if (spin_decomp) then
          ! Spin-up contribution
          TDF_k(XX, loop_f, 2) = TDF_k(XX, loop_f, 2) + rdum* &
                                 alpha_sq*deleig_k(BandIdx, 1)*deleig_k(BandIdx, 1)
          TDF_k(XY, loop_f, 2) = TDF_k(XY, loop_f, 2) + rdum* &
                                 alpha_sq*deleig_k(BandIdx, 1)*deleig_k(BandIdx, 2)
          TDF_k(YY, loop_f, 2) = TDF_k(YY, loop_f, 2) + rdum* &
                                 alpha_sq*deleig_k(BandIdx, 2)*deleig_k(BandIdx, 2)
          TDF_k(XZ, loop_f, 2) = TDF_k(XZ, loop_f, 2) + rdum* &
                                 alpha_sq*deleig_k(BandIdx, 1)*deleig_k(BandIdx, 3)
          TDF_k(YZ, loop_f, 2) = TDF_k(YZ, loop_f, 2) + rdum* &
                                 alpha_sq*deleig_k(BandIdx, 2)*deleig_k(BandIdx, 3)
          TDF_k(ZZ, loop_f, 2) = TDF_k(ZZ, loop_f, 2) + rdum* &
                                 alpha_sq*deleig_k(BandIdx, 3)*deleig_k(BandIdx, 3)
          ! Spin-down contribution
          TDF_k(XX, loop_f, 3) = TDF_k(XX, loop_f, 3) + rdum* &
                                 beta_sq*deleig_k(BandIdx, 1)*deleig_k(BandIdx, 1)
          TDF_k(XY, loop_f, 3) = TDF_k(XY, loop_f, 3) + rdum* &
                                 beta_sq*deleig_k(BandIdx, 1)*deleig_k(BandIdx, 2)
          TDF_k(YY, loop_f, 3) = TDF_k(YY, loop_f, 3) + rdum* &
                                 beta_sq*deleig_k(BandIdx, 2)*deleig_k(BandIdx, 2)
          TDF_k(XZ, loop_f, 3) = TDF_k(XZ, loop_f, 3) + rdum* &
                                 beta_sq*deleig_k(BandIdx, 1)*deleig_k(BandIdx, 3)
          TDF_k(YZ, loop_f, 3) = TDF_k(YZ, loop_f, 3) + rdum* &
                                 beta_sq*deleig_k(BandIdx, 2)*deleig_k(BandIdx, 3)
          TDF_k(ZZ, loop_f, 3) = TDF_k(ZZ, loop_f, 3) + rdum* &
                                 beta_sq*deleig_k(BandIdx, 3)*deleig_k(BandIdx, 3)
        end if
      end do
    end do !loop over bands

    ! I multiply it here, since I am assuming a constant relaxation time, independent of the band index
    ! (actually, it is also independent of k)
    TDF_k = TDF_k*pw90_boltzwann%relax_time

  end subroutine TDF_kpt

end module w90_boltzwann