!-*- 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_gyrotropic: various gyrotropic effects                !
!                                                            !
!------------------------------------------------------------!

module w90_gyrotropic

  !! This module computes various "gyrotropic" effects
  !! as described in :
  !!    TAS17 =  arXiv:1710.03204 (2017) Gyrotropic effects in trigonal tellurium studied from first principles
  !!                   S.S.Tsirkin, P. Aguado Puente, I. Souza

  use w90_constants, only: dp
  use w90_berry, only: berry_get_imf_klist, berry_get_imfgh_klist
  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 :: gyrotropic_main

  ! Pseudovector <--> Antisymmetric tensor
  !
  ! x <--> (y,z)
  ! y <--> (z,x)
  ! z <--> (x,y)
  !
  integer, dimension(3), parameter :: alpha_A = (/2, 3, 1/)
  integer, dimension(3), parameter ::  beta_A = (/3, 1, 2/)

  ! Independent components of a symmetric tensor
  !
  ! 1 <--> xx
  ! 2 <--> yy
  ! 3 <--> zz
  ! 4 <--> xy
  ! 5 <--> xz
  ! 6 <--> yz
  !
  !integer, dimension(6), parameter :: alpha_S = (/1, 2, 3, 1, 1, 2/)
  !integer, dimension(6), parameter ::  beta_S = (/1, 2, 3, 2, 3, 3/)

