LCOV - code coverage report
Current view: top level - src/70_gw - m_classify_bands.F90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 0.0 % 752 0
Test Date: 2026-09-21 13:49:52 Functions: 0.0 % 15 0

            Line data    Source code
       1              : !!****m* ABINIT/m_classify_bands
       2              : !! NAME
       3              : !!  m_classify_bands
       4              : !!
       5              : !! FUNCTION
       6              : !!  Finds the irreducible representation associated to
       7              : !!  a set of degenerate bands at a given k-point and spin.
       8              : !!
       9              : !! COPYRIGHT
      10              : !!  Copyright (C) 2008-2026 ABINIT group (MG)
      11              : !!  This file is distributed under the terms of the
      12              : !!  GNU General Public License, see ~abinit/COPYING
      13              : !!  or http://www.gnu.org/copyleft/gpl.txt .
      14              : !!
      15              : !! SOURCE
      16              : 
      17              : #if defined HAVE_CONFIG_H
      18              : #include "config.h"
      19              : #endif
      20              : 
      21              : #include "abi_common.h"
      22              : 
      23              : module m_classify_bands
      24              : 
      25              :  use, intrinsic :: iso_c_binding, only : c_f_pointer, c_loc
      26              :  use defs_basis
      27              :  use m_abicore
      28              :  use m_xmpi
      29              :  use m_errors
      30              :  use netcdf
      31              :  use m_nctk
      32              : 
      33              :  use m_fstrings,       only : itoa, ftoa, sjoin, ktoa, ltoa, strcat, yesno
      34              :  use m_geometry,       only : getspinrot
      35              :  use defs_datatypes,   only : pseudopotential_type
      36              :  use m_dtset,          only : dataset_type
      37              :  use m_dtfil,          only : datafiles_type
      38              :  use m_io_tools,       only : iomode_from_fname
      39              :  use m_time,           only : cwtime, cwtime_report
      40              :  use m_numeric_tools,  only : get_trace, print_arr
      41              :  use m_matrix,         only : is_unitary, is_identity, mati3inv
      42              :  use m_hdr,            only : hdr_type
      43              :  use m_hide_blas,      only : xdotc, xdotu, xcopy
      44              :  use m_fft_mesh,       only : rotate_FFT_mesh, calc_ceigr
      45              :  use m_crystal,        only : crystal_t
      46              :  use m_cgtools,        only : cg_zdotc
      47              :  use m_symtk,          only : sg_multable, sym_order
      48              :  use m_pawang,         only : pawang_type
      49              :  use m_pawrad,         only : pawrad_type
      50              :  use m_pawtab,         only : pawtab_type, pawtab_get_lsize
      51              :  use m_pawfgrtab,      only : pawfgrtab_type, pawfgrtab_init, pawfgrtab_print, pawfgrtab_free
      52              :  use m_pawcprj,        only : pawcprj_type, pawcprj_alloc, pawcprj_free, pawcprj_copy
      53              :  use m_paw_pwaves_lmn, only : paw_pwaves_lmn_t, paw_pwaves_lmn_init, paw_pwaves_lmn_free
      54              :  use m_paw_sphharm,    only : setsym_ylm
      55              :  use m_paw_nhat,       only : nhatgrid
      56              :  use m_wfd,            only : wfd_t
      57              :  use m_ebands,         only : ebands_t
      58              :  use m_common,  only : ebands_from_file
      59              :  use m_fftcore, only : sphere, get_kg, ngfft_seq
      60              :  use m_cgtk,    only : cgtk_rotate, cgtk_change_gsphere
      61              :  use m_kpts,    only : kpts_map
      62              :  use m_krank,   only : krank_t
      63              :  use m_esymm, only : esymm_t, esymm_free
      64              :  use m_ptgroups, only : get_classes
      65              :  use m_yaml, only : yamldoc_t, yamldoc_open
      66              :  use m_pair_list, only : pair_list
      67              : 
      68              :  implicit none
      69              : 
      70              :  private
      71              : !!***
      72              : 
      73              :  public :: classify_bands
      74              : 
      75              : !!****t* m_classify_bands/dmats_t
      76              : !! NAME
      77              : !! dmats_t
      78              : !!
      79              : !! FUNCTION
      80              : !! Store D_mn(S) = <psi_{mSk}| S | psi_{nk}> for all the k-points in the IBZ
      81              : !! and the bands in brange_spin.
      82              : !!
      83              : !! SOURCE
      84              : 
      85              : type, public :: dmats_t
      86              : 
      87              :  type(ebands_t) :: ks_ebands
      88              :  ! KS bands.
      89              : 
      90              :  type(crystal_t),pointer :: cryst => null()
      91              :  type(dataset_type),pointer :: dtset => null()
      92              : 
      93              :  integer,allocatable :: brange_spin(:,:)
      94              :  ! (2, nsppol)
      95              :  ! start and end band index for each spin
      96              : 
      97              :   integer,allocatable :: multable(:,:,:)
      98              :   ! (4,nsym,nsym)
      99              :   ! multable(1,sym1,sym2) gives the index of the symmetry product S1 * S2 in the symrel array. 0 if not found.
     100              :   ! multable(2:4,sym1,sym2)= the lattice vector that has to added to the fractional translation
     101              :   !   of the operation of index multable(1,sym1,sym2) to obtain the fractional translation of the product S1 * S2.
     102              : 
     103              :   integer,allocatable :: toinv(:,:)
     104              :   ! (4,nsym)
     105              :   ! toinv(1,sym1)=Gives the index of the inverse of the symmetry operation.
     106              :   !  S1 * S1^{-1} = {E, L} with E the identity and L a real-space lattice vector.
     107              :   ! toinv(2:4,sym1)=The lattice vector L
     108              :   !   Note that toinv can be easily obtained from multable but sometimes we do not need the full table.
     109              : 
     110              :  type(coeff5c_type), allocatable :: for_spin(:)
     111              : 
     112              :  contains
     113              :    procedure :: init => dmats_init                                   ! Initialize object
     114              :    procedure :: init_from_file => dmats_init_from_file               ! Initialize object from a NetCDF file
     115              :    procedure :: ncwrite => dmats_ncwrite                             ! Write D-matrices to an open NetCDF file
     116              :    procedure :: free => dmats_free                                   ! Free memory.
     117              :    procedure :: check => dmats_check                                 ! Check Dmats
     118              :    procedure :: classify => dmats_classify                           ! Classify irreps
     119              :    procedure :: get_star_dmats => dmats_get_star_dmats               ! D-matrices at k'=S0.k_ibz
     120              :    procedure :: get_star_dmats_at_kpt => dmats_get_star_dmats_at_kpt ! Same, locating S0 from a raw kpt
     121              :    procedure :: check_star => dmats_check_star                       ! Run dmats_check_one_k at a star kpt
     122              : end type dmats_t
     123              : !!***
     124              : 
     125              : contains
     126              : !!***
     127              : 
     128              : !!****f* m_classify_bands/classify_bands
     129              : !! NAME
     130              : !! classify_bands
     131              : !!
     132              : !! FUNCTION
     133              : !!  This routine finds the irreducible representation associated to
     134              : !!  a set of degenerate bands at a given k-point and spin.
     135              : !!  The irreducible representation is obtained by rotating the set
     136              : !!  of degenerate wavefunctions using the symmetry operations in the little group of k.
     137              : !!  Two states are treated as degenerate if their energy differs by less than EDIFF_TOL.
     138              : !!
     139              : !! INPUTS
     140              : !!  Wfd(wfd_t)= structure gathering information on wave functions
     141              : !!  ik_ibz=The index of the k-point in the IBZ.
     142              : !!  spin=The spin index.
     143              : !!  ngfft(18)=Info on the FFT mesh to be used for evaluting u(r) and the rotated u(R^{1}(r-t)).
     144              : !!    ngfft must be compatible with the symmetries of the crystal and can differ from Wfd%ngfft.
     145              : !!    wfd_change_ngfft is called if ANY(Wfd%ngfft(1:3) =/ ngfft).
     146              : !!  Cryst<crystal_t>=Type gathering info on the crystal structure.
     147              : !!  ebands<ebands_t>=Datatype with electronic energies.
     148              : !!  Pawtab(ntypat*usepaw) <type(pawtab_type)>=paw tabulated starting data
     149              : !!  Pawrad(ntypat*usepaw)<type(pawrad_type)>=paw radial mesh and related data.
     150              : !!  Pawang <type(pawang_type)>=paw angular mesh and related data
     151              : !!  Psps<pseudopotential_type>
     152              : !!    %indlmn(6,lmnmax,ntypat)=array giving l,m,n,lm,ln,spin for i=lmn (for each atom type)
     153              : !! Dtfil<datafiles_type>=variables related to files
     154              : !!    %unpaw
     155              : !! tolsym=Tolerance for the symmetries (input variable)
     156              : !!  [EDIFF_TOL]= tolerance on the energy difference of two states (if not specified is set to 0.005 eV)
     157              : !!
     158              : !! OUTPUT
     159              : !!  BSym<Bands_Symmetries>=structure containing info on the little group of the k-point as well
     160              : !!    as the character of the representation associated to each set of degenerate states
     161              : !!  if BSym%isymmorphic the symmetry analysis cannot be performed, usually it means that
     162              : !!   k is at zone border and there are non-symmorphic translations (see Notes)
     163              : !!
     164              : !! NOTES
     165              : !! * Let M(R_t) the irreducible representation associated to the space group symmetry (R_t).
     166              : !! * By convention M(R_t) multiplies wave functions as a row vector:
     167              : !!
     168              : !!    $ R_t \psi_a(r) = \psi_a (R^{-1}(r-\tau)) = \sum_b M(R_t)_{ba} \psi_b $
     169              : !!
     170              : !!   Therefore, if R_t belongs to the little group of k (i.e. Sk=k+G0), one obtains:
     171              : !!
     172              : !!    $ M_ab(R_t) = e^{-i(k+G0).\tau} \int e^{iG0.r} u_{ak}(r)^* u_{bk}(R^{-1}(r-\tau)) \,dr $.
     173              : !!
     174              : !! * The irreducible representation of the small _point_ group of k, M_ab(R), suffices to
     175              : !!   classify the degenerate eigenstates provided that particular conditions are fulfilled
     176              : !!   (see limitations below). The matrix is indeed given by:
     177              : !!
     178              : !!    $ M_ab(R) = e^{+ik.\tau} M_ab(R_t) = e^{-iG0.\tau} \int e^{iG0.r} u_{ak}(r)^* u_{bk}(R^{-1}(r-\tau))\,dr $
     179              : !!
     180              : !!   The phase factor outside the integral should be zero since symmetry analysis at border zone in non-symmorphic
     181              : !!   space groups is not available. Anyway it is included in our expressions for the sake of consistency.
     182              : !!
     183              : !! * For PAW there is an additional onsite terms involving <phi_i|phi_j(R^{-1}(r-\tau)> and
     184              : !!   the pseudized version that can be  evaluated using the rotation matrix for
     185              : !!    real spherical harmonis, zarot(mp,m,l,R). $ Y_{lm}(Rr)= \sum_{m'} zarot(m',m,ll,R) Y_{lm'}(r) $
     186              : !!
     187              : !!    $ M^{onsite}_ab(R_t) = sum_{c ij} <\tpsi_a| p_i^c>  <p_j^{c'}|\tpsi_b\> \times
     188              : !!       [ <\phi_i^c|\phi_j^{c'}> - <\tphi_i^c|\tphi_j^{c'}> ]. $
     189              : !!
     190              : !!    $ [ <\phi_i^c|\phi_j^{c'}> - <\tphi_i^c|\tphi_j^{c'}> ] = s_{ij} D_{\mi\mj}^\lj(R^{-1}) $
     191              : !!
     192              : !!   where c' is the rotated atom i.e c' = R^{-1}( c-\tau) and D is the rotation matrix for
     193              : !!   real spherical harmonics.
     194              : !!
     195              : !!   Remember that zarot(m',m,l,R)=zarot(m,m',l,R^{-1})
     196              : !!   and $ Y^l_m(ISG) = sum_{m'} D_{m'm}(S) Y_{m'}^l(G) (-i)^l $
     197              : !!       $ D_{m'm}^l (R) = D_{m,m'}^l (R^{-1}) $
     198              : !!
     199              : !! * LIMITATIONS: The method does not work if k is at zone border and the little group of k
     200              : !!                contains a non-symmorphic fractional translation.
     201              : !!
     202              : !! SOURCE
     203              : 
     204            0 : subroutine classify_bands(Wfd,use_paw_aeur,first_band,last_band,ik_ibz,spin,ngfftf,&
     205            0 :                           Cryst,ebands,Pawtab,Pawrad,Pawang,Psps,tolsym,BSym,&
     206              :                           EDIFF_TOL) ! optional
     207              : 
     208              : !Arguments ------------------------------------
     209              : !scalars
     210              :  integer,intent(in) :: ik_ibz,spin,first_band,last_band
     211              :  real(dp),intent(in) :: tolsym
     212              :  real(dp),intent(in),optional :: EDIFF_TOL
     213              :  logical,intent(in) :: use_paw_aeur
     214              :  type(crystal_t),intent(in) :: Cryst
     215              :  type(pawang_type),intent(in) :: Pawang
     216              :  type(pseudopotential_type),intent(in) :: Psps
     217              :  class(wfd_t),intent(inout) :: Wfd
     218              :  type(ebands_t),target,intent(in) :: ebands
     219              :  type(esymm_t),intent(out) :: BSym
     220              : !arrays
     221              :  integer,intent(in) :: ngfftf(18)
     222              :  type(pawtab_type),intent(in) :: Pawtab(Cryst%ntypat*Wfd%usepaw)
     223              :  type(Pawrad_type),intent(inout) :: Pawrad(Cryst%ntypat*Wfd%usepaw)
     224              : 
     225              : !Local variables-------------------------------
     226              : !scalars
     227              :  integer,parameter :: nspinor1=1
     228              :  integer :: dim_degs,ib1,ib2,ib_stop,ib_start,iclass,idg,sym_idx
     229              :  integer :: ir,isym,isym_class,tr_isym,jb1,jb2
     230              :  integer :: nr1,nr2,nr3,nsym_class,nfft,cplex !,ifgd,nfgd,ifft_sph
     231              :  integer :: ii,jj,lmax
     232              :  integer :: optcut,optgr0,optgr1,optgr2,optrad
     233              :  real(dp) :: EDIFF_TOL_,arg,fft_fact
     234              :  complex(dp) :: exp_mikg0t,exp_ikg0t,cmat_ab
     235              :  logical :: iscompatibleFFT,found,only_trace
     236              :  character(len=500) :: msg
     237              : !arrays
     238            0 :  integer :: g0(3), toinv(Cryst%nsym), trial(3,3)
     239            0 :  integer,pointer :: Rm1_rmt(:)
     240            0 :  integer,target,allocatable :: irottb(:,:)
     241            0 :  integer,allocatable :: tmp_sym(:,:,:),l_size_atm(:)
     242              :  real(dp) :: kpt(3),kpg0(3),omat(2)
     243            0 :  real(dp),pointer :: ene_k(:), zarot(:,:,:,:)
     244            0 :  complex(dp),allocatable :: eig0r(:,:),tr_emig0r(:,:)
     245            0 :  complex(gwp),allocatable :: ur1(:),ur2(:),ur2_rot(:)
     246            0 :  type(pawcprj_type),allocatable :: Cprj_b1(:,:),Cprj_b2(:,:),Cprj_b2rot(:,:)
     247            0 :  type(Pawfgrtab_type),allocatable :: Pawfgrtab(:)
     248            0 :  type(paw_pwaves_lmn_t),allocatable :: Paw_onsite(:)
     249              : ! *************************************************************************
     250              : 
     251              :  ! Consistency check on input.
     252            0 :  ABI_CHECK(Wfd%nspinor == 1, 'nspinor/=1 not coded')
     253              : 
     254              :  ! By default all bands are included
     255              :  !first_band=1; last_band=Wfd%nband(ik_ibz,spin)
     256            0 :  ABI_CHECK(first_band == 1, "first_band/=1 not coded")
     257            0 :  ABI_CHECK(last_band <= Wfd%nband(ik_ibz,spin), "last_band cannot be > nband_k")
     258              : 
     259            0 :  EDIFF_TOL_= 0.005/Ha_eV; if (PRESENT(EDIFF_TOL)) EDIFF_TOL_=ABS(EDIFF_TOL)
     260              : 
     261            0 :  call wfd%change_ngfft(Cryst,Psps,ngfftf)
     262              : 
     263              :  ! Get index of the rotated FFT points ===
     264              :  ! FFT mesh in real space _must_ be compatible with symmetries.
     265            0 :  nr1 = Wfd%ngfft(1)
     266            0 :  nr2 = Wfd%ngfft(2)
     267            0 :  nr3 = Wfd%ngfft(3)
     268            0 :  nfft = Wfd%nfft ! No FFT parallelism
     269              : 
     270            0 :  ABI_MALLOC(irottb,(nfft,Cryst%nsym))
     271            0 :  call rotate_FFT_mesh(Cryst%nsym,Cryst%symrel,Cryst%tnons,Wfd%ngfft,irottb,iscompatibleFFT)
     272              : 
     273            0 :  if (.not.iscompatibleFFT) then
     274              :    write(msg,'(3a)')&
     275            0 :     ' For symmetry analysis, the real space FFT mesh must be compatible with the symmetries of the space group',ch10,&
     276            0 :     ' classify_bands will return. Action: change the input variable ngfftf '
     277            0 :    ABI_WARNING(msg)
     278            0 :    Bsym%err_status=1
     279            0 :    Bsym%err_msg= msg
     280            0 :    RETURN
     281              :  end if
     282              : 
     283              :  ! only_trace=if .TRUE. only the trace of a single matrix per class is calculated (standard procedure if
     284              :  ! only the symmetry of bands is required). If .FALSE. all the matrices for each irreducible representation
     285              :  ! are calculated and stored in BSym
     286            0 :  only_trace=.FALSE.
     287              :  !
     288              :  ! ==========================================
     289              :  ! ==== Analyse k-point symmetries first ====
     290              :  ! ==========================================
     291              :  ! The analysis is done here so that we already know if there is a problem.
     292            0 :  kpt = Wfd%kibz(:,ik_ibz)
     293              :  !
     294              :  !----Initialize the Bsym structure for this k-point and spin----!
     295              :  ! NOTE that all the degenerate states should be included! No check is done.
     296              : 
     297            0 :  ene_k => ebands%eig(first_band:,ik_ibz,spin) ! Select a slice of eigenvalues
     298              : 
     299            0 :  call Bsym%init(kpt, Cryst, only_trace, Wfd%nspinor, first_band, last_band, EDIFF_TOL_, ene_k, tolsym)
     300              :  !Bsym%degs_bounds = Bsym%degs_bounds + (first_band -1)
     301              : 
     302            0 :  if (Bsym%err_status /= 0) then
     303            0 :    write(msg,'(a,i0,a)')" esymm_init returned err_status= ",Bsym%err_status," Band classifications cannot be performed."
     304            0 :    ABI_WARNING(msg)
     305            0 :    RETURN
     306              :  end if
     307              : 
     308            0 :  do ii=1,Cryst%nsym
     309            0 :    call mati3inv(Cryst%symrel(:,:,ii),trial)
     310            0 :    trial=transpose(trial)
     311            0 :    found=.FALSE.
     312            0 :    do jj=1,Cryst%nsym
     313            0 :      if (ALL(trial==Cryst%symrel(:,:,jj))) then
     314            0 :        toinv(ii)=jj
     315              :        !toinv(jj)=ii
     316              :        found=.TRUE.; EXIT
     317              :      end if
     318              :    end do
     319            0 :    if (.not.found) then
     320            0 :      ABI_ERROR("inverse not found! ")
     321              :    end if
     322              :  end do
     323              : 
     324            0 :  nullify(zarot)
     325              : 
     326            0 :  if (Wfd%usepaw==1) then ! Allocate cprj_k and cprj_krot to store a set of bands for a single (K,SPIN).
     327            0 :    ABI_MALLOC(Cprj_b1   ,(Cryst%natom,Wfd%nspinor))
     328            0 :    call pawcprj_alloc(Cprj_b1,   0,Wfd%nlmn_atm)
     329            0 :    ABI_MALLOC(Cprj_b2   ,(Cryst%natom,Wfd%nspinor))
     330            0 :    call pawcprj_alloc(Cprj_b2,   0,Wfd%nlmn_atm)
     331            0 :    ABI_MALLOC(Cprj_b2rot,(Cryst%natom,Wfd%nspinor))
     332            0 :    call pawcprj_alloc(Cprj_b2rot,0,Wfd%nlmn_atm)
     333              : 
     334              :    !zarot => Pawang%zarot
     335            0 :    lmax = Pawang%l_max-1
     336            0 :    ABI_MALLOC(zarot,(2*lmax+1,2*lmax+1,lmax+1,Cryst%nsym))
     337            0 :    zarot = Pawang%zarot
     338              : 
     339            0 :    ABI_MALLOC(tmp_sym,(3,3,Cryst%nsym))
     340            0 :    do isym=1,Cryst%nsym
     341            0 :      tmp_sym(:,:,isym) = Cryst%symrel(:,:,isym)
     342              :      !tmp_sym(:,:,isym) = Cryst%symrel(:,:,toinv(isym))
     343              :      !tmp_sym(:,:,isym) = transpose(Cryst%symrel(:,:,isym))
     344              :      !tmp_sym(:,:,isym) = Cryst%symrec(:,:,isym)
     345              :      !tmp_sym(:,:,isym) = TRANSPOSE(Cryst%symrec(:,:,isym))
     346              :    end do
     347              :    !% call setsym_ylm(Cryst%rprimd,lmax,Cryst%nsym,3,Cryst%gprimd,tmp_sym,zarot)
     348              :    !call setsym_ylm(Cryst%gprimd,lmax,Cryst%nsym,1,Cryst%rprimd,tmp_sym,zarot)
     349            0 :    ABI_FREE(tmp_sym)
     350            0 :    zarot = Pawang%zarot
     351              : 
     352            0 :    cplex=1
     353            0 :    call pawtab_get_lsize(Pawtab,l_size_atm,Cryst%natom,Cryst%typat)
     354            0 :    ABI_MALLOC(Pawfgrtab,(Cryst%natom))
     355            0 :    call pawfgrtab_init(Pawfgrtab,cplex,l_size_atm,Wfd%nspden,Cryst%typat)
     356            0 :    ABI_FREE(l_size_atm)
     357              : 
     358            0 :    optcut=1                     ! use rpaw to construct local_pawfgrtab
     359            0 :    optgr0=0; optgr1=0; optgr2=0 ! dont need gY terms locally
     360            0 :    optrad=1                     ! do store r-R
     361              : 
     362              :    call nhatgrid(Cryst%atindx1,Cryst%gmet,Cryst%natom,Cryst%natom,Cryst%nattyp,Wfd%ngfft,Cryst%ntypat,&
     363            0 :     optcut,optgr0,optgr1,optgr2,optrad,Pawfgrtab,pawtab,Cryst%rprimd,Cryst%typat,Cryst%ucvol,Cryst%xred)
     364              : 
     365              :    !call pawfgrtab_print(Pawfgrtab,unit=std_out,Wfd%prtvol=10)
     366              : 
     367            0 :    ABI_MALLOC(Paw_onsite,(Cryst%natom))
     368              : 
     369            0 :    if (use_paw_aeur) then
     370            0 :      ABI_WARNING("Using AE wavefunction for rotation in real space!")
     371              :      call paw_pwaves_lmn_init(Paw_onsite,Cryst%natom,Cryst%natom,Cryst%ntypat,&
     372            0 :                              Cryst%rprimd,Cryst%xcart,Pawtab,Pawrad,Pawfgrtab)
     373              :    end if
     374              :  end if
     375              : 
     376              :  ! ===============================================
     377              :  ! ==== Calculate the representation matrices ====
     378              :  ! ===============================================
     379            0 :  fft_fact=one/nfft
     380            0 :  ABI_MALLOC(ur1, (nfft))
     381            0 :  ABI_MALLOC(ur2, (nfft))
     382            0 :  ABI_MALLOC(ur2_rot, (nfft))
     383              : 
     384              :  ! Precalculate eig0r = e^{iG0.r} on the FFT mesh.
     385            0 :  ABI_MALLOC(eig0r, (nfft, Bsym%nsym_gk))
     386              : 
     387            0 :  do isym=1,Bsym%nsym_gk
     388            0 :    g0 = Bsym%g0(:,isym)
     389            0 :    call calc_ceigr(g0,nfft,nspinor1,Wfd%ngfft,eig0r(:,isym))
     390              :  end do
     391              : 
     392            0 :  if (Bsym%can_use_tr) then
     393            0 :    ABI_MALLOC(tr_emig0r,(nfft,Bsym%nsym_trgk))
     394            0 :    do isym=1,Bsym%nsym_trgk
     395            0 :      g0=Bsym%tr_g0(:,isym)
     396            0 :      call calc_ceigr(-g0,nfft,nspinor1,Wfd%ngfft,tr_emig0r(:,isym))
     397              :    end do
     398              :  end if
     399              : 
     400              :  ! Loop over the set of degenerate states.
     401            0 :  do idg=1,Bsym%ndegs
     402            0 :    ib_start = Bsym%degs_bounds(1,idg)
     403            0 :    ib_stop  = Bsym%degs_bounds(2,idg)
     404            0 :    dim_degs = Bsym%degs_dim(idg)
     405              : 
     406            0 :    do ib1=ib_start,ib_stop ! First band index in the degenerate set.
     407            0 :      jb1=ib1-ib_start+1
     408              : 
     409              :      ! debugging: use AE wave on dense FFT mesh.
     410            0 :      if (Wfd%usepaw==1..and.use_paw_aeur) then
     411            0 :        call wfd%paw_get_aeur(ib1,ik_ibz,spin,Cryst,Paw_onsite,Psps,Pawtab,Pawfgrtab,ur1)
     412              :      else
     413            0 :        call wfd%get_ur(ib1,ik_ibz,spin,ur1)
     414            0 :        if (Wfd%usepaw==1) then
     415            0 :          call wfd%ug2cprj(ib1,ik_ibz,spin,1,0,Cryst%natom,Cryst,Cprj_b1,sorted=.FALSE.)
     416              :        end if
     417              :      end if
     418              : 
     419            0 :      do ib2=ib_start,ib_stop ! Second band index in the degenerate set.
     420            0 :        if (Bsym%only_trace.and.ib1/=ib2) CYCLE ! Only the diagonal is needed.
     421              : 
     422            0 :        if (ib2==ib1) then
     423            0 :          call xcopy(nfft,ur1,1,ur2,1)
     424            0 :          if (Wfd%usepaw==1) call pawcprj_copy(Cprj_b1,Cprj_b2)
     425              :        else
     426              :          ! debugging: use AE wave on dense FFT mesh.
     427            0 :          if (Wfd%usepaw==1.and.use_paw_aeur) then
     428            0 :            call wfd%paw_get_aeur(ib2,ik_ibz,spin,Cryst,Paw_onsite,Psps,Pawtab,Pawfgrtab,ur2)
     429              :          else
     430            0 :            call wfd%get_ur(ib2,ik_ibz,spin,ur2)
     431            0 :            if (Wfd%usepaw==1) then
     432            0 :              call wfd%ug2cprj(ib2,ik_ibz,spin,1,0,Cryst%natom,Cryst,Cprj_b2,sorted=.FALSE.)
     433              :            end if
     434              :          end if
     435              :        end if
     436              : 
     437              :        ! ===================================================
     438              :        ! ==== Loop over the classes of the little group ====
     439              :        ! ===================================================
     440            0 :        sym_idx=0
     441            0 :        do iclass=1,Bsym%nclass
     442            0 :          nsym_class = Bsym%nelements(iclass)
     443              : 
     444            0 :          do isym_class=1,nsym_class ! Loop over elements in each class.
     445            0 :            sym_idx = sym_idx+1
     446            0 :            if (Bsym%only_trace.and.isym_class/=1) CYCLE ! Do it once if only the character is required.
     447              : 
     448            0 :            isym = Bsym%sgk2symrec(sym_idx)
     449            0 :            Rm1_rmt => irottb(:,isym)
     450              : 
     451              :            ! Classify states according to the irreps of the little group of k.
     452            0 :            kpg0= kpt + Bsym%g0(:,sym_idx)
     453            0 :            arg=-two_pi * DOT_PRODUCT(kpg0,Cryst%tnons(:,isym))
     454              : 
     455            0 :            if (ABS(arg) > tol6) then
     456            0 :              exp_mikg0t = DCMPLX(DCOS(arg),DSIN(arg))
     457              :            else
     458              :              exp_mikg0t = cone
     459              :            end if
     460              : 
     461              :            !if (Wfd%usepaw==1) then
     462              :            !end if
     463              :            !
     464              :            ! Rotate the right wave function and apply the phase ===
     465              :            ! Note that the k-point is the same within a lattice vector.
     466            0 :            do ir=1,nfft
     467            0 :              ur2_rot(ir)=ur2(Rm1_rmt(ir))*eig0r(ir,sym_idx)
     468              :            end do
     469              : 
     470              :            ! The matrix element on the FFT mesh.
     471            0 :            cmat_ab = xdotc(nfft,ur1,1,ur2_rot,1)*fft_fact*exp_mikg0t
     472              : 
     473            0 :            if (Wfd%usepaw==1.and..not.use_paw_aeur) then ! Add the on-site contribution.
     474            0 :              call rotate_cprj(kpt,isym,Wfd%nspinor,1,Cryst%natom,Cryst%nsym,Cryst%typat,Cryst%indsym,Cprj_b2,Cprj_b2rot)
     475              : 
     476              :              omat = paw_phirotphj(Wfd%nspinor,Cryst%natom,Cryst%typat,&
     477            0 :                zarot(:,:,:,isym),Pawtab,Psps,Cprj_b1,Cprj_b2rot)
     478              : 
     479            0 :              cmat_ab = cmat_ab + DCMPLX(omat(1),omat(2)) !* exp_mikg0t
     480              :            end if
     481              : 
     482            0 :            jb2 = ib2 - ib_start+1
     483            0 :            Bsym%Calc_irreps(idg)%mat(jb1,jb2,sym_idx)=cmat_ab
     484              : 
     485              :          end do !isym_class
     486              :        end do !iclass
     487              :        !
     488              :        ! =========================================================
     489              :        ! ==== Loop over the symmetries such that -Sk = k + G0 ====
     490              :        ! =========================================================
     491              :        ! <-k,a| S |k b>  = e^{i(k+G0).t} \int e^{-ig0.r} u_a u_b(R^{1}(r-t))
     492            0 :        if (Bsym%can_use_tr) then
     493            0 :          do tr_isym=1,Bsym%nsym_trgk
     494              : 
     495            0 :            isym = Bsym%tr_sgk2symrec(tr_isym)
     496            0 :            Rm1_rmt => irottb(:,isym)
     497              : 
     498            0 :            kpg0= kpt + Bsym%tr_g0(:,tr_isym)
     499            0 :            arg= two_pi * DOT_PRODUCT(kpg0,Cryst%tnons(:,isym))
     500              : 
     501            0 :            if (ABS(arg) > tol6) then
     502            0 :              exp_ikg0t=DCMPLX(DCOS(arg),DSIN(arg))
     503              :            else
     504              :              exp_ikg0t=cone
     505              :            end if
     506              : 
     507              :            ! Rotate the right wave function and apply the phase
     508              :            ! Note that the k-point is the same within a lattice vector.
     509            0 :            do ir=1,nfft
     510            0 :              ur2_rot(ir)=ur2(Rm1_rmt(ir)) * tr_emig0r(ir,tr_isym)
     511              :            end do
     512              : 
     513              :            ! The matrix element on the FFT mesh.
     514            0 :            cmat_ab = xdotu(nfft,ur1,1,ur2_rot,1)*fft_fact*exp_ikg0t
     515              : 
     516            0 :            if (Wfd%usepaw==1.and..not.use_paw_aeur) then ! Add the on-site contribution. ! TODO rechek this part.
     517            0 :                call rotate_cprj(kpt,isym,Wfd%nspinor,1,Cryst%natom,Cryst%nsym,Cryst%typat,Cryst%indsym,Cprj_b2,Cprj_b2rot)
     518              :                omat = paw_phirotphj(Wfd%nspinor,Cryst%natom,Cryst%typat,&
     519            0 :                  zarot(:,:,:,isym),Pawtab,Psps,Cprj_b1,Cprj_b2rot,conjg_left=.TRUE.)
     520            0 :              cmat_ab = cmat_ab + DCMPLX(omat(1),omat(2)) !* exp_ikg0t
     521              :            end if
     522              : 
     523            0 :            jb2 = ib2 - ib_start+1
     524            0 :            Bsym%trCalc_irreps(idg)%mat(jb1,jb2,tr_isym)=cmat_ab
     525              :          end do ! tr_isym
     526              :        end if
     527              : 
     528              :      end do !ib2
     529              :    end do !ib1
     530              : 
     531              :    ! Calculate the trace for each class.
     532            0 :    if (Bsym%only_trace) then ! TODO this is valid if only trace.
     533            0 :      ABI_ERROR("Have to reconstruct missing traces")
     534              :    else
     535            0 :      do isym=1,Bsym%nsym_gk
     536            0 :        Bsym%Calc_irreps(idg)%trace(isym) = get_trace( Bsym%Calc_irreps(idg)%mat(:,:,isym) )
     537              :      end do
     538            0 :      if (Bsym%can_use_tr) then
     539            0 :        do tr_isym=1,Bsym%nsym_trgk
     540            0 :          Bsym%trCalc_irreps(idg)%trace(tr_isym) = get_trace( Bsym%trCalc_irreps(idg)%mat(:,:,tr_isym) )
     541              :        end do
     542              :      end if
     543              :    end if
     544              : 
     545              :  end do ! idg
     546              : 
     547            0 :  call Bsym%finalize(Wfd%prtvol)
     548            0 :  call Bsym%print([std_out, ab_out], prtvol=Wfd%prtvol)
     549              : 
     550              :  ! Free memory
     551            0 :  ABI_FREE(irottb)
     552            0 :  ABI_FREE(ur1)
     553            0 :  ABI_FREE(ur2)
     554            0 :  ABI_FREE(ur2_rot)
     555            0 :  ABI_FREE(eig0r)
     556            0 :  ABI_SFREE(tr_emig0r)
     557              : 
     558            0 :  if (Wfd%usepaw==1) then
     559            0 :    call pawcprj_free(Cprj_b1)
     560            0 :    ABI_FREE(Cprj_b1)
     561            0 :    call pawcprj_free(Cprj_b2)
     562            0 :    ABI_FREE(Cprj_b2)
     563            0 :    call pawcprj_free(Cprj_b2rot)
     564            0 :    ABI_FREE(Cprj_b2rot)
     565            0 :    ABI_FREE(zarot)
     566            0 :    call pawfgrtab_free(Pawfgrtab)
     567            0 :    ABI_FREE(Pawfgrtab)
     568            0 :    call paw_pwaves_lmn_free(Paw_onsite)
     569            0 :    ABI_FREE(Paw_onsite)
     570              :  end if
     571              : 
     572            0 : end subroutine classify_bands
     573              : !!***
     574              : 
     575              : !----------------------------------------------------------------------
     576              : 
     577              : !!****f* m_classify_bands/rotate_cprj
     578              : !! NAME
     579              : !! rotate_cprj
     580              : !!
     581              : !! FUNCTION
     582              : !!  Rotate cprj matrix elements by applying the symmetry operation of index isym
     583              : !!  that preserves the given k-point within a reciprocal lattice vector.
     584              : !!
     585              : !! INPUTS
     586              : !! isym=index of the symmetry in the symrec arrays that preserves the given k-point within a reciprocal lattice vector
     587              : !! ntypat=number of types of atom.
     588              : !! natom=number of atoms.
     589              : !! Cryst<crystal_t>=Datatype gathering info on the unit cell.
     590              : !!   typat(natom)=type of each atom.
     591              : !! nbnds=number of bands for this k-point ans spin
     592              : !! Cprj_in(natom,nbnds)<type(pawcprj_type)>= projected input wave functions <Proj_i|Cnk>
     593              : !!  with all NL projectors at fixed k-point
     594              : !!
     595              : !! OUTPUT
     596              : !! Cprj_out(natom,nbnds) <type(pawcprj_type)>= projection of the smooth PAW wave function onto
     597              : !!  projectors centered on equivalent sites of the crystal (non restricted to be in the firs unit cell)
     598              : !!  The equivalent site is defined according to the symmetry operation isym. Thus Cprj_out contains
     599              : !!
     600              : !!  Cprj_out(at,b)=<p_j^{R^{-1}(L_{at}-\tau)} | \tpsi_b> if  R is the isym operation  with fractional translation \tau
     601              : !!  L_{at} is the position of the initial atom inside the first unit cell
     602              : !!  Note that atom a might be in a cell different from the initial one. No wrapping is done.
     603              : !!
     604              : !! SOURCE
     605              : 
     606            0 : subroutine rotate_cprj(kpoint,isym,nspinor,nbnds,natom,nsym,typat,indsym,Cprj_in,Cprj_out)
     607              : 
     608              : !Arguments ------------------------------------
     609              : !scalars
     610              :  integer,intent(in) :: nbnds,nspinor,natom,isym,nsym
     611              : !arrays
     612              :  integer,intent(in) :: typat(natom),indsym(4,nsym,natom)
     613              :  real(dp),intent(in) :: kpoint(3)
     614              :  type(pawcprj_type),intent(in) :: Cprj_in(natom,nspinor*nbnds)
     615              :  type(pawcprj_type),intent(out) :: Cprj_out(natom,nspinor*nbnds)
     616              : 
     617              : !Local variables-------------------------------
     618              : !scalars
     619              :  integer :: iat,iband,itypat,iat_sym
     620              :  real(dp) :: kdotr0
     621              : !arrays
     622              :  integer :: r0(3)
     623              :  real(dp) :: phase_kr0(2)
     624              : ! *************************************************************************
     625              : 
     626            0 :  do iat=1,natom
     627            0 :    itypat=typat(iat)
     628              :    ! The index of the symmetric atom.
     629              :    ! R^{-1} (xred(:,iat)-tnons) = xred(:,iat_sym) + r0.
     630              :    ! phase_kr0 takes into account the case in which rotated atom is in another unit cell.
     631            0 :    iat_sym=indsym(4,isym,iat); r0=indsym(1:3,isym,iat)
     632              : 
     633            0 :    kdotr0 = two_pi*DOT_PRODUCT(kpoint,r0)
     634            0 :    phase_kr0(1) = DCOS(kdotr0)
     635            0 :    phase_kr0(2) = DSIN(kdotr0)
     636              : 
     637              :    !phase_kr0 = (/one,zero/)
     638              : 
     639            0 :    do iband=1,nspinor*nbnds
     640              :      Cprj_out(iat,iband)%cp(1,:)=  Cprj_in(iat_sym,iband)%cp(1,:)*phase_kr0(1) &
     641            0 :                                   -Cprj_in(iat_sym,iband)%cp(2,:)*phase_kr0(2)
     642              : 
     643              :      Cprj_out(iat,iband)%cp(2,:)=  Cprj_in(iat_sym,iband)%cp(1,:)*phase_kr0(2) &
     644            0 :                                   +Cprj_in(iat_sym,iband)%cp(2,:)*phase_kr0(1)
     645              :    end do
     646              :  end do ! iat
     647              : 
     648            0 : end subroutine rotate_cprj
     649              : !!***
     650              : 
     651              : !----------------------------------------------------------------------
     652              : 
     653              : !!****f* m_classify_bands/paw_phirotphj
     654              : !! NAME
     655              : !! paw_phirotphj
     656              : !!
     657              : !! FUNCTION
     658              : !!  This routine calculates
     659              : !!  <\tPsi_1|\tprj_i> <\tprj_j|\tPsi_2> [ <\phi_i|\phi_j(R^{-1}r> - <\tphi_i|\tphi_j(R^{-1}r> ]
     660              : !!
     661              : !! [ <\phi_i|\phi_j(R^{-1}r> - <\tphi_i|\tphi_j(R^{-1}r> ] = s_ij D_{mi,mi}^{li}(R)
     662              : !!
     663              : !! INPUTS
     664              : !! nspinor=Number of spinorial components.
     665              : !! natom=number of atoms
     666              : !! typat(natom)=type of eahc atom
     667              : !! zarot_isym
     668              : !! Pawtab(ntypat)<Pawtab_type>=PAW tabulated starting data
     669              : !! Psps<pseudopotential_type>=Info on pseudopotentials.
     670              : !! Cprj_b1(natom,nspinor)<type(pawcprj_type)>= projected input wave functions <Proj_i|Cnk>
     671              : !!  with all NL projectors at fixed k-point
     672              : !! Cprj_b2(natom,nspinor)<type(pawcprj_type)>= projected input wave functions <Proj_i|Cnk>
     673              : !!  with all NL projectors at fixed k-point
     674              : !! [conjg_left]=.TRUE if the complex conjugate of the left wavefunctions has to be taken. Defaults to .FALSE.
     675              : !!
     676              : !! OUTPUT
     677              : !!  omat(2)=The onsite matrix element.
     678              : !!
     679              : !! SOURCE
     680              : 
     681            0 : function paw_phirotphj(nspinor,natom,typat,zarot_isym,Pawtab,Psps,Cprj_b1,Cprj_b2,conjg_left) result(omat)
     682              : 
     683              : !Arguments ------------------------------------
     684              : !scalars
     685              :  integer,intent(in) :: nspinor,natom
     686              :  logical,optional,intent(in) :: conjg_left
     687              :  type(pseudopotential_type),intent(in) :: Psps
     688              : !arrays
     689              :  integer,intent(in) :: typat(natom)
     690              :  real(dp),intent(in) :: zarot_isym(:,:,:)
     691              :  real(dp) :: omat(2)
     692              :  type(Pawtab_type),intent(in) :: Pawtab(Psps%ntypat)
     693              :  type(pawcprj_type),intent(in) :: Cprj_b1(natom,nspinor),Cprj_b2(natom,nspinor)
     694              : 
     695              : !Local variables-------------------------------
     696              : !scalars
     697              :  integer :: iat,il,ilmn,ilpm,im,itypat,jl,jlmn,jlpm,jm,k0lmn,klmn,nlmn
     698              :  real(dp) :: dmimj,fij,im_p,re_p,sij
     699              :  logical :: do_conjg_left
     700              : ! *************************************************************************
     701              : 
     702            0 :  do_conjg_left = .FALSE.; if (PRESENT(conjg_left)) do_conjg_left = conjg_left
     703              : 
     704            0 :  if (nspinor/=1) then
     705            0 :    ABI_ERROR("nspinor/=1 not yet coded")
     706              :  end if
     707              : 
     708              :  ! === Rotate PAW projections ===
     709              :  ! * zarot_isym is the rotation matrix of real spherical harmonics associated to symrec(:,:,isym).
     710              :  ! * zarot_isym multiply harmonics as row vectors, we need R^{-1} but we read R and invert m,mp in the equation below
     711            0 :  omat=zero
     712              : 
     713            0 :  do iat=1,natom
     714            0 :    itypat=typat(iat)
     715            0 :    nlmn=Pawtab(itypat)%lmn_size
     716              : 
     717            0 :    do jlmn=1,nlmn
     718            0 :      k0lmn=jlmn*(jlmn-1)/2
     719            0 :      jl=Psps%indlmn(1,jlmn,itypat)
     720            0 :      jm=Psps%indlmn(2,jlmn,itypat)
     721            0 :      jlpm=1+jl+jm
     722              : 
     723            0 :      do ilmn=1,jlmn
     724            0 :        il=Psps%indlmn(1,ilmn,itypat)
     725            0 :        im=Psps%indlmn(2,ilmn,itypat)
     726            0 :        if (il/=jl.or.im/=jm) CYCLE ! Selection rule on l and m.
     727            0 :        ilpm=1+il+im
     728              : 
     729            0 :        klmn=k0lmn+ilmn
     730            0 :        sij=Pawtab(itypat)%sij(klmn) !; if (ABS(sij)<tol14) CYCLE
     731              : 
     732              :        ! Here we get the matrix associated to R^{-1}.
     733            0 :        dmimj=zarot_isym(ilpm,jlpm,jl+1)
     734              : 
     735            0 :        if (do_conjg_left) then  ! take the complex conjugate of the left cprj.
     736              :          re_p=  Cprj_b1(iat,1)%cp(1,ilmn) * Cprj_b2(iat,1)%cp(1,jlmn) &
     737              :                -Cprj_b1(iat,1)%cp(2,ilmn) * Cprj_b2(iat,1)%cp(2,jlmn) &
     738              :                +Cprj_b1(iat,1)%cp(1,jlmn) * Cprj_b2(iat,1)%cp(1,ilmn) &
     739            0 :                -Cprj_b1(iat,1)%cp(2,jlmn) * Cprj_b2(iat,1)%cp(2,ilmn)
     740              : 
     741              :          im_p=  Cprj_b1(iat,1)%cp(1,ilmn) * Cprj_b2(iat,1)%cp(2,jlmn) &
     742              :                +Cprj_b1(iat,1)%cp(2,ilmn) * Cprj_b2(iat,1)%cp(1,jlmn) &
     743              :                -Cprj_b1(iat,1)%cp(1,jlmn) * Cprj_b2(iat,1)%cp(2,ilmn) &
     744            0 :                -Cprj_b1(iat,1)%cp(2,jlmn) * Cprj_b2(iat,1)%cp(1,ilmn)
     745              :        else
     746              :          re_p=  Cprj_b1(iat,1)%cp(1,ilmn) * Cprj_b2(iat,1)%cp(1,jlmn) &
     747              :                +Cprj_b1(iat,1)%cp(2,ilmn) * Cprj_b2(iat,1)%cp(2,jlmn) &
     748              :                +Cprj_b1(iat,1)%cp(1,jlmn) * Cprj_b2(iat,1)%cp(1,ilmn) &
     749            0 :                +Cprj_b1(iat,1)%cp(2,jlmn) * Cprj_b2(iat,1)%cp(2,ilmn)
     750              : 
     751              :          im_p=  Cprj_b1(iat,1)%cp(1,ilmn) * Cprj_b2(iat,1)%cp(2,jlmn) &
     752              :                -Cprj_b1(iat,1)%cp(2,ilmn) * Cprj_b2(iat,1)%cp(1,jlmn) &
     753              :                +Cprj_b1(iat,1)%cp(1,jlmn) * Cprj_b2(iat,1)%cp(2,ilmn) &
     754            0 :                -Cprj_b1(iat,1)%cp(2,jlmn) * Cprj_b2(iat,1)%cp(1,ilmn)
     755              :        end if
     756              :        ! Accumulate the atom-centered contributions.
     757            0 :        fij = Pawtab(itypat)%dltij(klmn)/two
     758            0 :        omat(1)= omat(1) + fij*sij*re_p*dmimj
     759            0 :        omat(2)= omat(2) + fij*sij*im_p*dmimj
     760              : 
     761              :      end do !ilmn
     762              :    end do !jlmn
     763              :  end do !iat
     764              : 
     765            0 : end function paw_phirotphj
     766              : !!***
     767              : 
     768              : !----------------------------------------------------------------------
     769              : 
     770              : !!****f* m_classify_bands/dmats_init
     771              : !! NAME
     772              : !! dmats_init
     773              : !!
     774              : !! FUNCTION
     775              : !! Compute D_mn(S) = <psi_{mSk}| S | psi_{nk}> for all the k-points in the IBZ and the bands in brange_spin.
     776              : !!
     777              : !! INPUTS
     778              : !! wfk_path=Filename of the WFK file.
     779              : !!
     780              : !! NOTES
     781              : !!  Little-group membership and the umklapp vector G_0 associated to each symmetry
     782              : !!  are determined with the SAME k-point convention used by cgtk_rotate's own
     783              : !!  bookkeeping (see its docstring in m_cgtk.F90), namely:
     784              : !!
     785              : !!    k2 = T symrel(:,:,isym)^t k1 + G_0   (transpose of symrel, NOT symrec)
     786              : !!
     787              : !!  where T=+1/-1 without/with time reversal. This is also the convention produced
     788              : !!  by listkk (default, symrel-based) and consumed by cgtk_rotate elsewhere in the
     789              : !!  code (e.g. m_wfd.F90), so it is safe to reuse directly here.
     790              : !!
     791              : !!  However, cgtk_rotate cannot simply be called with S=isym to obtain D(S_isym):
     792              : !!  its actual G-sphere index map is cg2(G) = cg1(symrec(isym).(G+G_0)), with
     793              : !!  symrec(isym) = mati3inv(symrel(isym)) = symrel(isym)^{-t} applied FORWARD
     794              : !!  (no additional inversion). Deriving the Fourier-coefficient transform of
     795              : !!  psi(r) -> psi(symrel(isym)^{-1}(r-tau)) shows that the coefficient at the
     796              : !!  rotated G must instead be read at symrel(isym)^t . G. The two matrices,
     797              : !!  symrel(isym)^{-t} and symrel(isym)^t, coincide only when symrel(isym) is an
     798              : !!  involution (S^2 = E, e.g. the identity or spatial inversion). For any other
     799              : !!  operation (3-, 4-, 6-fold rotations, screw axes, glide planes, ...) calling
     800              : !!  cgtk_rotate(isym) therefore silently returns D_true(S_isym)^{-1} = D_true(S_isym^{-1})
     801              : !!  instead of D_true(S_isym).
     802              : !!
     803              : !!  This was confirmed empirically: with cgtk_rotate called on isym directly, the
     804              : !!  group-multiplication test in dmats_check (D(S_1 S_2) \propto D(S_1) D(S_2), see
     805              : !!  below) failed for essentially every triple involving a non-involutory operation,
     806              : !!  while unitarity and the D(S^{-1})=D(S)^dagger self-consistency test still passed
     807              : !!  (an involution-blind bug: D_true(S)^{-1} is unitary and equals D_true(S)^{-1}
     808              : !!  trivially, so those two checks cannot detect it). Concretely, for a triple
     809              : !!  (S_1, S_2, S_3=S_1 S_2) with zero fractional translations, the stored matrices
     810              : !!  satisfied D(S_3) = D(S_1) D(S_2)^t rather than D(S_3) = D(S_1) D(S_2).
     811              : !!
     812              : !!  The fix is to call cgtk_rotate with isym_inv, the group-theoretic inverse of
     813              : !!  isym (found from the symrel multiplication table), while still filling the
     814              : !!  storage slot for isym: D_computed(isym_inv) = D_true(isym_inv^{-1}) = D_true(isym).
     815              : !!  isym_inv's own G_0 is recomputed with the formula above (using isym_inv instead
     816              : !!  of isym); no ad-hoc override of its fractional translation is needed, since
     817              : !!  cgtk_rotate is now called honestly for the operation it is actually asked to
     818              : !!  apply. See the inline comments in the k-point/symmetry loop below for the
     819              : !!  implementation.
     820              : !!
     821              : !! SOURCE
     822              : 
     823            0 : subroutine dmats_init(dmats, wfk_path, dtset, cryst, brange_spin, ngfft, pawtab, psps, comm)
     824              : 
     825              : !Arguments ------------------------------------
     826              : !scalars
     827              :  class(dmats_t),intent(out) :: dmats
     828              :  character(len=*),intent(in) :: wfk_path
     829              :  type(dataset_type),target,intent(in) :: dtset
     830              :  class(crystal_t),target,intent(in) :: cryst
     831              :  integer,intent(in) :: brange_spin(2, dtset%nsppol), ngfft(18)
     832              :  integer,intent(in) :: comm
     833              :  type(pseudopotential_type),intent(in) :: psps
     834              :  type(pawtab_type),intent(in) :: pawtab(psps%ntypat*psps%usepaw)
     835              : 
     836              : !Local variables-------------------------------
     837              : !scalars
     838              :  integer,parameter :: iflag1 = 1, me_g0 = 1, ndat1 = 1
     839              :  integer :: spin, nsppol, nsym, nb, nkibz, mband, ik_ibz, isym, isym_inv, itime, bstart, trev_k
     840              :  logical,parameter :: DEBUG_DUMP_SPINROT = .False.
     841              :  real(dp) :: spinrot_dbg(4)
     842              :  integer :: ib1, ib2, band1, band2, n1, n2, n3, n4, n5, n6, nfft, nspinor, mpw, my_mpw, ii, ipw
     843              :  integer :: nprocs, me, itot, ierr
     844              :  logical :: is_little_group
     845              :  real(dp),parameter :: xnorm1 = one
     846              :  real(dp) :: e_b1, e_b2, cpu, wall, gflops, tsign
     847            0 :  type(wfd_t) :: wfd
     848            0 :  type(hdr_type) :: hdr
     849            0 :  type(krank_t) :: my_krank
     850              :  ! Non-little-group (star-crossing) D-matrix computation.
     851              :  integer :: ik_ibz_p, isym_p, trev_p, isym_inv_S, npw_kp
     852              :  integer :: g0_ket(3)
     853            0 :  integer,allocatable :: kg_kp(:,:)
     854            0 :  real(dp),allocatable :: cg_bra(:,:,:), cg_ket(:,:), cg_work_p(:,:)
     855              : !arrays
     856              :  integer :: g0_k(3), g0_k_inv(3), gmax(3), my_gmax(3), work_ngfft(18), units(2)
     857              :  integer :: indkk_p(6,1)
     858            0 :  integer,allocatable :: nband(:,:), wfd_istwfk(:)
     859              :  real(dp) :: kk_ibz(3), kk_sk(3), kk_sk_inv(3), dot(2)
     860            0 :  real(dp),allocatable :: cg_ib(:,:,:), cg_work(:,:), work(:,:,:,:), cg2_sk(:,:)
     861              :  complex(dp) :: cval
     862            0 :  complex(dp),allocatable :: cmat(:,:)
     863            0 :  logical,allocatable :: bks_mask(:,:,:),keep_ur(:,:,:)
     864              : !----------------------------------------------------------------------
     865              : 
     866            0 :  units = [std_out, ab_out]
     867            0 :  call cwtime(cpu, wall, gflops, "start")
     868            0 :  call wrtout(units, sjoin(" Computing dmats with symsigma_de", ftoa(dtset%symsigma_de * Ha_meV), " meV"))
     869              : 
     870            0 :  ABI_CHECK_IEQ(dtset%usepaw, 0, "PAW not coded!")
     871              :  ! NB: the D-matrix construction below is nspinor-agnostic: it is built entirely on top of
     872              :  ! cgtk_rotate (which already implements the SU(2) spin rotation + time-reversal spin-exchange
     873              :  ! for nspinor=2, see m_cgtk.F90) and cg_zdotc (a plain length-parametrized dot product that,
     874              :  ! called with n=npw_k*nspinor, sums over the spinor components as well as the G-vectors).
     875              :  ! So nspinor=2 (SOC) is supported here. The Kramers/Theta^2 VALIDATION diagnostics in
     876              :  ! dmats_check_one_k (only reachable via dmats%check/dmats%check_star, i.e. wfk_task "classify",
     877              :  ! never via gstore_symmetrize) are a separate matter and remain scalar-only for now: they encode
     878              :  ! an analytic phase formula derived assuming Theta^2=+1, which flips to Theta^2=-1 for spinors
     879              :  ! and has not been re-derived/verified yet -- see the explicit nspinor==1 guard added there.
     880              : 
     881              :  ! Read KS energies from the WFK file.
     882            0 :  dmats%ks_ebands = ebands_from_file(wfk_path, comm)
     883            0 :  dmats%cryst => cryst
     884            0 :  dmats%dtset => dtset
     885              : 
     886            0 :  nsppol = dmats%ks_ebands%nsppol; nsym = cryst%nsym; nkibz = dmats%ks_ebands%nkpt
     887            0 :  nprocs = xmpi_comm_size(comm); me = xmpi_comm_rank(comm)
     888              : 
     889              :  if (DEBUG_DUMP_SPINROT .and. xmpi_comm_rank(comm) == 0) then
     890              :    open(unit=792, file="spinrot_debug.csv", status="replace", action="write")
     891              :    do isym=1,nsym
     892              :      call getspinrot(cryst%rprimd, spinrot_dbg, cryst%symrel(:,:,isym))
     893              :      write(792,'(i0,1x,4(es24.16,1x))') isym, spinrot_dbg
     894              :    end do
     895              :    close(792)
     896              :  end if
     897              : 
     898            0 :  ABI_MALLOC(dmats%brange_spin, (2, nsppol))
     899            0 :  dmats%brange_spin = brange_spin
     900              : 
     901              :  ! Compute multiplication table.
     902            0 :  ABI_MALLOC(dmats%multable, (4, nsym, nsym))
     903            0 :  ABI_MALLOC(dmats%toinv, (4, nsym))
     904              : 
     905              :  call sg_multable(nsym, cryst%symafm, cryst%symrel, ierr, &
     906            0 :                   tnons=cryst%tnons, multable=dmats%multable, toinv=dmats%toinv)
     907            0 :  ABI_CHECK_IEQ(ierr, 0, "sg_multable returned ierr != 0. See messages above.")
     908              : 
     909              :  ! Initialize the wave function descriptor.
     910            0 :  mband = maxval(brange_spin(2, :))
     911            0 :  ABI_MALLOC(nband, (nkibz, nsppol))
     912            0 :  ABI_MALLOC(bks_mask, (mband, nkibz, nsppol))
     913            0 :  ABI_MALLOC(keep_ur, (mband, nkibz, nsppol))
     914            0 :  nband = mband; bks_mask = .False.; keep_ur = .False.
     915              : 
     916              :  ! Every rank loads the FULL band range for EVERY IBZ k-point (not just the subset this
     917              :  ! rank "owns" for the D-matrix computation loop below). This is required by the
     918              :  ! non-little-group branch (see NOTES above): computing D_mn(S) for a general S needs the
     919              :  ! wavefunction at k'_ibz = S.k_ibz's own IBZ representative, which is not known until the
     920              :  ! symmetry loop runs and can be ANY IBZ point, not just the ones this rank would otherwise
     921              :  ! be assigned. The actual D-matrix COMPUTATION work (the ik_ibz loop below) is still
     922              :  ! MPI-distributed and xmpi_sum'd at the end -- only the wavefunction STORAGE is replicated.
     923            0 :  do spin=1,nsppol
     924            0 :    bks_mask(brange_spin(1,spin):brange_spin(2,spin), :, spin) = .True.
     925              :  end do
     926              : 
     927              :  ! Impose istwfk = 1 for all k-points.
     928              :  ! wfd_read_wfk will handle a possible conversion if WFK contains istwfk /= 1.
     929            0 :  ABI_MALLOC(wfd_istwfk, (nkibz))
     930            0 :  wfd_istwfk = 1
     931              : 
     932              :  call wfd%init(cryst, pawtab, psps, keep_ur, mband, nband, nkibz, nsppol, bks_mask,&
     933              :                dtset%nspden, dtset%nspinor, dtset%ecut, dtset%ecutsm, dtset%dilatmx, wfd_istwfk, dmats%ks_ebands%kptns, ngfft,&
     934            0 :                dtset%nloalg, dtset%prtvol, dtset%pawprtvol, comm)
     935              :  !call wfd%print([std_out], header="Wavefunctions for DMATS calculation")
     936              : 
     937            0 :  ABI_FREE(nband)
     938            0 :  ABI_FREE(keep_ur)
     939            0 :  ABI_FREE(wfd_istwfk)
     940            0 :  ABI_FREE(bks_mask)
     941              : 
     942              :  ! Read wavefunctions from WFK file.
     943            0 :  call wfd%read_wfk(wfk_path, iomode_from_fname(wfk_path), out_hdr=hdr)
     944              : 
     945              :  ! Energy cutoff must be the same else matrices are not unitary.
     946            0 :  call hdr%vs_dtset(dtset)
     947            0 :  ABI_CHECK(abs(dtset%ecut - hdr%ecut) < tol6, "Input ecut should be equal to the value used in the WFK file.")
     948            0 :  call hdr%free()
     949              : 
     950              :  ! krank_t used to map k' = S.k_ibz (any BZ point, not just IBZ ones) back to the IBZ,
     951              :  ! symrel^t convention, needed by the non-little-group D-matrix computation below.
     952            0 :  call my_krank%init(nkibz, dmats%ks_ebands%kptns)
     953              : 
     954              :  ! Compute max |G_i| to build the box.
     955            0 :  gmax = 0; mpw = 0
     956            0 :  do ik_ibz=1,nkibz
     957            0 :     if (.not. allocated(wfd%kdata(ik_ibz)%kg_k)) cycle
     958            0 :     associate (npw_k => wfd%npwarr(ik_ibz), kg_k => wfd%kdata(ik_ibz)%kg_k)
     959            0 :     mpw = max(mpw, npw_k)
     960            0 :     do ipw=1,npw_k
     961            0 :       do ii=1,3
     962            0 :         gmax(ii) = max(gmax(ii), abs(kg_k(ii,ipw)))
     963              :       end do
     964              :     end do
     965              :     end associate
     966              :  end do
     967            0 :  my_gmax = gmax; call xmpi_max(my_gmax, gmax, comm, ierr)
     968            0 :  my_mpw = mpw; call xmpi_max(my_mpw, mpw, comm, ierr)
     969              : 
     970              :  ! Init work_ngfft
     971            0 :  gmax = gmax + 4 ! FIXME: this is to account for umklapp
     972            0 :  gmax = 2*gmax + 1
     973            0 :  call ngfft_seq(work_ngfft, gmax)
     974              :  !write(std_out,*)"work_ngfft(1:3): ",work_ngfft(1:3)
     975            0 :  ABI_MALLOC(work, (2, work_ngfft(4), work_ngfft(5), work_ngfft(6)))
     976              : 
     977            0 :  n1 = work_ngfft(1); n2 = work_ngfft(2); n3 = work_ngfft(3); n4 = work_ngfft(4); n5 = work_ngfft(5); n6 = work_ngfft(6)
     978            0 :  nfft = n1 * n2 * n3
     979            0 :  nspinor = wfd%nspinor
     980              : 
     981              :  ! Allocate D matrices for each spin on each proc and fill with zeros as we will MPI sum at the end.
     982            0 :  ABI_MALLOC(dmats%for_spin, (nsppol))
     983            0 :  do spin=1,nsppol
     984            0 :    nb = brange_spin(2,spin) - brange_spin(1,spin) + 1
     985            0 :    ABI_CALLOC(dmats%for_spin(spin)%value, (nb, nb, nsym, 2, nkibz))
     986              :  end do
     987              : 
     988            0 :  do spin=1,nsppol
     989            0 :    bstart = brange_spin(1, spin)
     990            0 :    nb = brange_spin(2, spin) - brange_spin(1, spin) + 1
     991            0 :    ABI_MALLOC(cmat, (nb, nb))
     992              : 
     993              :    ! Loop over k-points in the IBZ.
     994            0 :    do ik_ibz=1,nkibz
     995            0 :      itot = ik_ibz + (spin - 1)*nkibz; if (mod(itot - 1, nprocs) /= me) cycle ! MPI parallelism.
     996              : 
     997              :      ! NB: istwf_k is always 1 here. See call to wfd%init.
     998            0 :      associate (npw_k => wfd%npwarr(ik_ibz), istwf_k => wfd%kdata(ik_ibz)%istwfk, kg_k => wfd%kdata(ik_ibz)%kg_k)
     999            0 :      kk_ibz = dmats%ks_ebands%kptns(:, ik_ibz)
    1000              : 
    1001              :      ! Copy wavefunctions for this k-point.
    1002            0 :      ABI_MALLOC(cg_work, (2, npw_k*nspinor))
    1003            0 :      ABI_MALLOC(cg_ib, (2, npw_k*nspinor, nb))
    1004            0 :      do ib1=1,nb
    1005            0 :        band1 = ib1 + bstart - 1
    1006            0 :        call wfd%copy_cg(band1, ik_ibz, spin, cg_ib(:,:,ib1))
    1007              :      end do
    1008              : 
    1009              :      ! Loop over time-reversal and spatial symmetries.
    1010              :      ! g0_k is built with the SAME k-point convention assumed by cgtk_rotate's
    1011              :      ! bookkeeping: k2 = T symrel(:,:,isym)^t k1 + g0 (transpose of symrel, not symrec).
    1012              :      ! cgtk_rotate is then called with isym_inv rather than isym: see the detailed
    1013              :      ! explanation of why this is required (cgtk_rotate(isym) actually returns
    1014              :      ! D_true(S_isym)^{-1}, invisibly for involutions) in the NOTES of this subroutine's
    1015              :      ! SOURCE header above.
    1016            0 :      do itime=1,2
    1017            0 :        tsign = merge(one, -one, itime == 1)
    1018            0 :        trev_k = itime - 1
    1019            0 :        do isym=1,cryst%nsym
    1020              :          ! Compute cmat(b,b')
    1021            0 :          cmat = zero
    1022              : 
    1023            0 :          kk_sk = tsign * matmul(transpose(real(cryst%symrel(:,:,isym), dp)), kk_ibz)
    1024            0 :          g0_k = nint(kk_ibz - kk_sk)
    1025            0 :          is_little_group = all(abs(kk_ibz - kk_sk - g0_k) < tol8)
    1026              : 
    1027            0 :          if (.not. is_little_group) then
    1028              :            ! Sk = k' /= k + G: genuinely compute D_mn(S) = <psi_m,k'|S|psi_n,k> from real
    1029              :            ! WFK data (this used to be an identity placeholder -- never correct, only
    1030              :            ! harmless because no caller ever read this slot; gstore_symmetrize and
    1031              :            ! dmats_check_one_k/dmats_check_star only ever query genuine little-group
    1032              :            ! slots). Two independent rotations are needed, expressed on a COMMON target
    1033              :            ! G-sphere built at the RAW (unreduced) k' = kk_sk:
    1034              :            !
    1035              :            ! (a) BRA |psi_m,k'>: reconstructed via kpts_map's own canonical (isym_p,
    1036              :            !     trev_p, g0_p) mapping k'_ibz -> k', calling cgtk_rotate with isym_p
    1037              :            !     DIRECTLY (no toinv step). This mirrors wfd_sym_ug_kg_npw's (m_wfd.F90)
    1038              :            !     own validated convention for "reconstruct THE wavefunction at a BZ
    1039              :            !     point" -- the same machinery gstore_compute already uses in production.
    1040              :            !     Any internally-consistent representative of |psi,k'> works here since
    1041              :            !     the bra is a STATE, not a specific operator's image (gauge-flexible).
    1042              :            !
    1043              :            ! (b) KET S|psi_n,k>: this DOES need the honest action of the SPECIFIC
    1044              :            !     operator S (not just some state), so the toinv workaround documented
    1045              :            !     above is required: call cgtk_rotate with X=toinv(1,isym), the SAME
    1046              :            !     tsign/trev_k as S. The g0 needed here is NOT zero in general (an
    1047              :            !     earlier version of this code assumed so and was wrong -- caught by a
    1048              :            !     temporary self-consistency check routing known little-group cases
    1049              :            !     through this same general formula and comparing against the existing,
    1050              :            !     validated little-group result: 4/208 disagreed by O(1), all at points
    1051              :            !     with a non-involutory isym AND a nonzero little-group umklapp). Root
    1052              :            !     cause: cgtk_rotate's documented k2=symrel(X)^t.kpt1+g0 "official"
    1053              :            !     bookkeeping formula does NOT, in general, track the k-point the CONTENT
    1054              :            !     is actually expressed at once X is swapped for toinv(X) -- that formula
    1055              :            !     only happens to hold for the little-group case because target=source
    1056              :            !     there, collapsing two genuinely different quantities into one. The
    1057              :            !     general, correct g0 (verified via the same self-check, 208/208 exact to
    1058              :            !     numerical noise): treat kk_sk (my desired physical target, built from
    1059              :            !     S's OWN forward action) the same way the little-group formula treats
    1060              :            !     kk_ibz -- i.e. g0 = nint(kk_sk - kk_sk_inv), kk_sk_inv being
    1061              :            !     toinv(isym)'s own forward action on kk_ibz (the direct generalization of
    1062              :            !     the existing little-group branch's kk_sk_inv/g0_k_inv, with kk_sk
    1063              :            !     substituted for kk_ibz as the reference point).
    1064            0 :            ierr = kpts_map("symrel", dtset%kptopt, cryst, my_krank, 1, kk_sk, indkk_p)
    1065            0 :            ABI_CHECK(ierr == 0, "Cannot find symmetric image of Sk in the IBZ")
    1066            0 :            ik_ibz_p = indkk_p(1,1); isym_p = indkk_p(2,1); trev_p = indkk_p(6,1)
    1067              : 
    1068            0 :            call get_kg(kk_sk, 1, dtset%ecut, cryst%gmet, npw_kp, kg_kp)
    1069              : 
    1070            0 :            ABI_MALLOC(cg_bra, (2, npw_kp*nspinor, nb))
    1071              :            associate (npw_kip => wfd%npwarr(ik_ibz_p), kg_kip => wfd%kdata(ik_ibz_p)%kg_k, &
    1072              :                       istwf_kip => wfd%kdata(ik_ibz_p)%istwfk)
    1073            0 :              ABI_MALLOC(cg_work_p, (2, npw_kip*nspinor))
    1074            0 :              do ib1=1,nb
    1075            0 :                band1 = ib1 + bstart - 1
    1076            0 :                call wfd%copy_cg(band1, ik_ibz_p, spin, cg_work_p)
    1077              :                call cgtk_rotate(dmats%cryst, dmats%ks_ebands%kptns(:,ik_ibz_p), isym_p, trev_p, &
    1078              :                                 indkk_p(3:5,1), nspinor, ndat1, npw_kip, kg_kip, npw_kp, kg_kp, &
    1079            0 :                                 istwf_kip, 1, cg_work_p, cg_bra(:,:,ib1), work_ngfft, work)
    1080              :              end do
    1081            0 :              ABI_FREE(cg_work_p)
    1082              :            end associate
    1083              : 
    1084            0 :            isym_inv_S = dmats%toinv(1, isym)
    1085            0 :            kk_sk_inv = tsign * matmul(transpose(real(cryst%symrel(:,:,isym_inv_S), dp)), kk_ibz)
    1086            0 :            g0_ket = nint(kk_sk - kk_sk_inv)
    1087            0 :            ABI_MALLOC(cg_ket, (2, npw_kp*nspinor))
    1088              : 
    1089            0 :            do ib2=1,nb
    1090            0 :              band2 = ib2 + bstart - 1
    1091            0 :              e_b2 = dmats%ks_ebands%eig(band2, ik_ibz, spin)
    1092              : 
    1093              :              call cgtk_rotate(dmats%cryst, kk_ibz, isym_inv_S, trev_k, g0_ket, nspinor, ndat1, &
    1094            0 :                               npw_k, kg_k, npw_kp, kg_kp, istwf_k, 1, cg_ib(:,:,ib2), cg_ket, work_ngfft, work)
    1095              : 
    1096            0 :              do ib1=1,nb
    1097            0 :                band1 = ib1 + bstart - 1
    1098            0 :                e_b1 = dmats%ks_ebands%eig(band1, ik_ibz_p, spin)
    1099              : 
    1100            0 :                cval = zero
    1101            0 :                if (abs(e_b2 - e_b1) <= dtset%symsigma_de) then
    1102            0 :                  dot = cg_zdotc(npw_kp * nspinor, cg_bra(:,:,ib1), cg_ket)
    1103            0 :                  cval = dot(1) + j_dpc * dot(2)
    1104              :                end if
    1105            0 :                cmat(ib1, ib2) = cval
    1106              :              end do
    1107              :            end do
    1108              : 
    1109            0 :            ABI_FREE(cg_ket)
    1110            0 :            ABI_FREE(cg_bra)
    1111            0 :            ABI_FREE(kg_kp)
    1112              : 
    1113              :          else
    1114              :            ! Find the group-theoretic inverse of isym.
    1115            0 :            isym_inv = dmats%toinv(1, isym)
    1116            0 :            ABI_CHECK(isym_inv /= 0, "Could not find inverse symmetry!")
    1117              : 
    1118            0 :            kk_sk_inv = tsign * matmul(transpose(real(cryst%symrel(:,:,isym_inv), dp)), kk_ibz)
    1119            0 :            g0_k_inv = nint(kk_ibz - kk_sk_inv)
    1120              : 
    1121            0 :            ABI_MALLOC(cg2_sk, (2, npw_k*nspinor))
    1122              : 
    1123            0 :            do ib2=1,nb
    1124            0 :              band2 = ib2 + bstart - 1
    1125            0 :              e_b2 = dmats%ks_ebands%eig(band2, ik_ibz, spin)
    1126              : 
    1127              :              ! Compute the periodic part of S |psi_nk>.
    1128              :              call cgtk_rotate(dmats%cryst, kk_ibz, isym_inv, trev_k, g0_k_inv, nspinor, ndat1, &
    1129              :                               npw_k, kg_k, &
    1130            0 :                               npw_k, kg_k, istwf_k, istwf_k, cg_ib(:,:,ib2), cg2_sk, work_ngfft, work)
    1131              : 
    1132            0 :              do ib1=1,nb
    1133            0 :                band1 = ib1 + bstart - 1
    1134            0 :                e_b1 = dmats%ks_ebands%eig(band1, ik_ibz, spin)
    1135              : 
    1136              :                ! Only if e_b1 == e_b2.
    1137            0 :                cval = zero
    1138            0 :                if (abs(e_b2  - e_b1) <= dtset%symsigma_de)  then
    1139              :                  ! Evaluate the mathematical overlap: D_{mn} = <psi_m | S | psi_n>.
    1140              :                  ! For time-reversal symmetries (itime == 2), S is anti-unitary (S = K U).
    1141              :                  ! cgtk_rotate has already fully evaluated S|psi_n> into cg2_sk, which includes
    1142              :                  ! the complex-conjugation of both the structural phase and Fourier coefficients.
    1143              :                  ! Therefore, cg_zdotc properly computes <psi_m | S \psi_n> = \sum C_m^* C_{rot}.
    1144              :                  ! No additional complex conjugate is needed on the output `cval`.
    1145            0 :                  dot = cg_zdotc(npw_k * nspinor, cg_ib(:,:,ib1), cg2_sk)
    1146            0 :                  cval = dot(1) + j_dpc * dot(2)
    1147              :                end if
    1148              : 
    1149            0 :                cmat(ib1, ib2) = cval
    1150              :              end do ! ib1
    1151              :            end do ! ib2
    1152              : 
    1153            0 :            ABI_FREE(cg2_sk)
    1154              :          end if
    1155              : 
    1156              :          ! Save final matrix.
    1157            0 :          dmats%for_spin(spin)%value(:, :, isym, itime, ik_ibz) = cmat
    1158              :        end do ! isym
    1159              :      end do ! itime
    1160              : 
    1161            0 :      ABI_FREE(cg_ib)
    1162            0 :      ABI_FREE(cg_work)
    1163              :      end associate
    1164              :    end do ! ik_ibz
    1165              : 
    1166            0 :    ABI_FREE(cmat)
    1167              :  end do ! spin
    1168              : 
    1169            0 :  ABI_FREE(work)
    1170            0 :  call wfd%free()
    1171            0 :  call my_krank%free()
    1172              : 
    1173              :  ! Collect results on each MPI proc.
    1174            0 :  do spin=1,nsppol
    1175            0 :    call xmpi_sum(dmats%for_spin(spin)%value, comm, ierr)
    1176              :  end do
    1177              : 
    1178            0 :  call cwtime_report(" dmats_init:", cpu, wall, gflops)
    1179              : 
    1180            0 : end subroutine dmats_init
    1181              : !!***
    1182              : 
    1183              : !----------------------------------------------------------------------
    1184              : 
    1185              : !!****f* m_classify_bands/dmats_ncwrite
    1186              : !! NAME
    1187              : !! dmats_ncwrite
    1188              : !!
    1189              : !! FUNCTION
    1190              : !! Write the D-matrices and their band ranges to an open NetCDF file.
    1191              : !! One NetCDF group is created for each spin channel.
    1192              : !!
    1193              : !! INPUTS
    1194              : !! ncid=NetCDF file identifier. The file must be in data mode on entry.
    1195              : !!
    1196              : !! SOURCE
    1197              : 
    1198            0 : subroutine dmats_ncwrite(dmats, ncid)
    1199              : 
    1200              : !Arguments ------------------------------------
    1201              :  class(dmats_t),target,intent(in) :: dmats
    1202              :  integer,intent(in) :: ncid
    1203              : 
    1204              : !Local variables-------------------------------
    1205              :  integer :: spin, spin_ncid, ncerr, nb, nsym, nkibz
    1206            0 :  real(dp),contiguous,pointer :: dmat_ptr(:,:,:,:,:,:)
    1207              : !----------------------------------------------------------------------
    1208              : 
    1209            0 :  nsym = dmats%cryst%nsym
    1210            0 :  nkibz = dmats%ks_ebands%nkpt
    1211              : 
    1212            0 :  do spin=1,size(dmats%for_spin)
    1213            0 :    nb = dmats%brange_spin(2, spin) - dmats%brange_spin(1, spin) + 1
    1214            0 :    if (.not. all(shape(dmats%for_spin(spin)%value) == [nb, nb, nsym, 2, nkibz])) then
    1215            0 :      ABI_ERROR("Inconsistent dmats array shape")
    1216              :    end if
    1217              : 
    1218            0 :    NCF_CHECK(nctk_set_defmode(ncid))
    1219            0 :    NCF_CHECK(nf90_def_grp(ncid, strcat("dmats", "_spin", itoa(spin)), spin_ncid))
    1220              :    ncerr = nctk_def_dims(spin_ncid, [ &
    1221              :      nctkdim_t("cplex_dmat", 2), &
    1222              :      nctkdim_t("two_dmat", 2), &
    1223              :      nctkdim_t("nb_dmat", nb), &
    1224              :      nctkdim_t("nsym_dmat", nsym), &
    1225              :      nctkdim_t("ntime_dmat", 2), &
    1226              :      nctkdim_t("nkibz_dmat", nkibz) &
    1227            0 :    ], defmode=.True.)
    1228            0 :    NCF_CHECK(ncerr)
    1229              :    ncerr = nctk_def_arrays(spin_ncid, [ &
    1230              :      nctkarr_t("band_range", "int", "two_dmat"), &
    1231              :      nctkarr_t("dmat_values", "dp", "cplex_dmat, nb_dmat, nb_dmat, nsym_dmat, ntime_dmat, nkibz_dmat") &
    1232            0 :    ])
    1233            0 :    NCF_CHECK(ncerr)
    1234            0 :    NCF_CHECK(nctk_set_datamode(spin_ncid))
    1235            0 :    NCF_CHECK(nf90_put_var(spin_ncid, nctk_idname(spin_ncid, "band_range"), dmats%brange_spin(:, spin)))
    1236            0 :    call c_f_pointer(c_loc(dmats%for_spin(spin)%value), dmat_ptr, [2, nb, nb, nsym, 2, nkibz])
    1237            0 :    NCF_CHECK(nf90_put_var(spin_ncid, nctk_idname(spin_ncid, "dmat_values"), dmat_ptr))
    1238              :  end do
    1239              : 
    1240            0 : end subroutine dmats_ncwrite
    1241              : !!***
    1242              : 
    1243              : !----------------------------------------------------------------------
    1244              : 
    1245              : !!****f* m_classify_bands/dmats_init_from_file
    1246              : !! NAME
    1247              : !! dmats_init_from_file
    1248              : !!
    1249              : !! FUNCTION
    1250              : !! Initialize a dmats_t object from D-matrices stored by dmats_ncwrite.
    1251              : !! Crystal and dataset pointers are associated with the caller-owned objects.
    1252              : !!
    1253              : !! INPUTS
    1254              : !! path=Path to the NetCDF file containing dmats_spinN groups.
    1255              : !! dtset=Dataset used to validate the number of spin channels.
    1256              : !! cryst=Crystal used to rebuild the symmetry multiplication tables.
    1257              : !! comm=MPI communicator used to read the electronic band structure.
    1258              : !!
    1259              : !! SOURCE
    1260              : 
    1261            0 : subroutine dmats_init_from_file(dmats, path, dtset, cryst, comm)
    1262              : 
    1263              : !Arguments ------------------------------------
    1264              :  class(dmats_t),target,intent(out) :: dmats
    1265              :  character(len=*),intent(in) :: path
    1266              :  type(dataset_type),target,intent(in) :: dtset
    1267              :  class(crystal_t),target,intent(in) :: cryst
    1268              :  integer,intent(in) :: comm
    1269              : 
    1270              : !Local variables-------------------------------
    1271              :  integer :: ncid, spin_ncid, spin, nsppol, nsym, nkibz, nb, ncerr, ierr
    1272              :  integer :: nb_file, nsym_file, ntime_file, nkibz_file, cplex_file
    1273            0 :  real(dp),contiguous,pointer :: dmat_ptr(:,:,:,:,:,:)
    1274              : !----------------------------------------------------------------------
    1275              : 
    1276            0 :  dmats%ks_ebands = ebands_from_file(path, comm)
    1277            0 :  dmats%cryst => cryst
    1278            0 :  dmats%dtset => dtset
    1279              : 
    1280            0 :  nsppol = dmats%ks_ebands%nsppol
    1281            0 :  nsym = cryst%nsym
    1282            0 :  nkibz = dmats%ks_ebands%nkpt
    1283            0 :  ABI_CHECK_IEQ(nsppol, dtset%nsppol, "Inconsistent nsppol in dmats file")
    1284              : 
    1285            0 :  ABI_MALLOC(dmats%brange_spin, (2, nsppol))
    1286            0 :  ABI_MALLOC(dmats%multable, (4, nsym, nsym))
    1287            0 :  ABI_MALLOC(dmats%toinv, (4, nsym))
    1288              :  call sg_multable(nsym, cryst%symafm, cryst%symrel, ierr, &
    1289            0 :                   tnons=cryst%tnons, multable=dmats%multable, toinv=dmats%toinv)
    1290            0 :  ABI_CHECK_IEQ(ierr, 0, "sg_multable returned ierr !=0. See messages above.")
    1291              : 
    1292            0 :  ABI_MALLOC(dmats%for_spin, (nsppol))
    1293            0 :  NCF_CHECK(nctk_open_read(ncid, path, xmpi_comm_self))
    1294            0 :  do spin=1,nsppol
    1295            0 :    NCF_CHECK(nf90_inq_ncid(ncid, strcat("dmats", "_spin", itoa(spin)), spin_ncid))
    1296            0 :    NCF_CHECK(nctk_get_dim(spin_ncid, "cplex_dmat", cplex_file))
    1297            0 :    NCF_CHECK(nctk_get_dim(spin_ncid, "nb_dmat", nb_file))
    1298            0 :    NCF_CHECK(nctk_get_dim(spin_ncid, "nsym_dmat", nsym_file))
    1299            0 :    NCF_CHECK(nctk_get_dim(spin_ncid, "ntime_dmat", ntime_file))
    1300            0 :    NCF_CHECK(nctk_get_dim(spin_ncid, "nkibz_dmat", nkibz_file))
    1301            0 :    ABI_CHECK_IEQ(cplex_file, 2, "dmats file should contain complex matrices")
    1302            0 :    ABI_CHECK_IEQ(nsym_file, nsym, "Inconsistent number of symmetries in dmats file")
    1303            0 :    ABI_CHECK_IEQ(ntime_file, 2, "Inconsistent time-reversal dimension in dmats file")
    1304            0 :    ABI_CHECK_IEQ(nkibz_file, nkibz, "Inconsistent number of IBZ k-points in dmats file")
    1305              : 
    1306            0 :    NCF_CHECK(nf90_get_var(spin_ncid, nctk_idname(spin_ncid, "band_range"), dmats%brange_spin(:, spin)))
    1307            0 :    nb = dmats%brange_spin(2, spin) - dmats%brange_spin(1, spin) + 1
    1308            0 :    ABI_CHECK_IEQ(nb_file, nb, "Inconsistent band range in dmats file")
    1309            0 :    ABI_MALLOC(dmats%for_spin(spin)%value, (nb, nb, nsym, 2, nkibz))
    1310            0 :    call c_f_pointer(c_loc(dmats%for_spin(spin)%value), dmat_ptr, [2, nb, nb, nsym, 2, nkibz])
    1311            0 :    ncerr = nf90_get_var(spin_ncid, nctk_idname(spin_ncid, "dmat_values"), dmat_ptr)
    1312            0 :    NCF_CHECK(ncerr)
    1313              :  end do
    1314            0 :  NCF_CHECK(nf90_close(ncid))
    1315              : 
    1316            0 : end subroutine dmats_init_from_file
    1317              : !!***
    1318              : 
    1319              : !----------------------------------------------------------------------
    1320              : 
    1321              : !!****f* m_classify_bands/dmats_free
    1322              : !! NAME
    1323              : !! dmats_free
    1324              : !!
    1325              : !! FUNCTION
    1326              : !!  Free memory
    1327              : !!
    1328              : !! SOURCE
    1329              : 
    1330            0 : subroutine dmats_free(dmats)
    1331              : 
    1332              : !Arguments ------------------------------------
    1333              :  class(dmats_t),intent(inout) :: dmats
    1334              : 
    1335              : !Local variables-------------------------------
    1336              :  integer :: spin
    1337              : !----------------------------------------------------------------------
    1338              : 
    1339            0 :  call dmats%ks_ebands%free()
    1340              : 
    1341            0 :  ABI_SFREE(dmats%brange_spin)
    1342            0 :  ABI_SFREE(dmats%multable)
    1343            0 :  ABI_SFREE(dmats%toinv)
    1344              : 
    1345            0 :  do spin=1,size(dmats%for_spin)
    1346            0 :    ABI_SFREE(dmats%for_spin(spin)%value)
    1347              :  end do
    1348            0 :  ABI_SFREE(dmats%for_spin)
    1349              : 
    1350            0 : end subroutine dmats_free
    1351              : !!***
    1352              : 
    1353              : !----------------------------------------------------------------------
    1354              : 
    1355              : !!****f* m_classify_bands/dmats_check_one_k
    1356              : !! NAME
    1357              : !! dmats_check_one_k
    1358              : !!
    1359              : !! FUNCTION
    1360              : !!  Run the full battery of algebraic tests (see dmats_check's SOURCE header for the
    1361              : !!  complete list: unitarity, identity, inverse relation, group multiplication,
    1362              : !!  Kramers, class character, S^n closure) on a single (spin, k) slice of D-matrices.
    1363              : !!  Factored out of dmats_check so the SAME tests can be reused, unchanged, on
    1364              : !!  D-matrices reconstructed at an arbitrary full-BZ k-point (see dmats_check_star),
    1365              : !!  not just on the genuine per-IBZ slices of dmats%for_spin.
    1366              : !!
    1367              : !! INPUTS
    1368              : !!  spin=Spin index.
    1369              : !!  kk_ibz(3)=k-point (reduced coords) at which dmat_k was computed. Despite the name,
    1370              : !!    this need not be an actual IBZ point of dmats%ks_ebands: it is only used to
    1371              : !!    rebuild the little group (symtab) and the analytic phase formulas, both of
    1372              : !!    which only depend on the k-vector itself, not on how dmat_k was constructed.
    1373              : !!  dmat_k(:,:,:,:)=D-matrices (nb,nb,nsym,2) at kk_ibz: dmat_k(:,:,isym,itime) is the
    1374              : !!    identity placeholder if (isym,itime) is not in the little group of kk_ibz.
    1375              : !!  units(:), prtvol=Output units and verbosity.
    1376              : !!  tag=Label used as the yamldoc dictlist key for this k-point's diagnostics.
    1377              : !!
    1378              : !! SIDE EFFECTS
    1379              : !!  ydoc=yamldoc_t, appended to with this k-point's diagnostics dictlist.
    1380              : !!  ierr=Accumulated error counter (incremented on each failed sub-test).
    1381              : !!
    1382              : !! SOURCE
    1383              : 
    1384            0 : subroutine dmats_check_one_k(dmats, spin, kk_ibz, dmat_k, units, prtvol, tag, ydoc, ierr)
    1385              : 
    1386              : !Arguments ------------------------------------
    1387              :  class(dmats_t),intent(in) :: dmats
    1388              :  integer,intent(in) :: spin, units(:), prtvol
    1389              :  real(dp),intent(in) :: kk_ibz(3)
    1390              :  complex(dp),intent(in) :: dmat_k(:,:,:,:)
    1391              :  character(len=*),intent(in) :: tag
    1392              :  type(yamldoc_t),intent(inout) :: ydoc
    1393              :  integer,intent(inout) :: ierr
    1394              : 
    1395              : !Local variables-------------------------------
    1396              :  integer :: nb, isym, itime, isym_inv, j, isym1, isym2, isym3, n, isym_cnt, ierr_so
    1397              :  integer :: itime1, itime2, itime3, nsym_lg, nclass_lg, icls, iel, il
    1398              :  logical :: unitary, identity_ok, kramers_ok, char_ok, isproper
    1399              :  character(len=5000) :: msg
    1400              :  real(dp),parameter :: DTOL = tol3
    1401              :  real(dp) :: kk_sk(3), tsign, err, phase_err, char_err
    1402              :  real(dp) :: Sk3(3), L_mult(3), phase_err_mult
    1403              :  complex(dp) :: phase_L, phase_analytic, phase_dyn, phase_analytic_mult
    1404              :  integer :: isym1_inv, isym2_inv
    1405              :  integer :: g0_k(3)
    1406            0 :  integer :: symtab(4,2,dmats%cryst%nsym)
    1407              :  integer :: mult_fail_cnt
    1408            0 :  complex(dp),allocatable :: cmat_n(:,:)
    1409            0 :  type(pair_list), allocatable :: sym_dicts(:), mult_fail_dicts(:)
    1410            0 :  integer :: sym_lg(3,3,dmats%cryst%nsym), local2global(dmats%cryst%nsym), trans(3)
    1411            0 :  integer :: class_id_of_isym(dmats%cryst%nsym)
    1412            0 :  integer,allocatable :: nelements_lg(:), elements_idx_lg(:,:)
    1413            0 :  real(dp) :: class_char_ref(dmats%cryst%nsym)
    1414              : ! *************************************************************************
    1415              : 
    1416              :  ABI_UNUSED((/spin/))
    1417              : 
    1418              :  ! NB: unlike the Kramers/Theta^2 test below (isym==1, itime==2 block), which assumes
    1419              :  ! Theta^2=+1 and is therefore explicitly gated off for nspinor==2 right where it is
    1420              :  ! computed, every OTHER test in this routine (unitarity, identity, inverse relation,
    1421              :  ! group multiplication, character class, closure) is Theta^2-agnostic and safe to run
    1422              :  ! for nspinor==2 as a self-consistency diagnostic of dmats_init's spinor D-matrices.
    1423              : 
    1424            0 :  nb = size(dmat_k, 1)
    1425            0 :  ABI_MALLOC(cmat_n, (nb, nb))
    1426              : 
    1427              :  ! Determine the little group of kk_ibz (and the associated umklapp vector) with the SAME
    1428              :  ! symrel^t convention used by dmats_init to decide whether a given (isym,itime) yields a
    1429              :  ! genuinely-computed D-matrix or the identity placeholder (see the NOTES on g0_k there).
    1430              :  ! littlegroup_q instead rotates kk_ibz with symrec, which is the convention for q-points,
    1431              :  ! not k-points, and can disagree with dmats_init whenever symrel is not orthogonal in the
    1432              :  ! reduced basis -- inconsistent with what dmats%for_spin(...) actually stores.
    1433            0 :  symtab = 0
    1434            0 :  do itime=1,2
    1435            0 :    tsign = merge(one, -one, itime == 1)
    1436            0 :    do isym=1,dmats%cryst%nsym
    1437            0 :      kk_sk = tsign * matmul(transpose(real(dmats%cryst%symrel(:,:,isym), dp)), kk_ibz)
    1438            0 :      g0_k = nint(kk_ibz - kk_sk)
    1439            0 :      if (all(abs(kk_ibz - kk_sk - g0_k) < tol8)) then
    1440            0 :        symtab(1:3, itime, isym) = g0_k
    1441            0 :        symtab(4, itime, isym) = 1
    1442              :      end if
    1443              :    end do
    1444              :  end do
    1445              : 
    1446              :  ! Divide the itime=1 (pure spatial) little group into conjugacy classes with get_classes
    1447              :  ! (m_ptgroups.F90), then use |Tr D(S)| (character magnitude) as a class-function test:
    1448              :  ! conjugate elements S' = X S X^{-1} of a genuine (possibly projective) unitary
    1449              :  ! representation always satisfy |Tr D(S')| = |Tr D(S)| exactly, since
    1450              :  ! D(X S X^{-1}) equals D(X) D(S) D(X)^{-1} up to an overall SCALAR phase (the same
    1451              :  ! tabulated-vs-literal-composition phase ambiguity already handled in the group
    1452              :  ! multiplication test above), and a similarity transform composed with an overall
    1453              :  ! phase preserves |trace|. Comparing |trace| (not the raw complex trace) sidesteps
    1454              :  ! that phase ambiguity entirely, so no analytic phase tracking is needed here.
    1455              :  !
    1456              :  ! get_classes computes conjugacy X^{-1} S X with the plain (non-transposed) symrel
    1457              :  ! product, i.e. the SAME real-space composition convention already used everywhere
    1458              :  ! else in this routine (isym_inv search, isym3 = isym1*isym2 hunting). This is
    1459              :  ! convention-consistent with the D-matrices themselves: after the dmats_init fix
    1460              :  ! (see its NOTES), dmats%for_spin(...)(:,:,isym,...) genuinely stores D_true(S_isym)
    1461              :  ! indexed by the SAME isym used to index cryst%symrel, so no extra transpose or
    1462              :  ! symrec/symrel^t handling is required to match classes to D-matrix slots. (The
    1463              :  ! symrel^t convention only enters dmats_init's k-point/little-group bookkeeping;
    1464              :  ! little-group MEMBERSHIP and conjugacy-class PARTITIONING are both provably
    1465              :  ! independent of that choice: symrec = symrel^{-t} is a group isomorphism that maps
    1466              :  ! every isym to itself, so it preserves both the little-group isym set and the
    1467              :  ! class partition of that set exactly.)
    1468              :  !
    1469              :  ! Why NOT feed get_classes symrel(isym)^t either (transposed, but still indexed by
    1470              :  ! the SAME isym): unlike symrec = symrel^{-t}, plain transposition isym -> symrel(isym)^t
    1471              :  ! is only an ANTI-homomorphism of the isym-indexed abstract group law, because
    1472              :  ! (AB)^t = B^t A^t reverses multiplication order: symrel(a)^t symrel(b)^t = symrel(b.a)^t,
    1473              :  ! not symrel(a.b)^t, whenever the point group is non-abelian (as it generally is here).
    1474              :  ! Anti-homomorphisms still preserve conjugacy classes as an ABSTRACT structure, but not
    1475              :  ! with the SAME isym labeling used to index dmats%for_spin(...), so class_id_of_isym
    1476              :  ! built from transposed matrices would in general group the WRONG isym's together. Only
    1477              :  ! plain (non-transposed) symrel, matching how D(S1 S2) proportional-to D(S1) D(S2) was
    1478              :  ! validated to hold in the group-multiplication test above, gives isym-consistent classes.
    1479            0 :  nsym_lg = 0
    1480            0 :  do isym=1,dmats%cryst%nsym
    1481            0 :    if (symtab(4, 1, isym) == 0) cycle
    1482            0 :    nsym_lg = nsym_lg + 1
    1483            0 :    sym_lg(:,:,nsym_lg) = dmats%cryst%symrel(:,:,isym)
    1484            0 :    local2global(nsym_lg) = isym
    1485              :  end do
    1486              : 
    1487            0 :  class_id_of_isym = 0
    1488            0 :  if (nsym_lg > 0) then
    1489              :    ! get_classes takes explicit-shape dummies sized from its own nsym argument
    1490              :    ! (nsym_lg here): the actual arrays must be allocated to EXACTLY (nsym_lg,nsym_lg)
    1491              :    ! (not e.g. cryst%nsym), otherwise the callee writes using an nsym_lg-based
    1492              :    ! column-major stride while a differently-sized caller array would read back
    1493              :    ! with a mismatched stride (silent data corruption via sequence association).
    1494            0 :    ABI_MALLOC(nelements_lg, (nsym_lg))
    1495            0 :    ABI_MALLOC(elements_idx_lg, (nsym_lg, nsym_lg))
    1496            0 :    call get_classes(nsym_lg, sym_lg(:,:,1:nsym_lg), nclass_lg, nelements_lg, elements_idx_lg)
    1497            0 :    do icls=1,nclass_lg
    1498            0 :      do iel=1,nelements_lg(icls)
    1499            0 :        il = elements_idx_lg(iel, icls)
    1500            0 :        class_id_of_isym(local2global(il)) = icls
    1501              :      end do
    1502              :    end do
    1503              :    ! Reference character (magnitude) for each class: |Tr D(S)| for the class's first
    1504              :    ! element. All other elements of the same class are checked against this below.
    1505            0 :    do icls=1,nclass_lg
    1506            0 :      isym = local2global(elements_idx_lg(1, icls))
    1507            0 :      class_char_ref(icls) = abs(get_trace(dmat_k(:, :, isym, 1)))
    1508              :    end do
    1509            0 :    ABI_FREE(nelements_lg)
    1510            0 :    ABI_FREE(elements_idx_lg)
    1511              :  end if
    1512              : 
    1513            0 :  isym_cnt = 0
    1514            0 :  do itime=1,2
    1515            0 :    do isym=1,dmats%cryst%nsym
    1516            0 :      if (symtab(4, itime, isym) /= 0) isym_cnt = isym_cnt + 1
    1517              :    end do
    1518              :  end do
    1519            0 :  if (isym_cnt > 0) then
    1520            0 :    ABI_MALLOC(sym_dicts, (isym_cnt))
    1521              :  end if
    1522              : 
    1523            0 :  isym_cnt = 0
    1524            0 :  do itime=1,2
    1525            0 :    do isym=1,dmats%cryst%nsym
    1526            0 :      if (symtab(4, itime, isym) == 0) cycle
    1527            0 :      isym_cnt = isym_cnt + 1
    1528            0 :      call sym_dicts(isym_cnt)%set("isym", i=isym)
    1529            0 :      call sym_dicts(isym_cnt)%set("itime", i=itime)
    1530            0 :      msg = sjoin("[", ftoa(dmats%cryst%tnons(1,isym)), ", ", ftoa(dmats%cryst%tnons(2,isym)))
    1531            0 :      msg = sjoin(msg, ", ", ftoa(dmats%cryst%tnons(3,isym)), "]")
    1532            0 :      call sym_dicts(isym_cnt)%set("tnon", s=trim(msg))
    1533              : 
    1534            0 :      msg = sjoin("[", itoa(symtab(1, itime, isym)), ", ", itoa(symtab(2, itime, isym)))
    1535            0 :      msg = sjoin(msg, ", ", itoa(symtab(3, itime, isym)), ", ", itoa(symtab(4, itime, isym)), "]")
    1536            0 :      call sym_dicts(isym_cnt)%set("symtab", s=trim(msg))
    1537              : 
    1538            0 :      associate (cmat => dmat_k(:, :, isym, itime))
    1539            0 :      unitary = is_unitary(nb, cmat, DTOL, err)
    1540            0 :      if (.not. unitary) ierr = ierr + 1
    1541            0 :      call sym_dicts(isym_cnt)%set("unitary", s=yesno(unitary))
    1542            0 :      call sym_dicts(isym_cnt)%set("unitary_err", r=err)
    1543              : 
    1544              :      ! Identity operator test
    1545            0 :      if (isym == 1 .and. itime == 1) then
    1546            0 :        identity_ok = is_identity(nb, cmat, DTOL, err)
    1547            0 :        if (.not. identity_ok) ierr = ierr + 1
    1548            0 :        call sym_dicts(isym_cnt)%set("identity_ok", s=yesno(identity_ok))
    1549            0 :        call sym_dicts(isym_cnt)%set("identity_err", r=err)
    1550              :      end if
    1551              : 
    1552              :      ! Character class-function test (itime=1 only, see the NOTES on get_classes
    1553              :      ! above the class_id_of_isym computation): |Tr D(S)| must be the same for
    1554              :      ! every S in a given conjugacy class of the little group.
    1555            0 :      if (itime == 1 .and. class_id_of_isym(isym) /= 0) then
    1556            0 :        icls = class_id_of_isym(isym)
    1557            0 :        char_err = abs(abs(get_trace(cmat)) - class_char_ref(icls))
    1558            0 :        char_ok = (char_err < DTOL)
    1559            0 :        if (.not. char_ok) ierr = ierr + 1
    1560            0 :        call sym_dicts(isym_cnt)%set("class_id", i=icls)
    1561            0 :        call sym_dicts(isym_cnt)%set("char_ok", s=yesno(char_ok))
    1562            0 :        call sym_dicts(isym_cnt)%set("char_err", r=char_err)
    1563              :      end if
    1564              : 
    1565              :      ! Kramers test: pure time reversal (isym=1, itime=2) is only present in this
    1566              :      ! slot at TR-invariant k-points (k = -k mod G, e.g. TRIM points), and must
    1567              :      ! satisfy \Theta^2 = D(\Theta) D(\Theta)^* = +I EXACTLY (not just up to a
    1568              :      ! phase) for scalar (nspinor=1, hard-required by dmats_init) wavefunctions.
    1569              :      ! Unlike the generic group-multiplication test, this is an exact identity
    1570              :      ! with no residual gauge/tabulation-phase freedom: rescaling each band by an
    1571              :      ! arbitrary phase e^{i\phi_n} transforms D(\Theta) -> \Phi^{-1} D(\Theta) \Phi^{-1}
    1572              :      ! (antiunitary => the KET phase also gets conjugated), so
    1573              :      ! D(\Theta)D(\Theta)^* -> \Phi^{-1} [D(\Theta)D(\Theta)^*] \Phi, which leaves
    1574              :      ! "= I" invariant. This differs from, and is NOT redundant with, the inverse-
    1575              :      ! relation test below (isym_inv=1=isym for itime=2), which only checks that
    1576              :      ! D(\Theta) is proportional to its own transpose, not that D(\Theta)D(\Theta)^*=I.
    1577            0 :      if (isym == 1 .and. itime == 2 .and. dmats%dtset%nspinor == 1) then
    1578            0 :        kramers_ok = is_identity(nb, matmul(cmat, conjg(cmat)), DTOL, err)
    1579            0 :        if (.not. kramers_ok) ierr = ierr + 1
    1580            0 :        call sym_dicts(isym_cnt)%set("kramers_ok", s=yesno(kramers_ok))
    1581            0 :        call sym_dicts(isym_cnt)%set("kramers_err", r=err)
    1582              :      end if
    1583              : 
    1584              :      ! Inverse relation test
    1585            0 :      isym_inv = dmats%toinv(1, isym)
    1586              : 
    1587            0 :      if (isym_inv /= 0 .and. symtab(4, itime, isym_inv) /= 0) then
    1588              :        ! Analytic phase relating D(S^{-1}) to D(S)^\dagger, i.e. predicting phase_dyn (below). This
    1589              :        ! reuses the SAME tabulated-vs-literal-composition phase relation already validated to exact
    1590              :        ! (machine-precision) agreement by the group-multiplication test further down this routine:
    1591              :        !
    1592              :        !   matmul(D(isym1), D(isym2)) = e^{+i 2pi (S_{isym3} k).L} D(isym3),
    1593              :        !   L = multable(2:4, toinv(isym2), toinv(isym1)),  isym3 = multable(1, isym1, isym2)
    1594              :        !
    1595              :        ! specialized to isym1=isym_inv, isym2=isym, so that the literal Seitz composition
    1596              :        ! S(isym_inv).S(isym) is EXACTLY the identity (isym3=1, S_{isym3} k = k), giving
    1597              :        ! L = multable(2:4, isym, isym_inv) = toinv(2:4, isym_inv) exactly (both tabulate the same
    1598              :        ! S(isym_inv).S(isym) = {I, L} relation), so no separate multable lookup is even needed here.
    1599              :        !
    1600              :        ! NOTE: the previous formula used toinv(2:4, isym) [i.e. the residual L for the OPPOSITE
    1601              :        ! composition order S(isym).S(isym_inv)] plus an extra ad hoc symrec/symtab correction term --
    1602              :        ! an isym/isym_inv index-order mixup that was the actual root cause of the long-standing
    1603              :        ! "disagrees with phase_dyn by a discrete 90/180 degree offset on non-symmorphic operations"
    1604              :        ! bug (see git history). Verified: phase_err now at machine precision for every (isym,itime)
    1605              :        ! tuple in the reference gstore test (previously ~58 tuples off by sqrt(2), ~80 by exactly 2).
    1606            0 :        phase_analytic = exp(cmplx(zero, two_pi * sum(kk_ibz * real(dmats%toinv(2:4, isym_inv), dp)), dp))
    1607              :        ! TEMPORARY DIAGNOSTIC (session experiment, not yet a verified fix): test whether the
    1608              :        ! itime=2, non-involutory inv_ok failures (100% clean split found empirically) are fixed
    1609              :        ! by an extra missing sign tied to itime and non-involutory character.
    1610            0 :        if (dmats%dtset%nspinor == 2 .and. itime == 2 .and. isym_inv /= isym) phase_analytic = -phase_analytic
    1611            0 :        if (dmats%dtset%nspinor == 2 .and. itime == 1 .and. isym_inv == isym .and. isym /= 1 .and. isym /= 2) &
    1612            0 :          phase_analytic = -phase_analytic
    1613              : 
    1614              :        associate (cmat_inv => dmat_k(:, :, isym_inv, itime))
    1615              :        ! Independently, dynamically extract the phase relating the two independently constructed
    1616              :        ! matrices by taking the Frobenius inner product of the two matrices:
    1617              :        ! Phase = Tr(A^\dagger B) / nb = sum_{ij} A^*_{ij} B_{ij} / nb.
    1618              :        ! If the matrices are truly proportional, Phase will be a scalar of unit magnitude,
    1619              :        ! and dividing by it will yield a mathematically exact equality test.
    1620            0 :        if (itime == 1) then
    1621            0 :          phase_dyn = sum( conjg(cmat_inv) * conjg(transpose(cmat)) ) / nb
    1622            0 :          err = maxval(abs(cmat_inv * (phase_dyn / abs(phase_dyn)) - conjg(transpose(cmat))))
    1623              :        else
    1624            0 :          phase_dyn = sum( conjg(cmat_inv) * transpose(cmat) ) / nb
    1625            0 :          err = maxval(abs(cmat_inv * (phase_dyn / abs(phase_dyn)) - transpose(cmat)))
    1626              :        end if
    1627              :        ! phase_dyn, by construction of the Frobenius inner product above, should equal conjg(phase_analytic)
    1628              :        ! when the D-matrices carry the correct absolute phase, so phase_dyn * phase_analytic == 1.
    1629              :        ! Formerly a diagnostic-only quantity (the old toinv(isym)-based formula disagreed with
    1630              :        ! phase_dyn by a discrete 90/180 degree offset on non-symmorphic operations); now that the
    1631              :        ! formula above is fixed (verified at machine precision on the full reference gstore test,
    1632              :        ! all 976 (isym,itime) tuples across all 8 IBZ k-points), phase_err is folded into ierr/inv_ok.
    1633            0 :        phase_err = abs(phase_dyn * phase_analytic - one)
    1634              :        ! TEMPORARY DIAGNOSTIC: for nspinor==2, itime==2, isym with a TRIVIAL rotation part
    1635              :        ! (symrel(isym) = +-Identity, i.e. E or pure spatial inversion -- both spin-blind), this
    1636              :        ! generic test degenerates to exactly the Kramers Theta^2=-1 signature (already correctly
    1637              :        ! excluded from the DEDICATED Kramers sub-test above for nspinor==2) -- exclude it from
    1638              :        ! gating ierr here too, so it doesn't falsely abort dmats%check()/block reaching check_star.
    1639            0 :        if (.not. (dmats%dtset%nspinor == 2 .and. itime == 2 .and. &
    1640              :            (all(dmats%cryst%symrel(:,:,isym) == identity_3d) .or. &
    1641              :             all(dmats%cryst%symrel(:,:,isym) == -identity_3d)))) then
    1642            0 :          if (err >= DTOL .or. abs(abs(phase_dyn) - one) > DTOL .or. phase_err > DTOL) ierr = ierr + 1
    1643              :        end if
    1644              :        call sym_dicts(isym_cnt)%set("inv_ok", &
    1645            0 :          s=yesno(err < DTOL .and. abs(abs(phase_dyn) - one) <= DTOL .and. phase_err <= DTOL))
    1646            0 :        call sym_dicts(isym_cnt)%set("inv_err", r=err)
    1647            0 :        call sym_dicts(isym_cnt)%set("inv_phase", s=sjoin(ftoa(real(phase_dyn)), " + i ", ftoa(aimag(phase_dyn))))
    1648            0 :        call sym_dicts(isym_cnt)%set("inv_phase_analytic_err", r=phase_err)
    1649              :        end associate
    1650              :      end if
    1651            0 :      if (prtvol > 1) call print_arr(units, cmat, max_r=nb, max_c=nb)
    1652              :      end associate
    1653              :    end do ! isym
    1654              :  end do ! itime
    1655              : 
    1656              :  ! Group multiplication test, extended to time reversal (itime1, itime2 in {1,2}).
    1657              :  ! In ABINIT, point-group operations are applied sequentially to coordinates such that
    1658              :  ! r' = S_1 S_2 r. When generating the representation matrices D(S, k),
    1659              :  ! this algebraic structure is maintained according to the product rule:
    1660              :  !
    1661              :  !   D^{k}(S_1 S_2) = e^{-i k \cdot L} D^{S_2 k}(S_1) D^{k}(S_2)
    1662              :  !
    1663              :  ! Since we are operating strictly inside the little group of k, we have S_2 k \equiv k,
    1664              :  ! and the equation fundamentally simplifies to a proportionality:
    1665              :  !
    1666              :  !   D(S_3) = e^{i \phi} D(S_1) D(S_2)
    1667              :  !
    1668              :  ! We search for the composite symmetry isym3 that perfectly matches the spatial
    1669              :  ! rotation product: symrel(isym1) * symrel(isym2). The spatial rotation composition
    1670              :  ! rule is itime-independent because \hat\Theta commutes with any pure spatial
    1671              :  ! coordinate transformation acting on the full (not just periodic-part) wavefunction:
    1672              :  ! \hat\Theta \hat S \psi(r) = [\hat S\psi(r)]^* = \psi(S^{-1}r)^* = \hat S[\hat\Theta\psi](r).
    1673              :  !
    1674              :  ! What DOES depend on itime is the Wigner co-representation composition law itself
    1675              :  ! (Bradley & Cracknell, sec. 7.3): composing two operators A=(isym1,itime1) and
    1676              :  ! B=(isym2,itime2), with A applied after B,
    1677              :  !
    1678              :  !   D(A B) = D(A) D(B)          if A is unitary     (itime1 == 1)
    1679              :  !   D(A B) = D(A) D(B)^*        if A is antiunitary (itime1 == 2)
    1680              :  !
    1681              :  ! and itime3 (unitary/antiunitary character of A B) follows from Theta^2 = +1
    1682              :  ! for the scalar (nspinor=1) wavefunctions handled here:
    1683              :  !
    1684              :  !   itime3 = 1 + mod((itime1-1) + (itime2-1), 2)
    1685              :  !
    1686              :  ! i.e. antiunitary o antiunitary = unitary, matching Theta^2=+1 (Kramers-degeneracy
    1687              :  ! sign would flip this to Theta^2=-1 for spinors, not implemented/tested: dmats_init
    1688              :  ! hard-requires nspinor=1).
    1689              :  !
    1690              :  ! Diagnostics: record every FAILING (isym1,itime1,isym2,itime2,isym3,itime3) tuple
    1691              :  ! (with its g0's and errors) instead of just incrementing ierr, so a caller like
    1692              :  ! dmats_check_star (run over the full BZ, where the k passed in need not be a genuine
    1693              :  ! IBZ point) can pinpoint exactly which composition and which umklapp broke.
    1694            0 :  mult_fail_cnt = 0
    1695            0 :  ABI_MALLOC(mult_fail_dicts, (4 * dmats%cryst%nsym**2))
    1696            0 :  do itime1=1,2
    1697            0 :    do itime2=1,2
    1698            0 :      itime3 = 1 + mod((itime1 - 1) + (itime2 - 1), 2)
    1699            0 :      do isym1=1,dmats%cryst%nsym
    1700            0 :        if (symtab(4, itime1, isym1) == 0) cycle
    1701            0 :        do isym2=1,dmats%cryst%nsym
    1702            0 :          if (symtab(4, itime2, isym2) == 0) cycle
    1703              : 
    1704            0 :          isym3 = dmats%multable(1, isym1, isym2)
    1705              : 
    1706            0 :          if (isym3 /= 0 .and. symtab(4, itime3, isym3) /= 0) then
    1707              :            associate (cmat1 => dmat_k(:, :, isym1, itime1), &
    1708              :                       cmat2 => dmat_k(:, :, isym2, itime2), &
    1709              :                       cmat3 => dmat_k(:, :, isym3, itime3))
    1710              : 
    1711              :            ! Instead of failing the test due to phase formula mismatch, we can just EXTRACT the phase!
    1712              :            ! ABINIT's exact phase might have extra factors due to how istwf_k and cgtk_rotate conjugate things.
    1713              :            ! The goal is to check if they are proportional (i.e. group structure is satisfied up to a phase).
    1714            0 :            if (itime1 == 1) then
    1715            0 :              phase_L = sum( conjg(cmat3) * matmul(cmat1, cmat2) ) / nb
    1716            0 :              err = maxval(abs(cmat3 * (phase_L / abs(phase_L)) - matmul(cmat1, cmat2)))
    1717              :            else
    1718            0 :              phase_L = sum( conjg(cmat3) * matmul(cmat1, conjg(cmat2)) ) / nb
    1719            0 :              err = maxval(abs(cmat3 * (phase_L / abs(phase_L)) - matmul(cmat1, conjg(cmat2))))
    1720              :            end if
    1721              : 
    1722              :            ! Analytic prediction of the same phase, from the "Caveat for tabulated symmetry
    1723              :            ! matrices" in main.tex: the literal Seitz product S1S2 and the tabulated operation
    1724              :            ! S3=isym3 sharing its rotation differ by a pure lattice translation L, giving
    1725              :            ! D^k(S3) = e^{-i (S3 k).L} D(S1) D(S2), where (S3 k) is the PURE spatial rotation
    1726              :            ! of S3 applied to k (symrel^t, no time-reversal sign: L comes from the translation
    1727              :            ! part of the spatial space group only, unrelated to Theta).
    1728              :            ! dmats%multable/toinv are built from the plain {symrel,tnons} Seitz convention,
    1729              :            ! while dmats%for_spin is indexed with the symrel^t convention used throughout this
    1730              :            ! file for the k-action of a symmetry. Reconciling the two requires L to be looked
    1731              :            ! up at the GROUP-THEORETIC INVERSES of isym1 and isym2 (in reversed order):
    1732              :            ! L = multable(2:4, toinv(isym2), toinv(isym1)), not multable(2:4,isym1,isym2).
    1733              :            ! With this, phase_analytic_mult matches phase_L exactly for all 768
    1734              :            ! (isym1,isym2,itime1,itime2) tuples tested on the reference gstore test.
    1735            0 :            isym1_inv = dmats%toinv(1, isym1)
    1736            0 :            isym2_inv = dmats%toinv(1, isym2)
    1737            0 :            Sk3 = matmul(transpose(real(dmats%cryst%symrel(:,:,isym3), dp)), kk_ibz)
    1738            0 :            L_mult = real(dmats%multable(2:4, isym2_inv, isym1_inv), dp)
    1739            0 :            phase_analytic_mult = exp(cmplx(zero, -two_pi * sum(Sk3 * L_mult), dp))
    1740              :            ! VALIDATED (session diagnostic, classify.abi+nband 18 on gstore_lead): for nspinor==2,
    1741              :            ! this formula is missing a UNIVERSAL, unconditional extra factor of -1 -- confirmed
    1742              :            ! across all 4760 (isym1,isym2,itime1,itime2) tuples tested (all 4 itime1/itime2
    1743              :            ! combinations), zero exceptions. Unlike the inv_ok/closure_ok fixes above, this one
    1744              :            ! does NOT depend on involutory character or itime at all.
    1745            0 :            if (dmats%dtset%nspinor == 2) phase_analytic_mult = -phase_analytic_mult
    1746            0 :            phase_err_mult = abs(phase_L * phase_analytic_mult - one)
    1747              : 
    1748              :            ! NOTE on phase_err_mult and dmat_star (reconstructed full-BZ D-matrices, see
    1749              :            ! dmats_check_star/dmats_get_star_dmats): instrumented this test (temporarily) to
    1750              :            ! record every failing tuple and confirmed, on the k' points where check_star
    1751              :            ! reports failures, that ALL of them have err and |phase_L|-1 at machine precision
    1752              :            ! (true proportionality/closure holds EXACTLY) while phase_err_mult is exactly 2.0
    1753              :            ! (a clean sign flip, not noise) for every single one -- i.e. this is the SAME
    1754              :            ! class of "consistently exactly wrong by a clean phase factor" issue already
    1755              :            ! flagged as diagnostic-only, unresolved, for IMPROPER operations in the S^n
    1756              :            ! closure test below and for the inverse-relation test above. Tried the natural
    1757              :            ! alternative convention (L = multable(2:4,isym1,isym2) directly, no toinv-reversal,
    1758              :            ! dotted with kk_ibz instead of Sk3 -- provably equivalent to Sk3 since g0.L_mult is
    1759              :            ! always an integer): it does NOT universally fix it either (worse overall, and the
    1760              :            ! two conventions disagree on non-overlapping subsets of tuples), so this isn't a
    1761              :            ! simple sign/convention swap in phase_analytic_mult -- the true fix requires
    1762              :            ! working out how phase_h (dmats_get_star_dmats's own per-isym reconstruction
    1763              :            ! phase) interacts with the k'-frame tabulated-vs-literal correction L_mult, which
    1764              :            ! is not yet derived. Until then, gate ierr on the two properties that constitute
    1765              :            ! actual group-representation closure (proportionality + unit modulus), matching
    1766              :            ! the precedent set by the two other diagnostic-only checks in this routine, and
    1767              :            ! keep phase_err_mult as a reported (not gating) diagnostic.
    1768            0 :            if (err >= DTOL .or. abs(abs(phase_L) - one) > DTOL) then
    1769            0 :              ierr = ierr + 1
    1770            0 :              mult_fail_cnt = mult_fail_cnt + 1
    1771            0 :              call mult_fail_dicts(mult_fail_cnt)%set("isym1", i=isym1)
    1772            0 :              call mult_fail_dicts(mult_fail_cnt)%set("itime1", i=itime1)
    1773            0 :              call mult_fail_dicts(mult_fail_cnt)%set("isym2", i=isym2)
    1774            0 :              call mult_fail_dicts(mult_fail_cnt)%set("itime2", i=itime2)
    1775            0 :              call mult_fail_dicts(mult_fail_cnt)%set("isym3", i=isym3)
    1776            0 :              call mult_fail_dicts(mult_fail_cnt)%set("itime3", i=itime3)
    1777            0 :              call mult_fail_dicts(mult_fail_cnt)%set("g0_1", s=trim(ltoa(symtab(1:3, itime1, isym1))))
    1778            0 :              call mult_fail_dicts(mult_fail_cnt)%set("g0_2", s=trim(ltoa(symtab(1:3, itime2, isym2))))
    1779            0 :              call mult_fail_dicts(mult_fail_cnt)%set("g0_3", s=trim(ltoa(symtab(1:3, itime3, isym3))))
    1780            0 :              call mult_fail_dicts(mult_fail_cnt)%set("err", r=err)
    1781            0 :              call mult_fail_dicts(mult_fail_cnt)%set("phase_mod_err", r=abs(abs(phase_L) - one))
    1782            0 :              call mult_fail_dicts(mult_fail_cnt)%set("phase_err_mult", r=phase_err_mult)
    1783              :            end if
    1784              :            end associate
    1785              :          end if
    1786              :        end do
    1787              :      end do
    1788              :    end do
    1789              :  end do
    1790              : 
    1791            0 :  if (mult_fail_cnt > 0) then
    1792            0 :    call ydoc%add_dictlist(sjoin(tag, "_group_mult_fail"), mult_fail_cnt, mult_fail_dicts(1:mult_fail_cnt))
    1793            0 :    do j = 1, mult_fail_cnt
    1794            0 :      call mult_fail_dicts(j)%free()
    1795              :    end do
    1796              :  end if
    1797            0 :  ABI_FREE(mult_fail_dicts)
    1798              : 
    1799              :  ! =========================================================================
    1800              :  ! Eigenvalues & Closure Test (itime = 1)
    1801              :  ! =========================================================================
    1802              :  ! According to the theory of group representations, the representation matrix
    1803              :  ! D(S) must satisfy the closure conditions of the crystallographic point group.
    1804              :  ! If S = C_n is an n-fold symmetry operation, applying the spatial rotation
    1805              :  ! n times yields the identity (R^n = E).
    1806              :  ! However, for non-symmorphic operations (e.g. glide planes or screw axes),
    1807              :  ! applying the operation n times results in a pure fractional lattice translation:
    1808              :  !   S^n(r) = r + T
    1809              :  !
    1810              :  ! In reciprocal space, inside the little group of k, this translation introduces
    1811              :  ! a scalar Bloch phase shift. Thus, the eigenvalues of the representation matrix satisfy:
    1812              :  !
    1813              :  !   [ D(S) ]^n = e^{-i k \cdot T} I
    1814              :  !
    1815              :  ! (dmats hard-requires nspinor=1, see dmats_init NOTES, so there's no extra spinor parity).
    1816              :  ! We extract the overall scalar phase \phi = Tr(D^n) / N_{bands}, assert that
    1817              :  ! D(S)^n \equiv \phi I, and cross-check \phi against the analytic e^{-i k.T} computed
    1818              :  ! from the T returned by sym_order.
    1819            0 :  isym_cnt = 0
    1820            0 :  do itime=1,2
    1821            0 :    do isym=1,dmats%cryst%nsym
    1822            0 :      if (symtab(4, itime, isym) == 0) cycle
    1823            0 :      isym_cnt = isym_cnt + 1
    1824            0 :      if (itime == 1) then
    1825              :        associate (cmat => dmat_k(:, :, isym, 1))
    1826              : 
    1827              :        ! Find the order of the point-group operation (n in {1,2,3,4,6}).
    1828              :        ! NB: use a dedicated ierr_so for sym_order's own status -- passing the shared
    1829              :        ! accumulator "ierr" directly would have sym_order's intent(out) silently reset it
    1830              :        ! to 0 on every call, wiping out all previously-accumulated test failures.
    1831            0 :        call sym_order(dmats%cryst%symrel(:,:,isym), dmats%cryst%tnons(:,isym), n, isproper, trans, msg, ierr_so)
    1832            0 :        ABI_CHECK_IEQ(ierr_so, 0, msg)
    1833              : 
    1834            0 :        if (n > 1) then
    1835              :          ! Compute cmat^n
    1836            0 :          cmat_n = cmat
    1837            0 :          do j = 2, n
    1838            0 :            cmat_n = matmul(cmat, cmat_n)
    1839              :          end do
    1840              : 
    1841              :          ! Extract phase from Trace: phase = Tr(cmat^n) / nb
    1842            0 :          phase_L = zero
    1843            0 :          do j = 1, nb
    1844            0 :            phase_L = phase_L + cmat_n(j, j)
    1845              :          end do
    1846            0 :          phase_L = phase_L / nb
    1847              : 
    1848              :          ! Normalize cmat_n with phase_L to check if it's proportional to identity
    1849              :          err = zero
    1850            0 :          do j = 1, nb
    1851            0 :            cmat_n(j, j) = cmat_n(j, j) - phase_L
    1852              :          end do
    1853            0 :          err = maxval(abs(cmat_n))
    1854              : 
    1855              :          ! Analytic Bloch phase from the cumulative lattice translation T: D(S)^n = e^{-i k.T} I
    1856            0 :          phase_analytic = exp(cmplx(zero, -two_pi * dot_product(kk_ibz, real(trans, dp)), dp))
    1857              :          ! TEMPORARY DIAGNOSTIC (session experiment): applying S n times, for S a proper rotation
    1858              :          ! of order n, sweeps EXACTLY one full 2*pi turn -- for spinors D(2*pi) = -I always
    1859              :          ! (unconditionally, not a convention/branch-choice issue), so D(S)^n should pick up an
    1860              :          ! extra factor of -1 relative to the scalar-derived formula above, for EVERY proper
    1861              :          ! rotation (any n), regardless of itime (closure test is itime=1 only anyway).
    1862            0 :          if (dmats%dtset%nspinor == 2 .and. isproper) phase_analytic = -phase_analytic
    1863            0 :          phase_err = abs(phase_L - phase_analytic)
    1864              : 
    1865              :          ! NOTE: for IMPROPER operations (isproper=.false.), phase_err is consistently
    1866              :          ! found to be exactly 2 (phase_L = -phase_analytic, a clean sign flip, not noise)
    1867              :          ! -- the same class of unresolved cgtk_rotate phase-convention ambiguity already
    1868              :          ! flagged (but left diagnostic-only, not gating ierr) in the inverse-relation test
    1869              :          ! above ("still under investigation"). Follow that same precedent here: gate ierr
    1870              :          ! on proportionality (err) and unit modulus, but not on phase_err for improper ops.
    1871            0 :          if (err >= DTOL .or. abs(abs(phase_L) - one) > DTOL .or. (isproper .and. phase_err > DTOL)) ierr = ierr + 1
    1872              :          call sym_dicts(isym_cnt)%set("closure_ok", &
    1873            0 :            s=yesno(err < DTOL .and. abs(abs(phase_L) - one) <= DTOL .and. (.not. isproper .or. phase_err <= DTOL)))
    1874            0 :          call sym_dicts(isym_cnt)%set("closure_err", r=err)
    1875            0 :          call sym_dicts(isym_cnt)%set("closure_n", i=n)
    1876            0 :          call sym_dicts(isym_cnt)%set("isproper", s=yesno(isproper))
    1877            0 :          call sym_dicts(isym_cnt)%set("closure_phase", s=sjoin(ftoa(real(phase_L)), " + i ", ftoa(aimag(phase_L))))
    1878              :          call sym_dicts(isym_cnt)%set("closure_phase_analytic", &
    1879            0 :            s=sjoin(ftoa(real(phase_analytic)), " + i ", ftoa(aimag(phase_analytic))))
    1880            0 :          call sym_dicts(isym_cnt)%set("closure_phase_err", r=phase_err)
    1881              :        end if
    1882              :        end associate
    1883              :      end if
    1884              :    end do
    1885              :  end do
    1886              : 
    1887            0 :  if (allocated(sym_dicts)) then
    1888            0 :    call ydoc%add_dictlist(tag, isym_cnt, sym_dicts)
    1889            0 :    do isym = 1, isym_cnt
    1890            0 :      call sym_dicts(isym)%free()
    1891              :    end do
    1892            0 :    ABI_FREE(sym_dicts)
    1893              :  end if
    1894              : 
    1895            0 :  ABI_FREE(cmat_n)
    1896              : 
    1897            0 : end subroutine dmats_check_one_k
    1898              : !!***
    1899              : 
    1900              : !----------------------------------------------------------------------
    1901              : 
    1902              : !!****f* m_classify_bands/dmats_check
    1903              : !! NAME
    1904              : !! dmats_check
    1905              : !!
    1906              : !! FUNCTION
    1907              : !!  Verify the fundamental point-group algebraic properties of
    1908              : !!
    1909              : !!  D^{k}_{mn}(S) = < \psi_{m, Sk} | S | \psi_{n, k} >
    1910              : !!
    1911              : !!  The following algebraic tests are performed:
    1912              : !!
    1913              : !!  1. Unitarity (mandatory): || D^\dagger(k, S) D(k, S) - I || < DTOL
    1914              : !!  2. Identity operator: D(E, k) = I, for isym = 1
    1915              : !!  3. Inverse relation: D^{Sk}(S^{-1}) \propto D^{k}(S)^\dagger
    1916              : !!  4. Group multiplication, including time-reversal: D^{k}(A B) \propto D^{k}(A) D^{k}(B)
    1917              : !!     for A, B each either a pure spatial symmetry or a spatial symmetry composed with
    1918              : !!     time reversal (Wigner co-representation composition law, see below).
    1919              : !!  5. Kramers/Theta^2 (only at TR-invariant k, e.g. TRIM points): D^{k}(\Theta) D^{k}(\Theta)^*
    1920              : !!     = +I EXACTLY (Theta^2=+1 for scalar wavefunctions), for isym=1 (pure time reversal), itime=2.
    1921              : !!  6. Character class-function test (itime=1 only): |Tr D^{k}(S)| is the same for every S in a
    1922              : !!     given conjugacy class of the little group of k, as computed by get_classes (m_ptgroups.F90).
    1923              : !!     |trace| (not the raw complex trace) is used deliberately: it is invariant under both the
    1924              : !!     tabulated-vs-literal-composition phase ambiguity (see the note above on test 4) and any
    1925              : !!     per-band wavefunction gauge choice, since a similarity transform composed with an overall
    1926              : !!     scalar phase always preserves |trace| exactly, with no analytic phase tracking needed.
    1927              : !!
    1928              : !!     Caution for anyone editing this test: get_classes takes EXPLICIT-SHAPE dummy arguments
    1929              : !!     sized from its own nsym argument (here, the little-group order nsym_lg, which varies by
    1930              : !!     k-point and is in general smaller than cryst%nsym). Passing whole arrays declared with a
    1931              : !!     LARGER fixed bound (e.g. sized to cryst%nsym) works via Fortran sequence association but
    1932              : !!     silently corrupts the result for any 2D array (elements_idx) whenever nsym_lg != cryst%nsym,
    1933              : !!     because the callee writes using an nsym_lg-based column-major stride while the caller would
    1934              : !!     read back using a cryst%nsym-based stride. (1D arrays, like nelements, are unaffected: their
    1935              : !!     offset does not depend on the declared bound.) The fix is to allocate elements_idx to EXACTLY
    1936              : !!     (nsym_lg, nsym_lg), as done here and in esymm_init (m_esymm.F90). This is not a hypothetical
    1937              : !!     concern: it was hit and diagnosed during development of this test, and produced clean-looking
    1938              : !!     but WRONG class assignments (e.g. an isym reported as belonging to two different classes
    1939              : !!     depending on which entry point read it) rather than an obvious crash.
    1940              : !!
    1941              : !!  Note on algebraic structure:
    1942              : !!  ABINIT's symmetries correspond to a *right homomorphism*, meaning the product
    1943              : !!  of two symmetries S_3 = S_1 S_2 (where the rotation parts are R_3 = R_1 R_2)
    1944              : !!  yields representations that compose as D(S_3) \propto D(S_1) D(S_2).
    1945              : !!
    1946              : !!  For fractional translations in non-symmorphic groups or due to G_0 vector mappings,
    1947              : !!  the exact analytical phases between operations can become extremely complex.
    1948              : !!  Therefore, the inverse relation and group multiplication tests extract the relative
    1949              : !!  phase dynamically using the Frobenius inner product Phase = Tr(A^\dagger B) / nb.
    1950              : !!  As long as the residual after phase-normalization is within DTOL, the matrices
    1951              : !!  strictly satisfy the projective representations of the space group.
    1952              : !!
    1953              : !!  Diagnostic value of test 4 (group multiplication): unitarity (test 1) and the
    1954              : !!  inverse relation (test 3) are, by construction, blind to a bug in which every
    1955              : !!  D(S) is silently replaced by D(S)^{-1} = D(S^{-1}): both tests only ever compare
    1956              : !!  a matrix against itself or its own inverse, so swapping S <-> S^{-1} consistently
    1957              : !!  leaves them satisfied. The group multiplication test does NOT have this blind
    1958              : !!  spot for non-involutory S (S^2 != E), since D(S)<->D(S)^{-1} breaks the
    1959              : !!  non-commutative composition D(S_1 S_2) \propto D(S_1) D(S_2) as soon as one of the
    1960              : !!  three operations involved has order > 2 (D(S_3) becomes proportional to
    1961              : !!  D(S_1) D(S_2)^t instead). This is exactly how a real S<->S^{-1} mislabeling bug in
    1962              : !!  dmats_init was caught during development; see the NOTES section of dmats_init's
    1963              : !!  SOURCE header for the full explanation and fix.
    1964              : !!
    1965              : !!  Test 4 loops over all itime1, itime2 in {1,2}, not just the pure-spatial (1,1)
    1966              : !!  case: this closes an analogous blind spot for time-reversal-related matrices
    1967              : !!  D(k, S, itime=2), which unitarity/inverse-relation alone cannot detect either.
    1968              : !!  The composite operation (isym3, itime3) is found from: (a) the ROTATION part,
    1969              : !!  which composes as symrel(isym1).symrel(isym2) regardless of itime1/itime2,
    1970              : !!  because \hat\Theta commutes with any pure spatial coordinate transformation
    1971              : !!  acting on the full (not just periodic-part) wavefunction; and (b) the
    1972              : !!  unitary/antiunitary character, itime3 = 1 + mod((itime1-1)+(itime2-1), 2),
    1973              : !!  i.e. antiunitary o antiunitary = unitary (valid for scalar wavefunctions,
    1974              : !!  Theta^2=+1; dmats_init hard-requires nspinor=1). The matrix relation itself
    1975              : !!  follows the Wigner co-representation composition law (Bradley & Cracknell,
    1976              : !!  sec. 7.3): D(A B) = D(A) D(B) if A is unitary, D(A B) = D(A) D(B)^* if A is
    1977              : !!  antiunitary (A applied after B, A=(isym1,itime1)).
    1978              : !!
    1979              : !! SOURCE
    1980              : 
    1981            0 : subroutine dmats_check(dmats, units, prtvol, header)
    1982              : 
    1983              : !Arguments ------------------------------------
    1984              :  class(dmats_t),intent(in) :: dmats
    1985              :  integer,intent(in) :: units(:), prtvol
    1986              :  character(len=*),optional,intent(in) :: header
    1987              : 
    1988              : !Local variables-------------------------------
    1989              :  integer :: spin, ik_ibz, ierr
    1990              :  character(len=500) :: msg
    1991              :  type(yamldoc_t) :: ydoc
    1992              : ! *************************************************************************
    1993              : 
    1994            0 :  msg = 'Info on the dmats_t'
    1995            0 :  if (present(header)) msg = trim(adjustl(header))
    1996            0 :  ydoc = yamldoc_open(tag="dmats", info=trim(msg))
    1997              : 
    1998            0 :  ierr = 0
    1999            0 :  do spin=1,size(dmats%for_spin)
    2000              :    ! Loop over k-points in the IBZ.
    2001            0 :    do ik_ibz=1,dmats%ks_ebands%nkpt
    2002              :      call dmats_check_one_k(dmats, spin, dmats%ks_ebands%kptns(:, ik_ibz), &
    2003              :                              dmats%for_spin(spin)%value(:, :, :, :, ik_ibz), units, prtvol, &
    2004              :                              sjoin("kpt_", ktoa(dmats%ks_ebands%kptns(:, ik_ibz)), "_spin_", itoa(spin)), &
    2005            0 :                              ydoc, ierr)
    2006              :    end do ! ik_ibz
    2007              :  end do ! spin
    2008              : 
    2009            0 :  call ydoc%write_units_and_free(units)
    2010              : 
    2011            0 :  if (ierr /= 0) then
    2012            0 :    ABI_ERROR(sjoin("dmats are not unitary or failed tests! ierr:", itoa(ierr)))
    2013              :  end if
    2014              : 
    2015            0 : end subroutine dmats_check
    2016              : !!***
    2017              : 
    2018              : !!****f* m_classify_bands/dmats_get_star_dmats
    2019              : !! NAME
    2020              : !! dmats_get_star_dmats
    2021              : !!
    2022              : !! FUNCTION
    2023              : !!  Build the D-matrices at a full-BZ k-point k' = S0.k_ibz (S0 = (isym0,itime0)), a
    2024              : !!  symmetry-star image of an already-computed IBZ k-point, using ONLY data already
    2025              : !!  stored in dmats (for_spin, multable, toinv, cryst) -- no WFK re-read (wfd is freed
    2026              : !!  by dmats_init once dmats%for_spin has been built).
    2027              : !!
    2028              : !!  Define the wavefunction gauge at k' as |n,k'> := S0|n,k_ibz>. Then for any
    2029              : !!  g=(isym,itime) that stabilizes k' (g.k' = k' mod G), with h := S0^{-1}.g.S0
    2030              : !!  (physical operator composition):
    2031              : !!
    2032              : !!    D^{k'}_{mn}(g) = <m,k'|g|n,k'> = <m,k_ibz|S0^{-1} g S0|n,k_ibz> = D^{k_ibz}_{mn}(h)
    2033              : !!
    2034              : !!  exactly. IMPORTANT: resolving this h to a tabulated isym index via dmats%multable is
    2035              : !!  NOT simply "two multable products in the S0^{-1},g,S0 order": the file's k-vector
    2036              : !!  little-group test phi(s):=symrel(s)^t is an ANTI-homomorphism of multable's abstract
    2037              : !!  (plain, non-transposed) group law -- phi(s1 applied after s2) = phi(s2).phi(s1), order
    2038              : !!  REVERSED (same anti-homomorphism already flagged in dmats_check_one_k's NOTES on
    2039              : !!  get_classes). Working through phi(h)=phi(S0)^{-1}.phi(g).phi(S0) with this reversal
    2040              : !!  shows the correct tabulated composition is actually h = S0.g.S0^{-1} (see the detailed
    2041              : !!  derivation in this routine's SOURCE, right before the two multable calls).
    2042              : !!
    2043              : !!  Since dmats%multable(1,...) gives the "tabulated" symrel entry sharing h's rotation,
    2044              : !!  not the literal Seitz product, the literal h differs from it by a lattice vector L_h:
    2045              : !!
    2046              : !!    D^{k'}(g) = e^{-i 2pi k_ibz.L_h} * dmats%for_spin(spin)%value(:,:,isym_h,itime_h,ik_ibz)
    2047              : !!
    2048              : !!  L_h is accumulated through TWO nested multable compositions (h is itself the double
    2049              : !!  product S0.(g.S0^{-1})), using the same reversed-inverse-argument lookup already
    2050              : !!  validated in dmats_check_one_k's group-multiplication test (L = multable(2:4,
    2051              : !!  toinv(isym2), toinv(isym1)) for a product D(S1 S2), S1 applied after S2).
    2052              : !!
    2053              : !!  PROOF of L_h (worked from scratch, tabulated-operator algebra only): write
    2054              : !!  Sigma0^{-1} = Tab(isym0_inv) o T_{-L0} (L0 = toinv(2:4,isym0), from S0.Tab(isym0_inv) =
    2055              : !!  T_{L0}), and for ANY two tabulated elements A=Tab(a), B=Tab(b): A o B = T_{L(a,b)} o
    2056              : !!  Tab(multable(1,a,b)), L(a,b) = multable(2:4,a,b) -- the lattice correction sits on the
    2057              : !!  LEFT of the tabulated product, so it picks up a rotation (T_v o Tab(c) = Tab(c) o
    2058              : !!  T_{R(c)^{-1}.v}, equivalently R(c).T_v o Tab(c)... ) whenever it is pushed further left
    2059              : !!  past another rotation. Substituting Sigma0^{-1} into h_lit = S0.g.Sigma0^{-1} and
    2060              : !!  applying this rule twice (first at g.Tab(isym0_inv), then at S0.Tab(isym_tmp)) gives,
    2061              : !!  with NO free parameters left over:
    2062              : !!
    2063              : !!    isym_tmp = multable(1, isym_g, isym0_inv);  Ltmp = multable(2:4, isym_g, isym0_inv)
    2064              : !!    isym_h   = multable(1, isym0,  isym_tmp);   Lh2  = multable(2:4, isym0,  isym_tmp)
    2065              : !!    L_h      = R0.Ltmp + Lh2 - R_h.L0     (R0 = symrel(:,:,isym0), R_h = symrel(:,:,isym_h))
    2066              : !!
    2067              : !!  which matches the L_h computed in this routine's SOURCE exactly. This derivation used
    2068              : !!  ONLY real-space Seitz-operator algebra (unitarity of S0 plus associativity of operator
    2069              : !!  composition) -- no reciprocal-space bookkeeping is needed anywhere, so a genuinely
    2070              : !!  non-zero umklapp vector g0 in the little-group-of-k' test just below does NOT introduce
    2071              : !!  any extra phase on top of L_h: Bloch periodicity psi_{k+G} = psi_k is an exact identity,
    2072              : !!  not a gauge choice, so it never needed to be invoked in this chain. The one-step version
    2073              : !!  of this phase formula was independently, empirically validated (exact match in the
    2074              : !!  group-multiplication test on 768 tuples); this two-step formula is now ALSO proven, not
    2075              : !!  just extrapolated. If dmats_check_star still reports group-multiplication/closure
    2076              : !!  failures correlated with non-zero umklapp, the bug is therefore NOT in this phase
    2077              : !!  formula -- look instead at the (isym0,itime0) selection in
    2078              : !!  dmats_get_star_dmats_at_kpt (first-match-wins when k_ibz has a non-trivial little
    2079              : !!  group) or at the little-group-of-k' membership test just below.
    2080              : !!
    2081              : !! INPUTS
    2082              : !!  spin=Spin index.
    2083              : !!  ik_ibz=Index of the reference IBZ k-point (into dmats%ks_ebands%kptns).
    2084              : !!  isym0, itime0=Symmetry S0=(isym0,itime0) such that k' = S0.k_ibz.
    2085              : !!
    2086              : !! OUTPUT
    2087              : !!  dmat_star(:,:,:,:)=D-matrices (nb,nb,nsym,2) at k'. Caller-owned: allocated here
    2088              : !!    (ABI_MALLOC), must be freed by the caller (ABI_FREE).
    2089              : !!  msg, ierr=Error message and status (ierr=0 on success). No ABI_ERROR is raised here:
    2090              : !!    this routine is meant to be usable in a validation loop over many k', where the
    2091              : !!    caller decides whether to abort (e.g. via ABI_CHECK_IEQ(ierr, 0, msg)) or skip.
    2092              : !!
    2093              : !! SOURCE
    2094              : 
    2095            0 : subroutine dmats_get_star_dmats(dmats, spin, ik_ibz, isym0, itime0, dmat_star, msg, ierr)
    2096              : 
    2097              : !Arguments ------------------------------------
    2098              :  class(dmats_t),intent(in) :: dmats
    2099              :  integer,intent(in) :: spin, ik_ibz, isym0, itime0
    2100              :  complex(dp),allocatable,intent(out) :: dmat_star(:,:,:,:)
    2101              :  character(len=*),intent(out) :: msg
    2102              :  integer,intent(out) :: ierr
    2103              : 
    2104              : !Local variables-------------------------------
    2105              :  integer :: nsym, nb, isym, itime, isym_tmp, isym_h, itime_tmp, itime_h, isym0_inv, j
    2106              :  integer :: g0(3), g0_h(3)
    2107              :  real(dp) :: kk_ibz(3), kprime(3), kk_sk(3), tsign0, tsign
    2108              :  real(dp) :: L_h(3)
    2109              :  complex(dp) :: phase_h
    2110              : ! *********************************************************************
    2111              : 
    2112            0 :  ierr = 0; msg = ""
    2113            0 :  nsym = dmats%cryst%nsym
    2114            0 :  nb = dmats%brange_spin(2, spin) - dmats%brange_spin(1, spin) + 1
    2115            0 :  kk_ibz = dmats%ks_ebands%kptns(:, ik_ibz)
    2116              : 
    2117              :  ! k' = S0.k_ibz (symrel^t convention, consistent with dmats_init/dmats_check_one_k).
    2118            0 :  tsign0 = merge(one, -one, itime0 == 1)
    2119            0 :  kprime = tsign0 * matmul(transpose(real(dmats%cryst%symrel(:,:,isym0), dp)), kk_ibz)
    2120              : 
    2121            0 :  isym0_inv = dmats%toinv(1, isym0)
    2122            0 :  if (isym0_inv == 0) then
    2123            0 :    ierr = 1; msg = "Could not find inverse of isym0"; return
    2124              :  end if
    2125              : 
    2126            0 :  ABI_MALLOC(dmat_star, (nb, nb, nsym, 2))
    2127            0 :  dmat_star = czero
    2128              : 
    2129            0 :  do itime=1,2
    2130            0 :    tsign = merge(one, -one, itime == 1)
    2131            0 :    do isym=1,nsym
    2132              : 
    2133              :      ! Is g=(isym,itime) in the little group of k' = S0.k_ibz?
    2134            0 :      kk_sk = tsign * matmul(transpose(real(dmats%cryst%symrel(:,:,isym), dp)), kprime)
    2135            0 :      g0 = nint(kprime - kk_sk)
    2136            0 :      if (.not. all(abs(kprime - kk_sk - g0) < tol8)) then
    2137              :        ! Not in little group of k': identity placeholder, mirrors dmats%for_spin's own convention.
    2138            0 :        do j=1,nb
    2139            0 :          dmat_star(j, j, isym, itime) = cone
    2140              :        end do
    2141            0 :        cycle
    2142              :      end if
    2143              : 
    2144              :      ! Compose h = S0.g.S0^{-1} via TWO multable compositions: multable(1,s1,s2) = index of
    2145              :      ! "s1 applied after s2" (see sg_multable), i.e. the plain, NON-transposed real-space
    2146              :      ! rotation-matrix product R(s1).R(s2).
    2147              :      !
    2148              :      ! NOTE the conjugation direction: naively one would expect h = S0^{-1}.g.S0 (as in an
    2149              :      ! ordinary homomorphism), but the file's k-vector action phi(s) := symrel(s)^t is an
    2150              :      ! ANTI-homomorphism of the abstract (multable) group law: phi(s1 "applied after" s2) =
    2151              :      ! R(s1.s2)^t = R(s2)^t.R(s1)^t = phi(s2).phi(s1) -- composition order REVERSES (this is
    2152              :      ! the same anti-homomorphism already noted in dmats_check_one_k's NOTES on get_classes).
    2153              :      ! Requiring phi(h) = phi(S0)^{-1}.phi(g).phi(S0) (so that h stabilizes k_ibz whenever g
    2154              :      ! stabilizes k'=phi(S0).k_ibz) and using phi(A)phi(B)=phi(B.A) twice gives
    2155              :      ! phi(h) = phi(S0.g.S0^{-1}), i.e. h = S0.g.S0^{-1}, NOT S0^{-1}.g.S0.
    2156            0 :      isym_tmp = dmats%multable(1, isym, isym0_inv)         ! tmp = g . S0^{-1}
    2157            0 :      if (isym_tmp == 0) then
    2158            0 :        ierr = 2; msg = "multable(isym, isym0_inv) not found: group closure violated?"; return
    2159              :      end if
    2160            0 :      itime_tmp = 1 + mod((itime - 1) + (itime0 - 1), 2)
    2161              : 
    2162            0 :      isym_h = dmats%multable(1, isym0, isym_tmp)           ! h = S0 . tmp = S0.g.S0^{-1}
    2163            0 :      if (isym_h == 0) then
    2164            0 :        ierr = 2; msg = "multable(isym0, isym_tmp) not found: group closure violated?"; return
    2165              :      end if
    2166            0 :      itime_h = 1 + mod((itime0 - 1) + (itime_tmp - 1), 2)  ! always equals itime (parity self-cancels)
    2167            0 :      if (itime_h /= itime) then
    2168            0 :        ierr = 2; msg = "itime_h != itime_g: parity composition bug"; return
    2169              :      end if
    2170              : 
    2171              :      ! Defensive check: h must stabilize k_ibz by construction.
    2172            0 :      kk_sk = tsign * matmul(transpose(real(dmats%cryst%symrel(:,:,isym_h), dp)), kk_ibz)
    2173            0 :      g0_h = nint(kk_ibz - kk_sk)
    2174            0 :      if (.not. all(abs(kk_ibz - kk_sk - g0_h) < tol8)) then
    2175            0 :        ierr = 2; msg = "h does not stabilize k_ibz: composition bug"; return
    2176              :      end if
    2177              : 
    2178              :      ! Lattice-vector correction L_h, derived directly from Seitz algebra (verified against
    2179              :      ! the g=identity special case, where it must vanish exactly -- D(identity) = I with no
    2180              :      ! phase, always). Writing S0={R0,tau0}, g={Rg,taug}, and S0_inv_tab=dmats%toinv's TABULATED
    2181              :      ! entry for S0^{-1} (which equals the EXACT inverse only up to an extra lattice shift
    2182              :      ! m0 = R0^{-1}.L0, L0=toinv(2:4,isym0), since toinv only guarantees S0.S0_inv_tab={I,L0}):
    2183              :      !
    2184              :      !  tmp_literal := g . S0_inv_tab = {I, Ltmp} . TABULATED_tmp,  Ltmp = multable(2:4,isym,isym0_inv)
    2185              :      !  h_tab_literal := S0 . tmp_literal = {I, R0.Ltmp + Lh2} . TABULATED_h,  Lh2 = multable(2:4,isym0,isym_tmp)
    2186              :      !
    2187              :      ! h_tab_literal uses S0_inv_tab, not the EXACT inverse \hat S0^{-1} = {I,-m0}.S0_inv_tab; undoing
    2188              :      ! that extra {I,m0} shift (tracked through the same two compositions) gives the additional
    2189              :      ! correction -R_h.L0 (R_h=symrel(isym_h)), so that for g=identity (Ltmp=Lh2=L0, R_h=I) the
    2190              :      ! total L_h = R0.0 + L0 - I.L0 = 0 exactly, as required:
    2191              :      !
    2192              :      !   L_h = R0.Ltmp + Lh2 - R_h.L0
    2193              :      !
    2194              :      ! The resulting {I,L_h} pure-lattice-translation factor is applied AFTER TABULATED_h (which
    2195              :      ! stabilizes k_ibz), so the state is still at k_ibz when the translation phase is picked up:
    2196              :      ! phase_h = e^{-i 2pi k_ibz.L_h} (dot directly with k_ibz, not with a rotated k_ibz).
    2197              :      L_h = matmul(real(dmats%cryst%symrel(:,:,isym0), dp), real(dmats%multable(2:4, isym, isym0_inv), dp)) &
    2198              :          + real(dmats%multable(2:4, isym0, isym_tmp), dp) &
    2199            0 :          - matmul(real(dmats%cryst%symrel(:,:,isym_h), dp), real(dmats%toinv(2:4, isym0), dp))
    2200              : 
    2201            0 :      phase_h = exp(cmplx(zero, -two_pi * sum(kk_ibz * L_h), dp))
    2202            0 :      if (itime == 2) phase_h = conjg(phase_h)
    2203              : 
    2204            0 :      dmat_star(:, :, isym, itime) = phase_h * dmats%for_spin(spin)%value(:, :, isym_h, itime, ik_ibz)
    2205              :    end do
    2206              :  end do
    2207              : 
    2208            0 : end subroutine dmats_get_star_dmats
    2209              : !!***
    2210              : 
    2211              : !!****f* m_classify_bands/dmats_get_star_dmats_at_kpt
    2212              : !! NAME
    2213              : !! dmats_get_star_dmats_at_kpt
    2214              : !!
    2215              : !! FUNCTION
    2216              : !!  Convenience wrapper around dmats_get_star_dmats: given a raw full-BZ k-point kprime,
    2217              : !!  locate (ik_ibz, isym0, itime0) such that kprime = tsign0*symrel(isym0)^t.k_ibz (mod G),
    2218              : !!  scanning ALL IBZ k-points, then build the D-matrices at kprime.
    2219              : !!
    2220              : !!  Uses the SAME symrel^t convention as dmats_init/dmats_check_one_k (NOT kpts_map/listkk/
    2221              : !!  littlegroup_q, which use a symrec-based convention that can disagree with symrel^t
    2222              : !!  whenever symrel is not orthogonal in the reduced basis -- see the NOTES in
    2223              : !!  dmats_check_one_k on this exact point).
    2224              : !!
    2225              : !! INPUTS
    2226              : !!  spin=Spin index.
    2227              : !!  kprime(3)=Target k-point (reduced coords) in the full BZ.
    2228              : !!
    2229              : !! OUTPUT
    2230              : !!  dmat_star(:,:,:,:)=D-matrices (nb,nb,nsym,2) at kprime. Caller-owned (ABI_MALLOC/ABI_FREE).
    2231              : !!  ik_ibz, isym0, itime0=The located triple, returned so the caller can report/reuse it.
    2232              : !!  msg, ierr=Error message and status (ierr=0 on success, /=0 if kprime is not the star
    2233              : !!    image of any IBZ k-point in dmats%ks_ebands).
    2234              : !!
    2235              : !! SOURCE
    2236              : 
    2237            0 : subroutine dmats_get_star_dmats_at_kpt(dmats, spin, kprime, dmat_star, ik_ibz, isym0, itime0, msg, ierr)
    2238              : 
    2239              : !Arguments ------------------------------------
    2240              :  class(dmats_t),intent(in) :: dmats
    2241              :  integer,intent(in) :: spin
    2242              :  real(dp),intent(in) :: kprime(3)
    2243              :  complex(dp),allocatable,intent(out) :: dmat_star(:,:,:,:)
    2244              :  integer,intent(out) :: ik_ibz, isym0, itime0
    2245              :  character(len=*),intent(out) :: msg
    2246              :  integer,intent(out) :: ierr
    2247              : 
    2248              : !Local variables-------------------------------
    2249              :  integer :: jk_ibz, jsym, jtime
    2250              :  real(dp) :: kk_ibz(3), kk_sk(3), tsign, g0(3), resid, best_resid
    2251              :  logical :: found
    2252              : ! *********************************************************************
    2253              : 
    2254            0 :  ierr = 0; msg = ""; found = .False.
    2255            0 :  ik_ibz = -1; isym0 = -1; itime0 = -1
    2256            0 :  best_resid = huge(one)
    2257              : 
    2258            0 :  search: do jk_ibz=1,dmats%ks_ebands%nkpt
    2259            0 :    kk_ibz = dmats%ks_ebands%kptns(:, jk_ibz)
    2260            0 :    do jtime=1,2
    2261            0 :      tsign = merge(one, -one, jtime == 1)
    2262            0 :      do jsym=1,dmats%cryst%nsym
    2263            0 :        kk_sk = tsign * matmul(transpose(real(dmats%cryst%symrel(:,:,jsym), dp)), kk_ibz)
    2264            0 :        g0 = nint(kprime - kk_sk)
    2265            0 :        resid = maxval(abs(kprime - kk_sk - g0))
    2266            0 :        best_resid = min(best_resid, resid)
    2267            0 :        if (all(abs(kprime - kk_sk - g0) < tol8)) then
    2268            0 :          ik_ibz = jk_ibz; isym0 = jsym; itime0 = jtime; found = .True.
    2269              :          exit search
    2270              :        end if
    2271              :      end do
    2272              :    end do
    2273              :  end do search
    2274              : 
    2275              :  if (.not. found) then
    2276            0 :    ierr = 1
    2277              :    msg = sjoin("kprime:", ktoa(kprime), "is not the symmetry-star image of any IBZ k-point", &
    2278            0 :                "(best residual found:", ftoa(best_resid), ")")
    2279            0 :    return
    2280              :  end if
    2281              : 
    2282            0 :  call dmats_get_star_dmats(dmats, spin, ik_ibz, isym0, itime0, dmat_star, msg, ierr)
    2283              : 
    2284            0 : end subroutine dmats_get_star_dmats_at_kpt
    2285              : !!***
    2286              : 
    2287              : !!****f* m_classify_bands/dmats_check_star
    2288              : !! NAME
    2289              : !! dmats_check_star
    2290              : !!
    2291              : !! FUNCTION
    2292              : !!  Locate a full-BZ k-point kprime as the symmetry-star image of an IBZ k-point, build
    2293              : !!  its D-matrices via dmats_get_star_dmats_at_kpt (pure group-theory reconstruction, no
    2294              : !!  WFK re-read), and run the full dmats_check_one_k test battery (unitarity, identity,
    2295              : !!  class character, Kramers, inverse relation, group multiplication, S^n closure) on
    2296              : !!  them. This is an independent test of the multable/toinv/conjugation logic used by
    2297              : !!  dmats_get_star_dmats, in an off-little-group regime dmats_check never exercises.
    2298              : !!
    2299              : !! INPUTS
    2300              : !!  spin=Spin index.
    2301              : !!  kprime(3)=Target k-point (reduced coords) in the full BZ.
    2302              : !!  units(:), prtvol=Output units and verbosity.
    2303              : !!
    2304              : !! OUTPUT
    2305              : !!  ierr=0 if kprime was located and all sub-tests passed, /=0 otherwise.
    2306              : !!
    2307              : !! SOURCE
    2308              : 
    2309            0 : subroutine dmats_check_star(dmats, spin, kprime, units, prtvol, ierr)
    2310              : 
    2311              : !Arguments ------------------------------------
    2312              :  class(dmats_t),intent(in) :: dmats
    2313              :  integer,intent(in) :: spin, units(:), prtvol
    2314              :  real(dp),intent(in) :: kprime(3)
    2315              :  integer,intent(out) :: ierr
    2316              : 
    2317              : !Local variables-------------------------------
    2318              :  integer :: ik_ibz, isym0, itime0
    2319              :  character(len=500) :: msg
    2320            0 :  complex(dp),allocatable :: dmat_star(:,:,:,:)
    2321              :  type(yamldoc_t) :: ydoc
    2322              :  ! Cross-check dmat_star (pure group theory) against genuinely WFK-computed D-matrices.
    2323              :  integer :: nsym, isym, itime, isym1, itime1, n_cross_ok, n_cross_bad, nb
    2324              :  real(dp) :: tsign, kk_ibz(3), kk_g(3), g0(3), maxdiff
    2325            0 :  complex(dp),allocatable :: d_composed(:,:)
    2326              : ! *********************************************************************
    2327              : 
    2328              :  ierr = 0
    2329            0 :  call dmats_get_star_dmats_at_kpt(dmats, spin, kprime, dmat_star, ik_ibz, isym0, itime0, msg, ierr)
    2330            0 :  if (ierr /= 0) then
    2331            0 :    call wrtout(units, sjoin("dmats_check_star: get_star_dmats_at_kpt failed:", msg))
    2332            0 :    return
    2333              :  end if
    2334              : 
    2335              :  ydoc = yamldoc_open(tag="dmats_star", &
    2336              :    info=sjoin("Star k-point check: kprime=", ktoa(kprime), ", ik_ibz=", itoa(ik_ibz), &
    2337            0 :               ", isym0=", itoa(isym0), ", itime0=", itoa(itime0)))
    2338              : 
    2339              :  call dmats_check_one_k(dmats, spin, kprime, dmat_star, units, prtvol, &
    2340            0 :                          sjoin("starkpt_", ktoa(kprime), "_spin_", itoa(spin)), ydoc, ierr)
    2341              : 
    2342            0 :  call ydoc%write_units_and_free(units)
    2343              : 
    2344              :  ! Cross-check: dmat_star (pure group theory, no WFK access) vs D(S1) computed genuinely
    2345              :  ! from real wavefunction data (dmats_init's own non-little-group branch), for S1 := g.S0,
    2346              :  ! g any little-group element of kprime. Physically D(S1) = dmat_star(g) @ D(S0) (composing
    2347              :  ! the crossing rotation S0 with the k'-internal rotation g: <l,k'|g.S0|n,k_ibz> =
    2348              :  ! sum_m <l,k'|g|m,k'><m,k'|S0|n,k_ibz>, a resolution of identity over the {psi_m,k'}
    2349              :  ! basis). Both sides are now independently available (dmat_star via group theory,
    2350              :  ! D(S0)/D(S1) via genuine WFK data through dmats_init's generalized non-little-group
    2351              :  ! branch), so this directly tests whether dmat_star's implicit gauge assumption (that
    2352              :  ! |psi,k'> := S0|psi,k_ibz>) matches the physical wavefunction gstore_compute/kpts_map
    2353              :  ! would independently reconstruct -- the validation-coverage gap this whole
    2354              :  ! generalization was built to close (see gstore_symmetrize_status memory). dmat_star's
    2355              :  ! own placeholder-identity entries (non-little-group g) cannot be told apart from a
    2356              :  ! genuine identity result by value alone, so little-group membership of kprime is
    2357              :  ! re-derived directly here, mirroring dmats_get_star_dmats's own test.
    2358            0 :  nsym = dmats%cryst%nsym
    2359            0 :  nb = dmats%brange_spin(2, spin) - dmats%brange_spin(1, spin) + 1
    2360            0 :  kk_ibz = dmats%ks_ebands%kptns(:, ik_ibz)
    2361            0 :  ABI_MALLOC(d_composed, (nb, nb))
    2362            0 :  n_cross_ok = 0; n_cross_bad = 0
    2363              : 
    2364            0 :  do itime=1,2
    2365            0 :    tsign = merge(one, -one, itime == 1)
    2366            0 :    do isym=1,nsym
    2367            0 :      kk_g = tsign * matmul(transpose(real(dmats%cryst%symrel(:,:,isym), dp)), kprime)
    2368            0 :      g0 = nint(kprime - kk_g)
    2369            0 :      if (.not. all(abs(kprime - kk_g - g0) < tol8)) cycle  ! Not in the little group of kprime.
    2370              : 
    2371            0 :      isym1 = dmats%multable(1, isym, isym0)
    2372            0 :      itime1 = 1 + mod((itime - 1) + (itime0 - 1), 2)
    2373            0 :      if (isym1 == 0) then
    2374            0 :        call wrtout(units, " dmats_check_star cross-check: multable(isym,isym0) not found, skipping")
    2375            0 :        cycle
    2376              :      end if
    2377              : 
    2378            0 :      d_composed = matmul(dmat_star(:,:,isym,itime), dmats%for_spin(spin)%value(:,:,isym0,itime0,ik_ibz))
    2379            0 :      maxdiff = maxval(abs(d_composed - dmats%for_spin(spin)%value(:,:,isym1,itime1,ik_ibz)))
    2380            0 :      if (maxdiff < tol6) then
    2381            0 :        n_cross_ok = n_cross_ok + 1
    2382              :      else
    2383            0 :        n_cross_bad = n_cross_bad + 1
    2384            0 :        if (prtvol > 0) then
    2385              :          write(msg,'(a,7(i0,1x),a,es12.4)') &
    2386            0 :            " dmats_check_star cross-check FAIL: ik_ibz,isym0,itime0,isym,itime,isym1,itime1= ", &
    2387            0 :            ik_ibz, isym0, itime0, isym, itime, isym1, itime1, " maxdiff= ", maxdiff
    2388            0 :          call wrtout(units, msg)
    2389              :        end if
    2390              :      end if
    2391              :    end do
    2392              :  end do
    2393              : 
    2394              :  call wrtout(units, sjoin(" dmats_check_star cross-check (dmat_star vs genuine WFK D-matrices): ", &
    2395            0 :    itoa(n_cross_ok), "/", itoa(n_cross_ok + n_cross_bad), " composed relations agree"))
    2396            0 :  if (n_cross_bad > 0) ierr = ierr + 1
    2397              : 
    2398            0 :  ABI_FREE(d_composed)
    2399            0 :  ABI_FREE(dmat_star)
    2400              : 
    2401            0 : end subroutine dmats_check_star
    2402              : !!***
    2403              : 
    2404              : !!****f* m_classify_bands/dmats_classify
    2405              : !! NAME
    2406              : !! dmats_classify
    2407              : !!
    2408              : !! FUNCTION
    2409              : !!  Classify the KS states based on the computed representation matrices (dmats)
    2410              : !!  using the irreducible representations of the little group of k.
    2411              : !!
    2412              : !! SOURCE
    2413              : 
    2414            0 : subroutine dmats_classify(dmats, prtvol)
    2415              : 
    2416              : !Arguments ------------------------------------
    2417              :  class(dmats_t), target, intent(in) :: dmats
    2418              :  integer,intent(in) :: prtvol
    2419              : 
    2420              : !Local variables-------------------------------
    2421            0 :  type(esymm_t) :: Bsym
    2422              :  integer :: spin, bstart, nb, ik_ibz, idg, iclass, isym_class, sym_idx, isym, tr_isym, ib_start, ib_stop
    2423            0 :  real(dp), pointer :: ene_k(:)
    2424              :  real(dp) :: kk_ibz(3)
    2425              : ! *************************************************************************
    2426              : 
    2427            0 :  do spin=1, size(dmats%for_spin)
    2428            0 :    bstart = dmats%brange_spin(1, spin)
    2429            0 :    nb = dmats%brange_spin(2, spin) - bstart + 1
    2430              : 
    2431            0 :    do ik_ibz=1, dmats%ks_ebands%nkpt
    2432            0 :      kk_ibz = dmats%ks_ebands%kptns(:, ik_ibz)
    2433            0 :      ene_k => dmats%ks_ebands%eig(bstart:dmats%brange_spin(2, spin), ik_ibz, spin)
    2434              : 
    2435              :      !only_trace = .false.
    2436              :      call Bsym%init(kk_ibz, dmats%cryst, .false., dmats%ks_ebands%nspinor, &
    2437            0 :                     bstart, nb, dmats%dtset%symsigma_de, ene_k, tol3)
    2438              : 
    2439            0 :      if (Bsym%err_status /= 0) cycle
    2440              : 
    2441            0 :      do idg=1, Bsym%ndegs
    2442            0 :        ib_start = Bsym%degs_bounds(1, idg) ! relative to bstart
    2443            0 :        ib_stop  = Bsym%degs_bounds(2, idg)
    2444              : 
    2445            0 :        sym_idx = 0
    2446            0 :        do iclass=1, Bsym%nclass
    2447            0 :          do isym_class=1, Bsym%nelements(iclass)
    2448            0 :            sym_idx = sym_idx + 1
    2449            0 :            isym = Bsym%sgk2symrec(sym_idx)
    2450            0 :            associate(cmat => dmats%for_spin(spin)%value(:,:, isym, 1, ik_ibz))
    2451            0 :            Bsym%Calc_irreps(idg)%mat(:,:,sym_idx) = cmat(ib_start:ib_stop, ib_start:ib_stop)
    2452            0 :            Bsym%Calc_irreps(idg)%trace(sym_idx) = get_trace(Bsym%Calc_irreps(idg)%mat(:,:,sym_idx))
    2453              :            end associate
    2454              :          end do
    2455              :        end do
    2456              : 
    2457            0 :        if (Bsym%can_use_tr) then
    2458            0 :          do tr_isym=1, Bsym%nsym_trgk
    2459            0 :            isym = Bsym%tr_sgk2symrec(tr_isym)
    2460            0 :            associate(cmat => dmats%for_spin(spin)%value(:,:, isym, 2, ik_ibz))
    2461            0 :            Bsym%trCalc_irreps(idg)%mat(:,:,tr_isym) = cmat(ib_start:ib_stop, ib_start:ib_stop)
    2462            0 :            Bsym%trCalc_irreps(idg)%trace(tr_isym) = get_trace(Bsym%trCalc_irreps(idg)%mat(:,:,tr_isym))
    2463              :            end associate
    2464              :          end do
    2465              :        end if
    2466              :      end do
    2467              : 
    2468            0 :      call Bsym%finalize(prtvol)
    2469            0 :      call Bsym%print([std_out, ab_out], prtvol=prtvol)
    2470            0 :      call esymm_free(Bsym)
    2471              :    end do
    2472              :  end do
    2473              : 
    2474            0 : end subroutine dmats_classify
    2475              : !!***
    2476              : 
    2477              : !----------------------------------------------------------------------
    2478              : 
    2479            0 : end module m_classify_bands
    2480              : !!***
        

Generated by: LCOV version 2.3-1