contains

  !================================================!
  !                   PUBLIC PROCEDURES
  !================================================!

  subroutine gyrotropic_main(pw90_berry, dis_manifold, fermi_energy_list, pw90_gyrotropic, &
                             kmesh_info, kpt_latt, physics, pw90_oper_read, pw90_band_deriv_degen, &
                             ws_region, w90_system, print_output, wannier_data, wigner_seitz, &
                             ws_distance, AA_R, BB_R, CC_R, HH_R, SS_R, u_matrix, v_matrix, &
                             eigval, real_lattice, scissors_shift, mp_grid, num_bands, num_kpts, &
                             num_wann, effective_model, have_disentangled, seedname, stdout, &
                             timer, error, comm)
    !================================================!
    !
    !! Computes the following quantities:
    !!   (i) D tensor
    !!  (ii) K tensor
    !! (iii) C tensor
    !!  (iv) current-induced optical activity
    !!   (v) natural optical activity
    !
    !================================================!

    use w90_comms, only: comms_reduce, w90_comm_type, mpirank, mpisize
    use w90_constants, only: dp, twopi, pw90_physical_constants_type
    use w90_get_oper, only: get_HH_R, get_AA_R_effective, get_AA_R, get_BB_R, get_CC_R, get_SS_R
    use w90_io, only: io_stopwatch_start, io_stopwatch_stop
    use w90_postw90_types, only: pw90_gyrotropic_type, pw90_berry_mod_type, pw90_oper_read_type, &
                                 pw90_band_deriv_degen_type, wigner_seitz_type
    use w90_types, only: dis_manifold_type, print_output_type, timer_list_type, &
                         kmesh_info_type, wannier_data_type, ws_region_type, w90_system_type, ws_distance_type
    use w90_utility, only: utility_det3

    implicit none

    ! arguments
    type(pw90_berry_mod_type), intent(in) :: pw90_berry
    type(dis_manifold_type), intent(in) :: dis_manifold
    type(pw90_gyrotropic_type), intent(in) :: pw90_gyrotropic
    type(kmesh_info_type), intent(in) :: kmesh_info
    type(pw90_band_deriv_degen_type), intent(in) :: pw90_band_deriv_degen
    type(pw90_oper_read_type), intent(in) :: pw90_oper_read
    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) :: AA_R(:, :, :, :)
    complex(kind=dp), allocatable, intent(inout) :: BB_R(:, :, :, :)
    complex(kind=dp), allocatable, intent(inout) :: CC_R(:, :, :, :, :)
    complex(kind=dp), allocatable, intent(inout) :: HH_R(:, :, :)
    complex(kind=dp), allocatable, intent(inout) :: SS_R(:, :, :, :)
    complex(kind=dp), intent(in) :: u_matrix(:, :, :), v_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), allocatable, intent(in) :: fermi_energy_list(:)
    real(kind=dp), intent(in) :: kpt_latt(:, :)

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

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

    ! local variables
    real(kind=dp), allocatable    :: gyro_K_spn(:, :, :)
    real(kind=dp), allocatable    :: gyro_DOS(:)
    real(kind=dp), allocatable    :: gyro_K_orb(:, :, :)
    real(kind=dp), allocatable    :: gyro_C(:, :, :)
    real(kind=dp), allocatable    :: gyro_D(:, :, :)
    real(kind=dp), allocatable    :: gyro_Dw(:, :, :, :)
    real(kind=dp), allocatable    :: gyro_NOA_spn(:, :, :, :)
    real(kind=dp), allocatable    :: gyro_NOA_orb(:, :, :, :)

    character(len=30) :: f_out_name_tmp
    character(len=30) :: units_tmp
    character(len=120) :: comment_tmp

    real(kind=dp) :: cell_volume
    real(kind=dp)     :: kweight, kpt(3), &
                         db1, db2, db3, fac
    integer           :: loop_x, loop_y, loop_z, loop_xyz
    integer           :: fermi_n
    logical           :: eval_K, eval_C, eval_D, eval_Dw, eval_NOA, eval_spn, eval_DOS

    integer :: my_node_id, num_nodes
    my_node_id = mpirank(comm)
    num_nodes = mpisize(comm)

    if (.not. allocated(fermi_energy_list)) then
      call set_error_input(error, 'Must specify one or more Fermi levels when gyrotropic=true', comm)
      return
    end if

    if (print_output%timing_level > 1 .and. print_output%iprint > 0) &
      call io_stopwatch_start('gyrotropic: prelims', timer)

    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))

    ! Mesh spacing in reduced coordinates
    db1 = 1.0_dp/real(pw90_gyrotropic%kmesh%mesh(1), dp)
    db2 = 1.0_dp/real(pw90_gyrotropic%kmesh%mesh(2), dp)
    db3 = 1.0_dp/real(pw90_gyrotropic%kmesh%mesh(3), dp)

    eval_K = .false.
    eval_C = .false.
    eval_D = .false.
    eval_Dw = .false.
    eval_spn = .false.
    eval_NOA = .false.
    eval_DOS = .false.

    if (index(pw90_gyrotropic%task, '-k') > 0) eval_K = .true.
    if (index(pw90_gyrotropic%task, '-c') > 0) eval_C = .true.
    if (index(pw90_gyrotropic%task, '-d0') > 0) eval_D = .true.
    if (index(pw90_gyrotropic%task, '-dw') > 0) eval_Dw = .true.
    if (index(pw90_gyrotropic%task, '-spin') > 0) eval_spn = .true.
    if (index(pw90_gyrotropic%task, '-noa') > 0) eval_NOA = .true.
    if (index(pw90_gyrotropic%task, '-dos') > 0) eval_DOS = .true.
    if (index(pw90_gyrotropic%task, 'all') > 0) then
      eval_K = .true.
      eval_C = .true.
      eval_D = .true.
      eval_Dw = .true.
      if (w90_system%spinors) eval_spn = .true.
      eval_NOA = .true.
      eval_DOS = .true.
    end if

    if (.not. (eval_K .or. eval_noa)) eval_spn = .false.

    if ((.not. w90_system%spinors) .and. eval_spn) then
      call set_error_input(error, "spin contribution requested for gyrotropic, but the wavefunctions are not spinors", comm)
      return
    end if

    ! Wannier matrix elements, allocations and initializations

    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, &
                  w90_system%num_valence_bands, effective_model, have_disentangled, seedname, &
                  ws_distance, ws_region, stdout, timer, error, comm)
    if (allocated(error)) return

    if (eval_D .or. eval_Dw .or. eval_K .or. eval_NOA) then

      if (effective_model) then
        call get_AA_R_effective(print_output, AA_R, HH_R, wigner_seitz%nrpts, num_wann, seedname, &
                                stdout, timer, error, comm)
      else
        call get_AA_R(pw90_berry, dis_manifold, kmesh_info, kpt_latt, print_output, wannier_data, AA_R, &
                      v_matrix, eigval, wigner_seitz, ws_distance, ws_region, num_bands, num_kpts, &
                      num_wann, have_disentangled, seedname, stdout, timer, error, comm)
      end if
      if (allocated(error)) return

    end if

    if (eval_spn) then

      call get_SS_R(dis_manifold, kpt_latt, print_output, pw90_oper_read, 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

    end if

    ! not allocated was tested at start of routine
    fermi_n = size(fermi_energy_list)
    if (eval_K) then
      call get_BB_R(pw90_berry, dis_manifold, kmesh_info, kpt_latt, print_output, HH_R, BB_R, v_matrix, &
                    eigval, scissors_shift, wigner_seitz, ws_distance, ws_region, num_bands, num_kpts, &
                    num_wann, have_disentangled, seedname, stdout, timer, error, comm)
      if (allocated(error)) return

      call get_CC_R(pw90_berry, dis_manifold, kmesh_info, kpt_latt, print_output, pw90_oper_read, &
                    HH_R, BB_R, CC_R, v_matrix, eigval, scissors_shift, wigner_seitz, ws_distance, &
                    ws_region, num_bands, num_kpts, num_wann, have_disentangled, seedname, stdout, &
                    timer, error, comm)
      if (allocated(error)) return

      allocate (gyro_K_orb(3, 3, fermi_n))
      gyro_K_orb = 0.0_dp
      if (eval_spn) then
        allocate (gyro_K_spn(3, 3, fermi_n))
        gyro_K_spn = 0.0_dp
      end if
    end if

    if (eval_D) then
      allocate (gyro_D(3, 3, fermi_n))
      gyro_D = 0.0_dp
    end if

    if (eval_DOS) then
      allocate (gyro_DOS(fermi_n))
      gyro_DOS = 0.0_dp
    end if

    if (eval_C) then
      allocate (gyro_C(3, 3, fermi_n))
      gyro_C = 0.0_dp
    end if

    if (eval_Dw) then
      allocate (gyro_Dw(3, 3, fermi_n, pw90_gyrotropic%nfreq))
      gyro_Dw = 0.0_dp
    end if

    if (eval_NOA) then
      allocate (gyro_NOA_orb(3, 3, fermi_n, pw90_gyrotropic%nfreq))
      gyro_NOA_orb = 0.0_dp
      if (eval_spn) then
        allocate (gyro_NOA_spn(3, 3, fermi_n, pw90_gyrotropic%nfreq))
        gyro_NOA_spn = 0.0_dp
      end if
    end if

    if (print_output%iprint > 0) then
      flush (stdout)
      write (stdout, '(/,/,1x,a)') 'Properties calculated in module  g y r o t r o p i c'
      write (stdout, '(1x,a)') '------------------------------------------'

      if (eval_D) write (stdout, '(/,3x,a)') '* D-tensor  --- Eq.2 of TAS17 '

      if (eval_dos) write (stdout, '(/,3x,a)') '* density of states '

      if (eval_K) then
        write (stdout, '(/,3x,a)') '* K-tensor  --- Eq.3 of TAS17 '
        if (eval_spn) then
          write (stdout, '(3x,a)') '    * including spin component '
        else
          write (stdout, '(3x,a)') '    * excluding spin component '
        end if
      end if

      if (eval_Dw) write (stdout, '(/,3x,a)') '* Dw-tensor  --- Eq.12 of TAS17 '

      if (eval_C) write (stdout, '(/,3x,a)') '* C-tensor  --- Eq.B6 of TAS17 '

      if (eval_NOA) then
        write (stdout, '(/,3x,a)') '* gamma-tensor of NOA --- Eq.C12 of TAS17 '
        if (eval_spn) then
          write (stdout, '(3x,a)') '    * including spin component '
        else
          write (stdout, '(3x,a)') '    * excluding spin component '
        end if
      end if

      if (pw90_berry%transl_inv) then
        if (eval_K) then
          call set_error_input(error, 'transl_inv=T disabled for K-tensor', comm)
          return
        end if
        write (stdout, '(/,1x,a)') &
          'Using a translationally-invariant discretization for the'
        write (stdout, '(1x,a)') &
          'band-diagonal Wannier matrix elements of r, etc.'
      end if

      if (print_output%timing_level > 1) then
        call io_stopwatch_stop('gyrotropic: prelims', timer)
        call io_stopwatch_start('gyrotropic: k-interpolation', timer)
      end if

      write (stdout, '(1x,a20,3(i0,1x))') 'Interpolation grid: ', pw90_gyrotropic%kmesh%mesh(1:3)

      flush (stdout)

    end if ! print_output%iprint >0, aka "on_root"

    ! Do not read 'kpoint.dat'. Loop over a regular grid in the full BZ

    kweight = db1*db2*db3*utility_det3(pw90_gyrotropic%box)

    do loop_xyz = my_node_id, PRODUCT(pw90_gyrotropic%kmesh%mesh) - 1, num_nodes
      loop_x = loop_xyz/(pw90_gyrotropic%kmesh%mesh(2)*pw90_gyrotropic%kmesh%mesh(3))
      loop_y = (loop_xyz - loop_x*(pw90_gyrotropic%kmesh%mesh(2) &
                                   *pw90_gyrotropic%kmesh%mesh(3)))/pw90_gyrotropic%kmesh%mesh(3)
      loop_z = loop_xyz - loop_x*(pw90_gyrotropic%kmesh%mesh(2)*pw90_gyrotropic%kmesh%mesh(3)) &
               - loop_y*pw90_gyrotropic%kmesh%mesh(3)
      kpt(1) = loop_x*db1
      kpt(2) = loop_y*db2
      kpt(3) = loop_z*db3
      kpt(:) = pw90_gyrotropic%box_corner(:) + matmul(kpt, pw90_gyrotropic%box)

      call gyrotropic_get_k_list(ws_region, w90_system%num_valence_bands, have_disentangled, kpt, &
                                 kweight, gyro_K_spn, gyro_K_orb, gyro_D, gyro_Dw, gyro_C, &
                                 gyro_DOS, gyro_NOA_orb, gyro_NOA_spn, eval_K, eval_D, eval_Dw, &
                                 eval_NOA, eval_spn, eval_C, eval_dos, num_wann, print_output, &
                                 fermi_energy_list, wannier_data, eigval, real_lattice, mp_grid, &
                                 num_bands, num_kpts, u_matrix, v_matrix, dis_manifold, kpt_latt, &
                                 pw90_gyrotropic, scissors_shift, effective_model, &
                                 pw90_band_deriv_degen, ws_distance, wigner_seitz, stdout, &
                                 seedname, timer, error, comm, HH_R, AA_R, BB_R, CC_R, SS_R)
      if (allocated(error)) return

    end do !loop_xyz

    ! Collect contributions from all nodes
    if (eval_K) then
      call comms_reduce(gyro_K_orb(1, 1, 1), 3*3*fermi_n, 'SUM', error, comm)
      if (allocated(error)) return
      if (eval_spn) then
        call comms_reduce(gyro_K_spn(1, 1, 1), 3*3*fermi_n, 'SUM', error, comm)
        if (allocated(error)) return
      end if
    end if

    if (eval_D) then
      call comms_reduce(gyro_D(1, 1, 1), 3*3*fermi_n, 'SUM', error, comm)
      if (allocated(error)) return
    end if

    if (eval_C) then
      call comms_reduce(gyro_C(1, 1, 1), 3*3*fermi_n, 'SUM', error, comm)
      if (allocated(error)) return
    end if

    if (eval_Dw) then
      call comms_reduce(gyro_Dw(1, 1, 1, 1), 3*3*fermi_n*pw90_gyrotropic%nfreq, 'SUM', error, comm)
      if (allocated(error)) return
    end if

    if (eval_dos) then
      call comms_reduce(gyro_DOS(1), fermi_n, 'SUM', error, comm)
      if (allocated(error)) return
    end if

    if (eval_NOA) then
      call comms_reduce(gyro_NOA_orb(1, 1, 1, 1), 3*3*fermi_n*pw90_gyrotropic%nfreq, &
                        'SUM', error, comm)
      if (allocated(error)) return
      if (eval_spn) then
        call comms_reduce(gyro_NOA_spn(1, 1, 1, 1), 3*3*fermi_n*pw90_gyrotropic%nfreq, &
                          'SUM', error, comm)
        if (allocated(error)) return
      end if
    end if

    if (print_output%iprint > 0) then

      if (print_output%timing_level > 1) call io_stopwatch_stop('gyrotropic: k-interpolation', timer)
      write (stdout, '(1x,a)') ' '
      write (stdout, *) 'Calculation finished, writing results'
      flush (stdout)

      if (eval_K) then
        if (eval_spn) then
          ! At this point gme_spn_list contains
          ! (1/N) sum_k delta(E_kn-E_f).(d E_{kn}/d k_i).sigma_{kn,j}
          ! (units of length) in Angstroms.
          !        ====================================
          ! To get K in units of Ampere do the following:
          !        ====================================
          !   * Divide by V_c in Ang^3 to get a quantity with units of [L]^{-2}
          !   * Multiply by 10^20 to convert to SI
          !   * Multiply by -g_s.e.hbar/(4m_e) \simeq e.hbar/(2.m_e) in SI units
          !==================================================
          ! fac = 10^20*e*hbar/(2.m_e.V_c)
          !==================================================
          fac = -1.0e20_dp*physics%elem_charge_SI*physics%hbar_SI/(2.*physics%elec_mass_SI &
                                                                   *cell_volume)
          gyro_K_spn(:, :, :) = gyro_K_spn(:, :, :)*fac
          f_out_name_tmp = 'K_spin'
          units_tmp = "Ampere"
          comment_tmp = "spin part of the K tensor -- Eq. 3 of TAS17"
          call gyrotropic_outprint_tensor(stdout, seedname, pw90_gyrotropic, fermi_energy_list, f_out_name_tmp, &
                                          arrEf=gyro_K_spn, units=units_tmp, comment=comment_tmp)
        end if  ! eval_K && eval_spin

        ! At this point gme_orb_list contains
        ! (1/N)sum_{k,n} delta(E_kn-E_f).(d E_{kn}/d k_i)
        !                           .Im[<del_k u_kn| x (H_k-E_kn)|del_k u_kn>]
        ! (units of energy times length^3) in eV.Ang^3.
        !        ====================================
        ! To get K  in units of Ampere do the following:
        !        ====================================
        !   * Divide by V_c in Ang^3 to get a quantity with units of eV
        !   * Multiply by 'e' in SI to convert to SI (Joules)
        !   * Multiply by e/(2.hbar) to get K in Ampere
        !==================================================
        ! fac = e^2/(2.hbar.V_c)
        !==================================================
        fac = physics%elem_charge_SI**2/(2.*physics%hbar_SI*cell_volume)
        gyro_K_orb(:, :, :) = gyro_K_orb(:, :, :)*fac

        f_out_name_tmp = 'K_orb'
        units_tmp = "Ampere"
        comment_tmp = "orbital part of the K tensor -- Eq. 3 of TAS17"
        call gyrotropic_outprint_tensor(stdout, seedname, pw90_gyrotropic, fermi_energy_list, f_out_name_tmp, &
                                        arrEf=gyro_K_orb, units=units_tmp, comment=comment_tmp)
      end if ! eval_K

      if (eval_D) then
        fac = 1./cell_volume
        gyro_D(:, :, :) = gyro_D(:, :, :)*fac

        f_out_name_tmp = 'D'
        units_tmp = "dimensionless"
        comment_tmp = "the D tensor -- Eq. 2 of TAS17"
        call gyrotropic_outprint_tensor(stdout, seedname, pw90_gyrotropic, fermi_energy_list, f_out_name_tmp, &
                                        arrEf=gyro_D, units=units_tmp, comment=comment_tmp)
      end if

      if (eval_Dw) then
        fac = 1./cell_volume
        gyro_Dw(:, :, :, :) = gyro_Dw(:, :, :, :)*fac

        f_out_name_tmp = 'tildeD'
        units_tmp = "dimensionless"
        comment_tmp = "the tildeD tensor -- Eq. 12 of TAS17"
        call gyrotropic_outprint_tensor(stdout, seedname, pw90_gyrotropic, fermi_energy_list, f_out_name_tmp, &
                                        arrEfW=gyro_Dw, units=units_tmp, comment=comment_tmp)
      end if

      if (eval_C) then
        ! At this point gyro_C contains
        ! (1/N)sum_{k,n} delta(E_kn-E_f).(d E_{kn}/d k_i).(d E_{kn}/d k_j)
        ! (units of energy*length^2) in eV*Ang^2
        !
        ! To get it in Cab = e/h * (1/N*V_cell)sum_{k,n} delta(E_kn-E_f).(d E_{kn}/d k_i).(d E_{kn}/d k_j)
        ! in units Ampere/cm
        !
        ! divide by V_c in Ang^3 to get  eV/Ang
        ! multiply by 10^8*e in SI to get J/cm
        ! multiply by e/h in SI
        !
        fac = 1.0e+8_dp*physics%elem_charge_SI**2/(twopi*physics%hbar_SI*cell_volume)
        gyro_C(:, :, :) = gyro_C(:, :, :)*fac

        f_out_name_tmp = 'C'
        units_tmp = "Ampere/cm"
        comment_tmp = "the C tensor -- Eq. B6 of TAS17"
        call gyrotropic_outprint_tensor(stdout, seedname, pw90_gyrotropic, fermi_energy_list, f_out_name_tmp, &
                                        arrEf=gyro_C, units=units_tmp, comment=comment_tmp)
      end if

      if (eval_noa) then
        ! at this point gyro_NOA_orb  is in eV^-1.Ang^3   !
        !  We want the result in angstrems  !
        !   * Divide by V_c in Ang^3 to make it eV^{-1}
        !   * Divide by e in SI to get J^{-1}
        !   * multiply by e^2/eps_0 to get meters
        !   *multiply dy 1e10 to get Ang
        fac = 1e+10_dp*physics%elem_charge_SI/(cell_volume*physics%eps0_SI)
        gyro_NOA_orb = gyro_NOA_orb*fac
        f_out_name_tmp = 'NOA_orb'
        units_tmp = "Ang"
        comment_tmp = "the tensor $gamma_{abc}^{orb}$ (Eq. C12,C14 of TAS17)"
        call gyrotropic_outprint_tensor(stdout, seedname, pw90_gyrotropic, fermi_energy_list, f_out_name_tmp, &
                                        arrEfW=gyro_NOA_orb, units=units_tmp, comment=comment_tmp, &
                                        symmetrize=.false.)

        if (eval_spn) then
          ! at this point gyro_NOA_spn  is in eV^-2.Ang   !
          !  We want the result in angstrems  !
          !   * Divide by V_c in Ang^3 to make it (eV.Ang)^{-2}
          !   * multiply by 1e20/e^2 in SI to get (J.m)^{-2}
          !   * multiply by e^2/eps_0 to get (J.m)^{-1}
          !   *multiply dy hbar^2/m_e to get m
          !   *multiply by 1e10 to get Ang
          fac = 1e+30_dp*physics%hbar_SI**2/(cell_volume*physics%eps0_SI*physics%elec_mass_SI)
          gyro_NOA_spn = gyro_NOA_spn*fac
          f_out_name_tmp = 'NOA_spin'
          units_tmp = "Ang"
          comment_tmp = "the tensor $gamma_{abc}^{spin}$ (Eq. C12,C15 of TAS17)"
          call gyrotropic_outprint_tensor(stdout, seedname, pw90_gyrotropic, fermi_energy_list, f_out_name_tmp, &
                                          arrEfW=gyro_NOA_spn, units=units_tmp, &
                                          comment=comment_tmp, symmetrize=.false.)
        end if
      end if  !eval_NOA

      if (eval_DOS) then
        ! At this point gyro_C contains
        ! (1/N)sum_{k,n} delta(E_kn-E_f)
        ! in units of eV^{-1}
        ! divide by V_c in Ang^3 to get units 1./(eV*Ang^3)
        gyro_DOS(:) = gyro_DOS(:)/cell_volume
        f_out_name_tmp = 'DOS'
        units_tmp = "eV^{-1}.Ang^{-3}"
        comment_tmp = "density of states"
        call gyrotropic_outprint_tensor(stdout, seedname, pw90_gyrotropic, fermi_energy_list, f_out_name_tmp, &
                                        arrEf1d=gyro_DOS, units=units_tmp, comment=comment_tmp)
      end if

    end if !print_output%iprint >0, aka "on_root"

  end subroutine gyrotropic_main

  subroutine gyrotropic_get_k_list(ws_region, num_valence_bands, have_disentangled, kpt, kweight, &
                                   gyro_K_spn, gyro_K_orb, gyro_D, gyro_Dw, gyro_C, gyro_DOS, &
                                   gyro_NOA_orb, gyro_NOA_spn, eval_K, eval_D, eval_Dw, eval_NOA, &
                                   eval_spn, eval_C, eval_dos, num_wann, print_output, &
                                   fermi_energy_list, wannier_data, eigval, real_lattice, mp_grid, &
                                   num_bands, num_kpts, u_matrix, v_matrix, dis_manifold, kpt_latt, &
                                   pw90_gyrotropic, scissors_shift, effective_model, pw90_band_deriv_degen, &
                                   ws_distance, wigner_seitz, stdout, seedname, timer, error, &
                                   comm, HH_R, AA_R, BB_R, CC_R, SS_R)
    !======================================================================!
    !                                                                      !
    ! Contribution from point k to the GME tensor, Eq.(9) of ZMS16,        !
    ! evaluated in the clean limit of omega.tau >> 1 where  it is real.    !
    ! The following two quantities are calculated (sigma = Pauli matrix):  !
    !                                                                      !
    ! gyro_K_spn_k = delta(E_kn-E_f).(d E_{kn}/d k_i).sigma_{kn,j}         !
    ! [units of length]                                                    !
    !                                                                      !
    ! gyro_K_orb_k = delta(E_kn-E_f).(d E_{kn}/d k_i).(2.hbar/e).m^orb_{kn,j} !
    ! [units of (length^3)*energy]                                         !
    !                                                                      !
    ! gyro_D_k = delta(E_kn-E_f).(d E_{kn}/d k_i).Omega_{kn,j}             !
    ! [units of length^3]                                                  !
    !                                                                      !
    ! gyro_Dw_k = delta(E_kn-E_f).(d E_{kn}/d k_i).tildeOmega_{kn,j}       !
    ! [units of length^3]                                                  !
    !                                                                      !
    ! gyro_C_k = delta(E_kn-E_f).(d E_{kn}/d k_i).(d E_{kn}/d k_j)         !
    ! [units of energy*length^3]                                           !
    !                                                                      !
    ! gyro_DOS_k = delta(E_kn-E_f)                                         !
    ! [units of 1/Energy]                                                  !
    !                                                                      !
    ! gme_NOA_orb_k = ?????                                                !
    !                                                                      !
    ! gme_NOA_spn_k = ??????                                               !
    !                                                                      !
    !======================================================================!

    use w90_comms, only: w90_comm_type, mpirank
    use w90_constants, only: dp, cmplx_0, cmplx_i
    use w90_postw90_types, only: pw90_gyrotropic_type, pw90_band_deriv_degen_type, wigner_seitz_type
    use w90_types, only: dis_manifold_type, print_output_type, &
                         wannier_data_type, ws_region_type, ws_distance_type, timer_list_type
    use w90_postw90_common, only: pw90common_fourier_R_to_k_new_second_d, &
                                  pw90common_fourier_R_to_k_vec
    use w90_spin, only: spin_get_S
    use w90_utility, only: utility_diagonalize, utility_rotate, utility_rotate_diag, &
                           utility_w0gauss, utility_recip_lattice_base
    use w90_wan_ham, only: wham_get_eig_deleig, wham_get_D_h

    implicit none

    ! arguments
    type(dis_manifold_type), intent(in) :: dis_manifold
    real(kind=dp), allocatable, intent(in) :: fermi_energy_list(:)
    type(pw90_gyrotropic_type), intent(in) :: pw90_gyrotropic
    real(kind=dp), intent(in) :: kpt_latt(:, :)
    type(pw90_band_deriv_degen_type), intent(in) :: pw90_band_deriv_degen
    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) :: mp_grid(3)
    integer, intent(in) :: num_bands, num_kpts, num_wann, num_valence_bands
    integer, intent(in) :: stdout

    real(kind=dp), allocatable, intent(inout) :: gyro_DOS(:)
    real(kind=dp), allocatable, intent(inout) :: gyro_Dw(:, :, :, :)
    real(kind=dp), allocatable, intent(inout) :: gyro_NOA_spn(:, :, :, :)
    real(kind=dp), allocatable, intent(inout) :: gyro_NOA_orb(:, :, :, :)
    real(kind=dp), allocatable, intent(inout) :: gyro_K_spn(:, :, :)
    real(kind=dp), allocatable, intent(inout) :: gyro_K_orb(:, :, :)
    real(kind=dp), allocatable, intent(inout) :: gyro_D(:, :, :)
    real(kind=dp), allocatable, intent(inout) :: gyro_C(:, :, :)
    real(kind=dp), intent(in) :: eigval(:, :)
    real(kind=dp), intent(in) :: kpt(3), kweight
    real(kind=dp), intent(in) :: real_lattice(3, 3)
    real(kind=dp), intent(in) :: scissors_shift

    complex(kind=dp), allocatable, intent(inout) :: AA_R(:, :, :, :)
    complex(kind=dp), allocatable, intent(inout) :: BB_R(:, :, :, :)
    complex(kind=dp), allocatable, intent(inout) :: CC_R(:, :, :, :, :)
    complex(kind=dp), allocatable, intent(inout) :: HH_R(:, :, :)
    complex(kind=dp), allocatable, intent(inout) :: SS_R(:, :, :, :)
    complex(kind=dp), intent(in) :: u_matrix(:, :, :), v_matrix(:, :, :)

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

    logical, intent(in) :: eval_K, eval_D, eval_Dw, eval_C, eval_NOA, eval_spn, eval_dos
    logical, intent(in) :: have_disentangled
    logical, intent(in) :: effective_model

    ! local variables
    complex(kind=dp), allocatable :: UU(:, :)
    complex(kind=dp), allocatable :: HH(:, :)
    complex(kind=dp), allocatable :: delHH(:, :, :)
    complex(kind=dp), allocatable :: SS(:, :, :)
    complex(kind=dp), allocatable :: AA(:, :, :)
    complex(kind=dp), allocatable :: D_h(:, :, :)

    real(kind=dp), allocatable :: curv_w_nk(:, :, :)

    integer          :: i, j, n, n1, ifermi, fermi_n
    real(kind=dp)    :: delta, occ(num_wann), &
                        eig(num_wann), del_eig(num_wann, 3), &
                        S(num_wann, 3), eta_smr, arg, &
                        orb_nk(3), curv_nk(3), &
                        imf_k(3, 3, 1), img_k(3, 3, 1), imh_k(3, 3, 1)
    logical          :: got_spin, got_orb_n

    if (pw90_gyrotropic%smearing%use_adaptive) then
      call set_error_input(error, 'Adaptive smearing not allowed in Gyrotropic', comm)
      return
    end if

    allocate (UU(num_wann, num_wann))
    allocate (HH(num_wann, num_wann))
    allocate (delHH(num_wann, num_wann, 3))
    allocate (D_h(num_wann, num_wann, 3))

    if (eval_spn) allocate (SS(num_wann, num_wann, 3))

    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

    if (eval_Dw .or. eval_NOA) then
      allocate (AA(num_wann, num_wann, 3))
      call wham_get_D_h(delHH, D_h, UU, eig, num_wann)
      call pw90common_fourier_R_to_k_vec(ws_region, wannier_data, ws_distance, wigner_seitz, AA_R, &
                                         kpt, real_lattice, mp_grid, num_wann, error, comm, &
                                         OO_true=AA)
      if (allocated(error)) return

      do i = 1, 3
        AA(:, :, i) = utility_rotate(AA(:, :, i), UU, num_wann)
      end do
      AA = AA + cmplx_i*D_h ! Eq.(25) WYSV06
    end if

    if (eval_Dw) allocate (curv_w_nk(num_wann, pw90_gyrotropic%nfreq, 3))

    eta_smr = pw90_gyrotropic%smearing%fixed_width

    got_spin = .false.

    do n1 = 1, pw90_gyrotropic%num_bands
      n = pw90_gyrotropic%band_list(n1)
      !
      ! ***ADJUSTABLE PARAMETER***
      ! avoid degeneracies
      !---------------------------------------------------
      if (n > 1) then
        if (eig(n) - eig(n - 1) <= pw90_gyrotropic%degen_thresh) cycle
      end if
      if (n < num_wann) then
        if (eig(n + 1) - eig(n) <= pw90_gyrotropic%degen_thresh) cycle
      end if
      !---------------------------------------------------
      fermi_n = size(fermi_energy_list)
      got_orb_n = .false.
      do ifermi = 1, fermi_n
        arg = (eig(n) - fermi_energy_list(ifermi))/eta_smr
        !
        ! To save time: far from the Fermi surface, negligible contribution
        !
        !-------------------------
        if (abs(arg) > pw90_gyrotropic%smearing%max_arg) cycle
        !-------------------------
        !
        ! Spin is computed for all bands simultaneously
        !
        if (eval_spn .and. .not. got_spin) then
          call spin_get_S(kpt, S, num_wann, ws_region, wannier_data, real_lattice, mp_grid, &
                          ws_distance, HH_R, SS_R, wigner_seitz, error, comm)
          if (allocated(error)) return
          got_spin = .true. ! Do it for only one value of ifermi and n
        end if
        ! Orbital quantities are computed for each band separately
        if (.not. got_orb_n) then
          if (eval_K) then
            ! Fake occupations: band n occupied, others empty
            occ = 0.0_dp
            occ(n) = 1.0_dp

            call berry_get_imfgh_klist(dis_manifold, fermi_energy_list, kpt_latt, ws_region, &
                                       print_output, wannier_data, ws_distance, wigner_seitz, &
                                       AA_R, BB_R, CC_R, HH_R, u_matrix, v_matrix, eigval, kpt, &
                                       real_lattice, scissors_shift, mp_grid, fermi_n, num_bands, &
                                       num_kpts, num_wann, num_valence_bands, effective_model, &
                                       have_disentangled, seedname, stdout, timer, error, comm, &
                                       imf_k, img_k, imh_k, occ)
            if (allocated(error)) return

            do i = 1, 3
              orb_nk(i) = sum(imh_k(:, i, 1)) - sum(img_k(:, i, 1))
              curv_nk(i) = sum(imf_k(:, i, 1))
            end do
          else if (eval_D) then
            occ = 0.0_dp
            occ(n) = 1.0_dp

            call berry_get_imf_klist(dis_manifold, fermi_energy_list, kpt_latt, ws_region, &
                                     print_output, wannier_data, ws_distance, wigner_seitz, AA_R, &
                                     BB_R, CC_R, HH_R, u_matrix, v_matrix, eigval, kpt, &
                                     real_lattice, imf_k, scissors_shift, mp_grid, num_bands, &
                                     num_kpts, num_wann, num_valence_bands, effective_model, &
                                     have_disentangled, seedname, stdout, timer, error, comm, occ)
            if (allocated(error)) return

            do i = 1, 3
              curv_nk(i) = sum(imf_k(:, i, 1))
            end do
            got_orb_n = .true. ! Do it for only one value of ifermi
          end if

          if (eval_Dw) call gyrotropic_get_curv_w_k(eig, AA, curv_w_nk, pw90_gyrotropic)

          got_orb_n = .true. ! Do it for only one value of ifermi
        end if
        !
        delta = utility_w0gauss(arg, pw90_gyrotropic%smearing%type_index, error, comm) &
                /eta_smr*kweight ! Broadened delta(E_nk-E_f)
        if (allocated(error)) return

        !
        ! Loop over Cartesian tensor components
        !
        do j = 1, 3
          if (eval_K .and. eval_spn) gyro_K_spn(:, j, ifermi) = &
            gyro_K_spn(:, j, ifermi) + del_eig(n, :)*S(n, j)*delta
          if (eval_K) gyro_K_orb(:, j, ifermi) = &
            gyro_K_orb(:, j, ifermi) + del_eig(n, :)*orb_nk(j)*delta
          if (eval_D) gyro_D(:, j, ifermi) = &
            gyro_D(:, j, ifermi) + del_eig(n, :)*curv_nk(j)*delta
          if (eval_Dw) then
            do i = 1, 3
              gyro_Dw(i, j, ifermi, :) = &
                gyro_Dw(i, j, ifermi, :) + del_eig(n, i)*delta*curv_w_nk(n, :, j)
            end do
          end if
          if (eval_C) gyro_C(:, j, ifermi) = &
            gyro_C(:, j, ifermi) + del_eig(n, :)*del_eig(n, j)*delta
        end do !j
        if (eval_dos) gyro_DOS(ifermi) = gyro_DOS(ifermi) + delta

      end do !ifermi
    end do !n

    if (eval_NOA) then
      if (eval_spn) then
        call gyrotropic_get_NOA_k(ws_region, kpt, kweight, eig, del_eig, AA, UU, gyro_NOA_orb, &
                                  num_wann, print_output, fermi_energy_list, wannier_data, &
                                  real_lattice, mp_grid, pw90_gyrotropic, ws_distance, &
                                  wigner_seitz, stdout, error, comm, SS_R, gyro_NOA_spn)
        if (allocated(error)) return
      else
        call gyrotropic_get_NOA_k(ws_region, kpt, kweight, eig, del_eig, AA, UU, gyro_NOA_orb, &
                                  num_wann, print_output, fermi_energy_list, wannier_data, &
                                  real_lattice, mp_grid, pw90_gyrotropic, ws_distance, &
                                  wigner_seitz, stdout, error, comm, SS_R)
        if (allocated(error)) return
      end if
    end if

  end subroutine gyrotropic_get_k_list

  subroutine gyrotropic_get_curv_w_k(eig, AA, curv_w_k, pw90_gyrotropic)
    !================================================!
    !
    ! calculation of the band-resolved
    ! frequency-dependent   berry curvature
    !
    ! tildeOmega(w)=
    !     -eps_{bcd}sum_m ( w_mn^2/(wmn^2-w^2)) *Im[A_{nm,c}A_{mn,d}
    !
    !================================================!

    use w90_postw90_types, only: pw90_gyrotropic_type
    use w90_constants, only: dp

    implicit none

    ! arguments
    type(pw90_gyrotropic_type), intent(in) :: pw90_gyrotropic
    real(kind=dp), intent(in) :: eig(:)
    real(kind=dp), intent(out) :: curv_w_k(:, :, :)  ! (num_wann,n_freq,3)
    complex(kind=dp), intent(in) :: AA(:, :, :)

    ! local variables
    real(kind=dp), allocatable    :: multWre(:)
    integer          :: i, n, m, n1, m1
    real(kind=dp)    :: wmn

    allocate (multWre(pw90_gyrotropic%nfreq))
    curv_w_k(:, :, :) = 0_dp

    do n1 = 1, pw90_gyrotropic%num_bands
      n = pw90_gyrotropic%band_list(n1)
      do m1 = 1, pw90_gyrotropic%num_bands
        m = pw90_gyrotropic%band_list(m1)
        if (n == m) cycle
        wmn = eig(m) - eig(n)
        multWre(:) = real(wmn**2/(wmn**2 - pw90_gyrotropic%freq_list(:)**2))
        do i = 1, 3
          curv_w_k(n, :, i) = curv_w_k(n, :, i) - &
                              2_dp*aimag(AA(n, m, alpha_A(i))*AA(m, n, beta_A(i)))*multWre
        end do
      end do !m
    end do !n

  end subroutine gyrotropic_get_curv_w_k

  subroutine gyrotropic_get_NOA_k(ws_region, kpt, kweight, eig, del_eig, AA, UU, gyro_NOA_orb, &
                                  num_wann, print_output, fermi_energy_list, wannier_data, &
                                  real_lattice, mp_grid, pw90_gyrotropic, ws_distance, &
                                  wigner_seitz, stdout, error, comm, SS_R, gyro_NOA_spn)
    !================================================!
    !
    ! Contribution from point k to the real (antisymmetric) part
    ! of the natural  complex interband optical conductivity
    !
    ! Re gyro_NOA_orb  =  SUM_{n,l}^{oe}  hbar^{-2}/(w_nl^2-w^2) *
    !   Re (  A_lnb Bnlac -Alna Bnlbc)
    !     -SUM_{n,l}^{oe}  hbar^{-2}(3*w_ln^2-w^2)/(w_nl^2-w^2)^2 *
    ! Im (  A_lnb Bnlac -Alna Bnlac)nm_a A_mn_b )
    ! [units of Ang^3/eV]

    ! [units of Ang^3]
    ! Re gyro_NOA_spn_{ab,c}  =  SUM_{n,l}^{oe}  hbar^{-2}/(w_nl^2-w^2) *
    !   Re (  A_lnb Bnlac -Alna Bnlbc)
    ! [units of Ang/eV^2]
    !
    !   here a,b  defined as epsilon_{abd}=1  (and NOA_dc tensor is saved)  !
    !================================================!

    use w90_postw90_types, only: pw90_gyrotropic_type, wigner_seitz_type
    use w90_constants, only: dp, cmplx_1
    use w90_io, only: io_time
    use w90_types, only: print_output_type, wannier_data_type, ws_region_type, &
                         ws_distance_type
    use w90_postw90_common, only: pw90common_fourier_R_to_k_new
    use w90_spin, only: spin_get_S
    use w90_utility, only: utility_rotate
    use w90_comms, only: w90_comm_type

    implicit none

    ! arguments
    real(kind=dp), allocatable, intent(in) :: fermi_energy_list(:)
    type(pw90_gyrotropic_type), intent(in) :: pw90_gyrotropic
    type(print_output_type), intent(in) :: print_output
    type(ws_region_type), intent(in) :: ws_region
    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(w90_error_type), allocatable, intent(out) :: error
    type(w90_comm_type), intent(in) :: comm

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

    real(kind=dp), intent(in) :: del_eig(:, :)
    real(kind=dp), intent(in) :: eig(:)
    real(kind=dp), intent(inout) :: gyro_NOA_orb(:, :, :, :)
    real(kind=dp), intent(inout), optional :: gyro_NOA_spn(:, :, :, :)
    real(kind=dp), intent(in) :: kpt(3), kweight
    real(kind=dp), intent(in) :: real_lattice(3, 3)

    complex(kind=dp), allocatable, intent(inout) :: SS_R(:, :, :, :)
    complex(kind=dp), intent(in) :: AA(:, :, :)
    complex(kind=dp), intent(in) :: UU(:, :)

    ! local variables
    integer :: j, n, l, n1, l1, a, b, c, ab, ifermi
    integer ::  num_occ, num_unocc, occ_list(num_wann), unocc_list(num_wann)

    real(kind=dp) :: wln
    real(kind=dp) :: multWe(pw90_gyrotropic%nfreq), multWm(pw90_gyrotropic%nfreq)
    complex(kind=dp) :: multW1(pw90_gyrotropic%nfreq)

    complex(kind=dp), allocatable :: S_h(:, :, :)
    complex(kind=dp), allocatable :: SS(:, :, :)
    complex(kind=dp), allocatable :: Bnl_orb(:, :, :, :)
    complex(kind=dp), allocatable :: Bnl_spin(:, :, :, :)

    if (present(gyro_NOA_spn)) then
      allocate (SS(num_wann, num_wann, 3))
      allocate (S_h(num_wann, num_wann, 3))
      do j = 1, 3 ! spin direction
        call pw90common_fourier_R_to_k_new(ws_region, wannier_data, ws_distance, wigner_seitz, &
                                           SS_R(:, :, :, j), kpt, real_lattice, mp_grid, num_wann, &
                                           error, comm, OO=SS(:, :, j))
        if (allocated(error)) return

        S_h(:, :, j) = utility_rotate(SS(:, :, j), UU, num_wann)
      end do
    end if

    do ifermi = 1, size(fermi_energy_list)

      num_occ = 0
      num_unocc = 0
      do n1 = 1, pw90_gyrotropic%num_bands
        n = pw90_gyrotropic%band_list(n1)
        if (eig(n) < fermi_energy_list(ifermi)) then
          num_occ = num_occ + 1
          occ_list(num_occ) = n
        elseif (eig(n) < pw90_gyrotropic%eigval_max) then
          num_unocc = num_unocc + 1
          unocc_list(num_unocc) = n
        end if
      end do

      if (num_occ == 0) then
        if (print_output%iprint .ge. 2) &
          write (stdout, *) "WARNING no occupied bands included in the calculation for kpt=", &
          kpt, ", EF[", ifermi, "]=", fermi_energy_list(ifermi), "eV"
        cycle
      end if

      if (num_unocc == 0) then
        if (print_output%iprint .ge. 2) &
          write (stdout, *) "WARNING no unoccupied bands included in the calculation for kpt=", &
          kpt, ", EF[", ifermi, "]=", fermi_energy_list(ifermi), "eV"
        cycle
      end if

      allocate (Bnl_orb(num_occ, num_unocc, 3, 3))
      call gyrotropic_get_NOA_Bnl_orb(eig, del_eig, AA, num_occ, occ_list, num_unocc, unocc_list, &
                                      Bnl_orb, pw90_gyrotropic)

      if (present(gyro_NOA_spn)) then
        allocate (Bnl_spin(num_occ, num_unocc, 3, 3))
        call gyrotropic_get_NOA_Bnl_spin(S_h, num_occ, occ_list, num_unocc, unocc_list, Bnl_spin)
      end if

      do n1 = 1, num_occ
        n = occ_list(n1)
        do l1 = 1, num_unocc
          l = unocc_list(l1)

          wln = eig(l) - eig(n)
          multW1(:) = cmplx_1/(wln*wln - pw90_gyrotropic%freq_list(:)**2)
          multWm(:) = real(multW1)*kweight
          multWe(:) = real(-multW1(:)*(2*wln**2*multW1(:) + cmplx_1))*kweight
          do ab = 1, 3
            a = alpha_A(ab)
            b = beta_A(ab)
            do c = 1, 3
              gyro_NOA_orb(ab, c, ifermi, :) = &
                gyro_NOA_orb(ab, c, ifermi, :) + &
                multWm(:)*real(AA(l, n, b)*Bnl_orb(n1, l1, a, c) - &
                               AA(l, n, a)*Bnl_orb(n1, l1, b, c)) + &
                multWe(:)*(del_eig(n, c) + del_eig(l, c))*aimag(AA(n, l, a)*AA(l, n, b))

              if (present(gyro_NOA_spn)) &
                gyro_NOA_spn(ab, c, ifermi, :) = &
                gyro_NOA_spn(ab, c, ifermi, :) + &
                multWm(:)*real(AA(l, n, b)*Bnl_spin(n1, l1, a, c) - &
                               AA(l, n, a)*Bnl_spin(n1, l1, b, c))

            end do ! c
          end do ! ab
        end do  ! l1
      end do ! n1
      deallocate (Bnl_orb)
      if (present(gyro_NOA_spn)) deallocate (Bnl_spin)
    end do !ifermi

  end subroutine gyrotropic_get_NOA_k

  subroutine gyrotropic_get_NOA_Bnl_orb(eig, del_eig, AA, num_occ, occ_list, num_unocc, &
                                        unocc_list, Bnl, pw90_gyrotropic)
    !================================================!
    !
    ! Calculating the matrix
    ! B_{nl,ac}(num_occ,num_unocc,3,3)=
    !      -sum_m(  (E_m-E_n)A_nma*Amlc +(E_l-E_m)A_nmc*A_mla -
    !      -i( del_a (E_n+E_l) A_nlc
    !   in units eV*Ang^2
    !================================================!

    use w90_postw90_types, only: pw90_gyrotropic_type
    use w90_constants, only: dp, cmplx_i, cmplx_0

    implicit none

    ! arguments
    type(pw90_gyrotropic_type), intent(in) :: pw90_gyrotropic
    integer, intent(in) :: num_occ, num_unocc
    integer, intent(in) :: occ_list(:), unocc_list(:)
    real(kind=dp), intent(in) :: eig(:)               !  n
    real(kind=dp), intent(in) :: del_eig(:, :)        !  n
    complex(kind=dp), intent(in) :: AA(:, :, :)       !  n,l,a
    complex(kind=dp), intent(out) :: Bnl(:, :, :, :)  !  n,l,a,c

    ! local variables
    integer n, m, l, a, c, n1, m1, l1
    Bnl(:, :, :, :) = cmplx_0

    do a = 1, 3
      do c = 1, 3
        do n1 = 1, num_occ
          n = occ_list(n1)
          do l1 = 1, num_unocc
            l = unocc_list(l1)
            Bnl(n1, l1, a, c) = -cmplx_i*(del_eig(n, a) + del_eig(l, a))*AA(n, l, c)
            do m1 = 1, pw90_gyrotropic%num_bands
              m = pw90_gyrotropic%band_list(m1)
              Bnl(n1, l1, a, c) = Bnl(n1, l1, a, c) + &
                                  (eig(n) - eig(m))*AA(n, m, a)*AA(m, l, c) - &
                                  (eig(l) - eig(m))*AA(n, m, c)*AA(m, l, a)
            end do ! m1
          end do !l1
        end do !n1
      end do !c
    end do !a

  end subroutine gyrotropic_get_NOA_Bnl_orb

  subroutine gyrotropic_get_NOA_Bnl_spin(S_h, num_occ, occ_list, num_unocc, unocc_list, Bnl)
    !================================================!
    !
    ! Calculating the matrix
    ! B_{nl,ac}^spin(num_occ,num_unocc,3,3)=
    !      -i  eps_{abc}  < u_n | sigma_b | u_l >
    !   ( dimensionless )
    !================================================!

    use w90_constants, only: dp, cmplx_i, cmplx_0

    implicit none

    ! arguments
    integer, intent(in) :: num_occ, num_unocc
    integer, intent(in) :: occ_list(:), unocc_list(:)
    complex(kind=dp), intent(in) :: S_h(:, :, :)     !  n,l,a
    complex(kind=dp), intent(out) :: Bnl(:, :, :, :)     !   n,l,a,c

    ! local variables
    integer n, l, a, b, c, n1, l1

    Bnl(:, :, :, :) = cmplx_0

    do b = 1, 3
      c = alpha_A(b)
      a = beta_A(b)
      do n1 = 1, num_occ
        n = occ_list(n1)
        do l1 = 1, num_unocc
          l = unocc_list(l1)
          Bnl(n1, l1, a, c) = S_h(n, l, b)
        end do !l1
      end do !n1
    end do !b

    Bnl = Bnl*(-cmplx_i)

  end subroutine gyrotropic_get_NOA_Bnl_spin

  subroutine gyrotropic_outprint_tensor(stdout, seedname, pw90_gyrotropic, fermi_energy_list, &
                                        f_out_name, arrEf, arrEF1D, arrEfW, units, comment, &
                                        symmetrize)
    !================================================!

    use w90_postw90_types, only: pw90_gyrotropic_type

    implicit none

    ! arguments
    real(kind=dp), allocatable, intent(in) :: fermi_energy_list(:)
    type(pw90_gyrotropic_type), intent(in) :: pw90_gyrotropic

    integer, intent(in) :: stdout

    real(kind=dp), intent(in), optional :: arrEf(:, :, :)
    real(kind=dp), intent(in), optional :: arrEfW(:, :, :, :)
    real(kind=dp), intent(in), optional :: arrEf1D(:)
    character(len=30), intent(in) :: f_out_name
    character(len=50), intent(in) :: seedname
    character(len=30), intent(in), optional :: units
    character(len=120), intent(in), optional :: comment

    logical, optional, intent(in) :: symmetrize

    ! local variables
    character(len=120)  :: file_name
    integer             :: i, file_unit, fermi_n
    logical             :: lsym

    lsym = .true.
    if (present(symmetrize)) then
      if (.not. symmetrize) lsym = .false.
    end if

    file_name = trim(seedname)//"-gyrotropic-"//trim(f_out_name)//".dat"
    file_name = trim(file_name)
    write (stdout, '(/,3x,a)') '* '//file_name
    open (newunit=file_unit, FILE=file_name, STATUS='UNKNOWN', FORM='FORMATTED')

    if (present(comment)) write (file_unit, *) "#"//trim(comment)
    if (present(units)) write (file_unit, *) "# in units of [ "//trim(units)//" ] "

    fermi_n = size(fermi_energy_list)
    if (present(arrEf)) then
      call gyrotropic_outprint_tensor_w(fermi_energy_list, fermi_n, file_unit, 0.0_dp, arr33N=arrEf, symmetrize=lsym)
    elseif (present(arrEfW)) then
      do i = 1, pw90_gyrotropic%nfreq
        call gyrotropic_outprint_tensor_w(fermi_energy_list, fermi_n, file_unit, real(pw90_gyrotropic%freq_list(i)), &
                                          arr33N=arrEfW(:, :, :, i), symmetrize=lsym)
      end do
    elseif (present(arrEf1D)) then
      call gyrotropic_outprint_tensor_w(fermi_energy_list, fermi_n, file_unit, 0.0_dp, arrN=arrEf1D)
    end if

    close (file_unit)

  end subroutine gyrotropic_outprint_tensor

  subroutine gyrotropic_outprint_tensor_w(fermi_energy_list, fermi_n, file_unit, omega, arr33N, &
                                          arrN, symmetrize)
    !================================================!

    implicit none

    ! arguments
    real(kind=dp), allocatable, intent(in) :: fermi_energy_list(:)

    integer, intent(in) :: file_unit
    real(kind=dp), intent(in) :: omega
    integer, intent(in) :: fermi_n
    real(kind=dp), optional, intent(in) :: arr33N(:, :, :)
    real(kind=dp), optional, intent(in) :: arrN(:)

    ! symmetrize= True  - get symmetric and assimetric parts
    ! symmetrize= False - write the asymmetric (in xy) tensor gamma_{xyz}
    ! symmetrize not present - write as is
    logical, optional, intent(in) :: symmetrize

    ! local variables
    real(kind=dp) ::  xx(fermi_n), yy(fermi_n), zz(fermi_n), &
                     xy(fermi_n), xz(fermi_n), yz(fermi_n), &
                     x(fermi_n), y(fermi_n), z(fermi_n)
    integer :: i
    logical :: lsym

    if (present(arr33N)) then
      lsym = .false.
      if (present(symmetrize)) lsym = symmetrize

      if (lsym) then
        ! Symmetric part
        xx = arr33N(1, 1, :)
        yy = arr33N(2, 2, :)
        zz = arr33N(3, 3, :)
        xy = (arr33N(1, 2, :) + arr33N(2, 1, :))/2.0_dp
        xz = (arr33N(1, 3, :) + arr33N(3, 1, :))/2.0_dp
        yz = (arr33N(2, 3, :) + arr33N(3, 2, :))/2.0_dp
        ! Antisymmetric part, in polar-vector form
        x = (arr33N(2, 3, :) - arr33N(3, 2, :))/2.0_dp
        y = (arr33N(3, 1, :) - arr33N(1, 3, :))/2.0_dp
        z = (arr33N(1, 2, :) - arr33N(2, 1, :))/2.0_dp
      else
        xx = arr33N(1, 1, :)
        yy = arr33N(2, 2, :)
        zz = arr33N(3, 3, :)
        xy = arr33N(1, 2, :)
        xz = arr33N(1, 3, :)
        yz = arr33N(2, 3, :)
        x = arr33N(3, 2, :)
        y = arr33N(3, 1, :)
        z = arr33N(2, 1, :)
      end if

      if (present(symmetrize)) then
        if (symmetrize) then
          write (file_unit, '(a1,29x,a1,38x,a14,37x,a2,14x,a15,14x,a1)') '#', "|", "symmetric part", "||", "asymmetric part", "|"
          write (file_unit, '(11a15)') '# EFERMI(eV)', "omega(eV)", 'xx', 'yy', 'zz', 'xy', 'xz', 'yz', 'x', 'y', 'z'
        else
          write (file_unit, '(11a15)') '# EFERMI(eV)', "omega(eV)", 'yzx', 'zxy', 'xyz', 'yzy', 'yzz', 'zxz', 'xyy', 'xyx', 'zxx'
        end if
      else
        write (file_unit, '(11a15)') '# EFERMI(eV)', "omega(eV)", 'xx', 'yy', 'zz', 'xy', 'xz', 'yz', 'zy', 'xz', 'yx'
      end if

      do i = 1, fermi_n
        write (file_unit, '(11E15.6)') fermi_energy_list(i), omega, xx(i), yy(i), zz(i), xy(i), xz(i), yz(i), x(i), y(i), z(i)
      end do
    end if

    if (present(arrN)) then
      write (file_unit, '(2a15)') '# EFERMI(eV) '
      do i = 1, fermi_n
        write (file_unit, '(11E15.6)') fermi_energy_list(i), arrN(i)
      end do

    end if
    write (file_unit, *)
    write (file_unit, *)
  end subroutine gyrotropic_outprint_tensor_w

end module w90_gyrotropic
