2019-12-04 18:32:00 +01:00
2016-02-01 14:06:41 +01:00
################################################################################
#
# TRIQS: a Toolbox for Research in Interacting Quantum Systems
#
# Copyright (C) 2011 by M. Ferrero, O. Parcollet
#
# DFT tools: Copyright (C) 2011 by M. Aichhorn, L. Pourovskii, V. Vildosola
#
# PLOVasp: Copyright (C) 2015 by O. E. Peil
#
# TRIQS is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# TRIQS is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# TRIQS. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
2015-11-20 18:54:51 +01:00
r """
2019-12-04 18:32:00 +01:00
plovasp . proj_group
== == == == == == == == ==
2015-11-13 18:15:21 +01:00
2015-11-20 18:54:51 +01:00
Storage and manipulation of projector groups .
"""
2015-11-13 18:15:21 +01:00
import numpy as np
2020-04-08 21:35:59 +02:00
from . proj_shell import ComplementShell
2015-11-13 18:15:21 +01:00
np . set_printoptions ( suppress = True )
################################################################################
################################################################################
#
# class ProjectorGroup
#
################################################################################
################################################################################
class ProjectorGroup :
"""
Container of projectors defined within a certain energy window .
The constructor selects a subset of projectors according to
the parameters from the config - file ( passed in ` pars ` ) .
Parameters :
2019-12-04 18:32:00 +01:00
- gr_pars ( dict ) : group parameters from the config - file
- shells ( [ ProjectorShell ] ) : array of ProjectorShell objects
- eigvals ( numpy . array ) : array of KS eigenvalues
2015-11-13 18:15:21 +01:00
"""
2015-11-19 16:01:05 +01:00
def __init__ ( self , gr_pars , shells , eigvals ) :
2015-11-13 18:15:21 +01:00
"""
Constructor
"""
self . emin , self . emax = gr_pars [ ' ewindow ' ]
self . ishells = gr_pars [ ' shells ' ]
self . ortho = gr_pars [ ' normalize ' ]
self . normion = gr_pars [ ' normion ' ]
2019-07-12 16:04:10 +02:00
self . complement = gr_pars [ ' complement ' ]
2015-11-13 18:15:21 +01:00
self . shells = shells
# Determine the minimum and maximum band numbers
2019-06-27 09:04:22 +02:00
if ' bands ' in gr_pars :
nk , nband , ns_band = eigvals . shape
2023-01-23 21:40:57 +01:00
ib_win = np . zeros ( ( nk , ns_band , 2 ) , dtype = int )
2019-06-28 14:47:15 +02:00
ib_win [ : , : , 0 ] = gr_pars [ ' bands ' ] [ 0 ] - 1
ib_win [ : , : , 1 ] = gr_pars [ ' bands ' ] [ 1 ] - 1
2019-07-08 10:27:17 +02:00
ib_min = gr_pars [ ' bands ' ] [ 0 ] - 1
ib_max = gr_pars [ ' bands ' ] [ 1 ] - 1
2019-12-04 18:32:00 +01:00
2019-06-27 09:04:22 +02:00
else :
ib_win , ib_min , ib_max = self . select_bands ( eigvals )
2023-03-24 11:42:42 +01:00
2015-11-13 18:15:21 +01:00
self . ib_win = ib_win
self . ib_min = ib_min
self . ib_max = ib_max
self . nb_max = ib_max - ib_min + 1
2019-12-04 18:32:00 +01:00
2019-07-12 16:04:10 +02:00
if self . complement :
2019-06-28 14:47:15 +02:00
n_bands = self . ib_win [ : , : , 1 ] - self . ib_win [ : , : , 0 ] + 1
2019-06-27 16:26:22 +02:00
n_orbs = sum ( [ x . ndim for x in self . shells ] )
assert np . all ( n_bands == n_bands [ 0 , 0 ] ) , " At each band the same number of bands has to be selected for calculating the complement (to end up with an equal number of orbitals at each k-point). "
2019-06-28 14:47:15 +02:00
if n_orbs == n_bands [ 0 , 0 ] :
2019-07-12 16:04:10 +02:00
self . complement = False
2020-04-08 21:35:59 +02:00
print ( " \n WARNING: The total number of orbitals in this group is " )
print ( " equal to the number of bands. Setting COMPLEMENT to FALSE! \n " )
2019-12-04 18:32:00 +01:00
2015-11-13 18:15:21 +01:00
# Select projectors within the energy window
for ish in self . ishells :
shell = self . shells [ ish ]
shell . select_projectors ( ib_win , ib_min , ib_max )
2019-12-04 18:32:00 +01:00
2015-11-13 18:15:21 +01:00
################################################################################
#
# nelect_window
#
################################################################################
def nelect_window ( self , el_struct ) :
"""
Determines the total number of electrons within the window .
"""
self . nelect = 0
nk , ns_band , _ = self . ib_win . shape
2022-08-04 13:03:10 +02:00
rspin = 2.0 if ( ns_band == 1 and el_struct . nc_flag == False ) else 1.0
2020-04-08 21:35:59 +02:00
for isp in range ( ns_band ) :
for ik in range ( nk ) :
2015-11-13 18:15:21 +01:00
ib1 = self . ib_win [ ik , isp , 0 ]
2020-03-24 17:50:09 +01:00
ib2 = self . ib_win [ ik , isp , 1 ] + 1
2015-11-13 18:15:21 +01:00
occ = el_struct . ferw [ isp , ik , ib1 : ib2 ]
kwght = el_struct . kmesh [ ' kweights ' ] [ ik ]
self . nelect + = occ . sum ( ) * kwght * rspin
return self . nelect
################################################################################
#
# orthogonalize
#
################################################################################
def orthogonalize ( self ) :
"""
Orthogonalize a group of projectors .
There are two options for orthogonalizing projectors :
1. one ensures orthogonality on each site ( NORMION = True ) ;
2. one ensures orthogonality for subsets of sites ( NORMION = False ) ,
as , e . g . , in cluster calculations .
In order to handle various cases the strategy is first to build a
mapping that selects appropriate blocks of raw projectors , forms a
matrix consisting of these blocks , orthogonalize the matrix , and use
the mapping again to write the orthogonalized projectors back to the
projector arrays . Note that the blocks can comprise several projector arrays
contained in different projector shells .
2015-11-19 14:48:45 +01:00
The construction of block maps is performed in ' self.get_block_matrix_map() ' .
"""
# Quick exit if no normalization is requested
if not self . ortho :
return
block_maps , ndim = self . get_block_matrix_map ( )
2015-11-19 14:56:34 +01:00
_ , ns , nk , _ , _ = self . shells [ 0 ] . proj_win . shape
2023-01-23 21:40:57 +01:00
p_mat = np . zeros ( ( ndim , self . nb_max ) , dtype = complex )
2015-11-19 14:48:45 +01:00
# Note that 'ns' and 'nk' are the same for all shells
2020-04-08 21:35:59 +02:00
for isp in range ( ns ) :
for ik in range ( nk ) :
2015-11-19 14:48:45 +01:00
nb = self . ib_win [ ik , isp , 1 ] - self . ib_win [ ik , isp , 0 ] + 1
# Combine all projectors of the group to one block projector
for bl_map in block_maps :
p_mat [ : , : ] = 0.0 j # !!! Clean-up from the last k-point and block!
for ibl , block in enumerate ( bl_map ) :
i1 , i2 = block [ ' bmat_range ' ]
ish , ion = block [ ' shell_ion ' ]
2015-11-19 14:56:34 +01:00
nlm = i2 - i1 + 1
2015-11-19 14:48:45 +01:00
shell = self . shells [ ish ]
p_mat [ i1 : i2 , : nb ] = shell . proj_win [ ion , isp , ik , : nlm , : nb ]
# Now orthogonalize the obtained block projector
ibl_max = i2
p_orth , overl , eig = self . orthogonalize_projector_matrix ( p_mat [ : ibl_max , : nb ] )
# Distribute projectors back using the same mapping
for ibl , block in enumerate ( bl_map ) :
i1 , i2 = block [ ' bmat_range ' ]
ish , ion = block [ ' shell_ion ' ]
2015-11-19 14:56:34 +01:00
nlm = i2 - i1 + 1
2015-11-19 14:48:45 +01:00
shell = self . shells [ ish ]
shell . proj_win [ ion , isp , ik , : nlm , : nb ] = p_orth [ i1 : i2 , : nb ]
2019-12-04 18:32:00 +01:00
2019-06-27 15:21:07 +02:00
################################################################################
#
# calc_hk
#
################################################################################
def calc_hk ( self , eigvals ) :
"""
2019-06-28 14:47:15 +02:00
Calculate H ( k ) for a group by applying the projectors P
to the eigenvalues eps .
2019-12-04 18:32:00 +01:00
2019-06-28 14:47:15 +02:00
H_ij ( k ) = sum_l P * _il eps_l P_lj
2019-12-04 18:32:00 +01:00
2019-06-27 15:21:07 +02:00
"""
2019-12-04 18:32:00 +01:00
2019-07-02 11:00:21 +02:00
# here we abuse the get_block_matrix_map(), however, it only works
# if self.normion is false
temp = self . normion
self . normion = False
2019-06-27 15:21:07 +02:00
block_maps , ndim = self . get_block_matrix_map ( )
2019-07-02 11:00:21 +02:00
self . normion = temp
2019-06-27 15:21:07 +02:00
_ , ns , nk , _ , _ = self . shells [ 0 ] . proj_win . shape
2019-12-04 18:32:00 +01:00
2023-01-23 21:40:57 +01:00
self . hk = np . zeros ( ( ns , nk , ndim , ndim ) , dtype = complex )
2019-06-27 15:21:07 +02:00
# Note that 'ns' and 'nk' are the same for all shells
2020-04-08 21:35:59 +02:00
for isp in range ( ns ) :
for ik in range ( nk ) :
2019-06-27 15:21:07 +02:00
bmin = self . ib_win [ ik , isp , 0 ]
bmax = self . ib_win [ ik , isp , 1 ] + 1
2019-12-04 18:32:00 +01:00
nb = bmax - bmin
2023-01-23 21:40:57 +01:00
p_mat = np . zeros ( ( ndim , nb ) , dtype = complex )
2019-06-27 15:21:07 +02:00
#print(bmin,bmax,nb)
# Combine all projectors of the group to one block projector
for bl_map in block_maps :
p_mat [ : , : ] = 0.0 j # !!! Clean-up from the last k-point and block!
for ibl , block in enumerate ( bl_map ) :
i1 , i2 = block [ ' bmat_range ' ]
ish , ion = block [ ' shell_ion ' ]
nlm = i2 - i1 + 1
shell = self . shells [ ish ]
p_mat [ i1 : i2 , : nb ] = shell . proj_win [ ion , isp , ik , : nlm , : nb ]
2019-12-04 18:32:00 +01:00
2019-07-02 11:00:21 +02:00
self . hk [ isp , ik , : , : ] = np . dot ( p_mat * eigvals [ ik , bmin : bmax , isp ] ,
p_mat . transpose ( ) . conjugate ( ) )
2019-06-27 15:21:07 +02:00
################################################################################
#
# complement
#
################################################################################
2019-07-12 16:04:10 +02:00
def calc_complement ( self , eigvals ) :
2019-06-27 15:21:07 +02:00
"""
Calculate the complement for a group of projectors .
2019-12-04 18:32:00 +01:00
2023-06-15 21:09:14 +02:00
This leads to quadtratic projectors : math : ` P = < l | n > ` by using a Gram - Schmidt .
2019-12-04 18:32:00 +01:00
2019-06-28 14:47:15 +02:00
The projector on the orthogonal complement of the existing projectors
2023-06-15 21:09:14 +02:00
: math : ` | l > ` is : math : ` P ^ u = 1 - sum_l | l > < l | `
2019-06-28 14:47:15 +02:00
We get candidates for complement projectors by applying P ^ u to a Bloch
2023-06-15 21:09:14 +02:00
state : math : ` | n > ` : : math : ` | l * > = P ^ u | n > ` . For numerical stability we select that Bloch
state which leads to the : math : ` | l * > ` with the largest norm ( that corresponds to
that Bloch state with the smallest overlap with the space spanned by : math : ` | l > ` )
We normalize : math : ` | l * > ` and add it to : math : ` | l > ` . We do so untill we have as many
: math : ` | l > ` states as we have : math : ` | n > ` states .
2019-12-04 18:32:00 +01:00
2019-06-27 15:21:07 +02:00
"""
2020-04-08 21:35:59 +02:00
print ( ' \n Calculating complement \n ' )
2019-12-04 18:32:00 +01:00
2019-06-27 15:21:07 +02:00
block_maps , ndim = self . get_block_matrix_map ( )
_ , ns , nk , _ , _ = self . shells [ 0 ] . proj_win . shape
2023-01-23 21:40:57 +01:00
p_mat = np . zeros ( ( ndim , self . nb_max ) , dtype = complex )
p_full = np . zeros ( ( 1 , ns , nk , self . nb_max , self . nb_max ) , dtype = complex )
2019-12-04 18:32:00 +01:00
2019-06-27 15:21:07 +02:00
# Note that 'ns' and 'nk' are the same for all shells
2019-12-04 18:32:00 +01:00
2020-04-08 21:35:59 +02:00
for isp in range ( ns ) :
for ik in range ( nk ) :
2019-06-28 14:47:15 +02:00
bmin = self . ib_win [ ik , isp , 0 ]
bmax = self . ib_win [ ik , isp , 1 ] + 1
2019-12-04 18:32:00 +01:00
nb = bmax - bmin
2019-06-28 14:47:15 +02:00
# Combine all projectors of the group to one block projector
for bl_map in block_maps :
p_mat [ : , : ] = 0.0 j # !!! Clean-up from the last k-point and block!
for ibl , block in enumerate ( bl_map ) :
i1 , i2 = block [ ' bmat_range ' ]
ish , ion = block [ ' shell_ion ' ]
nlm = i2 - i1 + 1
shell = self . shells [ ish ]
p_mat [ i1 : i2 , : nb ] = shell . proj_win [ ion , isp , ik , : nlm , : nb ]
orbs_done = 1 * ndim
p_full [ 0 , isp , ik , : ndim , : ] = p_mat
2019-07-12 16:04:10 +02:00
while orbs_done < self . nb_max :
2019-06-28 14:47:15 +02:00
#We calculate the overlap of all bloch states: sum_l <n|l><l|m>
2019-07-12 16:04:10 +02:00
overlap = np . dot ( p_full [ 0 , isp , ik , : orbs_done , : ] . transpose ( ) . conjugate ( ) , p_full [ 0 , isp , ik , : orbs_done , : ] )
2019-06-28 14:47:15 +02:00
# work is the projector onto the orthogonal complment <n| ( 1 - sum_l |l><l| ) |m>
2019-06-27 15:21:07 +02:00
work = np . eye ( self . nb_max ) - overlap
2019-06-28 14:47:15 +02:00
# calculate the norm of the projected bloch function
2019-07-12 16:04:10 +02:00
norm = np . sqrt ( np . sum ( work * work . transpose ( ) , axis = 1 ) )
2019-06-28 14:47:15 +02:00
# select the bloch function leading to the largest norm
2019-06-27 15:21:07 +02:00
max_ind = np . argmax ( norm )
2019-06-28 14:47:15 +02:00
# normalize and put it to the projectors
2019-07-12 16:04:10 +02:00
p_full [ 0 , isp , ik , orbs_done , : ] = work [ : , max_ind ] . conjugate ( ) / norm [ max_ind ]
2019-06-28 14:47:15 +02:00
orbs_done + = 1
2019-07-12 16:04:10 +02:00
2019-06-27 15:21:07 +02:00
sh_pars = { }
sh_pars [ ' lshell ' ] = - 1
sh_pars [ ' ions ' ] = { ' nion ' : 1 , ' ion_list ' : [ [ 1 ] ] }
sh_pars [ ' user_index ' ] = ' complement '
sh_pars [ ' corr ' ] = False
2019-06-28 14:47:15 +02:00
sh_pars [ ' ib_min ' ] = bmin
sh_pars [ ' ib_max ' ] = bmax
sh_pars [ ' ib_win ' ] = self . ib_win
2019-12-04 18:32:00 +01:00
2019-06-27 15:21:07 +02:00
self . shells . append ( ComplementShell ( sh_pars , p_full [ : , : , : , ndim : , : ] , False ) )
self . ishells . append ( self . ishells [ - 1 ] + 1 )
2019-12-04 18:32:00 +01:00
2015-11-19 14:48:45 +01:00
################################################################################
#
# gen_block_matrix_map
#
################################################################################
def get_block_matrix_map ( self ) :
"""
Generates a map from a set of projectors belonging to different shells
and ions onto a set of block projector matrices , each of which is
orthonormalized .
Returns the map and the maximum orbital dimension of the block projector
matrix .
2015-11-13 18:15:21 +01:00
Mapping is defined as a list of ' block_maps ' corresponding to subsets
of projectors to be orthogonalized . Each subset corresponds to a subset of sites
and spans all orbital indices . defined by ' bl_map ' as
2019-12-04 18:32:00 +01:00
bl_map = [ ( ( i1_start , i1_end ) , ( i1_shell , i1_ion ) ) ,
( ( i2_start , i2_end ) , ( i2_shell , i2_ion ) ) ,
2015-11-13 18:15:21 +01:00
. . . ] ,
where ` iX_start ` , ` iX_end ` is the range of indices of the block matrix
( in Python convention ` iX_end = iX_last + 1 ` , with ` iX_last ` being the last index
2019-12-04 18:32:00 +01:00
of the range ) ,
2015-11-13 18:15:21 +01:00
` iX_shell ` and ` iX_ion ` the shell and site indices . The length of the range
should be consistent with ' nlm ' dimensions of a corresponding shell , i . e . ,
` iX_end - iX_start = nlm [ iX_shell ] ` .
Consider particular cases :
1. Orthogonality is ensured on each site ( NORMION = True ) .
For each site ' ion ' we have the following mapping :
2020-04-08 21:51:27 +02:00
block_maps = [ bl_map [ ion ] for ion in range ( shell . nion )
2015-11-13 18:15:21 +01:00
for shell in shells ]
bl_map = [ ( ( i1_start , i1_end ) , ( i1_shell , ion ) ) ,
( ( i2_start , i2_end ) , ( i2_shell , ion ) ) ,
. . . ] ,
2015-11-19 11:47:59 +01:00
2. Orthogonality is ensured on all sites within the group ( NORMION = False ) .
2015-11-13 18:15:21 +01:00
The mapping :
block_maps = [ bl_map ]
bl_map = [ ( ( i1_start , i1_end ) , ( i1_shell , i1_shell . ion1 ) ) ,
( ( i1_start , i1_end ) , ( i1_shell , i1_shell . ion2 ) ) ,
. . .
( ( i2_start , i2_end ) , ( i2_shell , i2_shell . ion1 ) ) ,
( ( i2_start , i2_end ) , ( i2_shell , i2_shell . ion2 ) ) ,
. . . ] ,
"""
2015-11-19 11:47:59 +01:00
if self . normion :
2015-11-19 14:48:45 +01:00
# Projectors for each site are mapped onto a separate block matrix
2015-11-19 12:11:55 +01:00
block_maps = [ ]
ndim = 0
for ish in self . ishells :
_shell = self . shells [ ish ]
nion , ns , nk , nlm , nb_max = _shell . proj_win . shape
ndim = max ( ndim , nlm )
2020-04-08 21:35:59 +02:00
for ion in range ( nion ) :
2015-11-19 12:11:55 +01:00
i1_bl = 0
i2_bl = nlm
block = { ' bmat_range ' : ( i1_bl , i2_bl ) }
block [ ' shell_ion ' ] = ( ish , ion )
bl_map = [ block ]
block_maps . append ( bl_map )
2015-11-19 11:47:59 +01:00
else :
2015-11-19 14:48:45 +01:00
# All projectors within a group are combined into one big block matrix
2015-11-19 11:47:59 +01:00
block_maps = [ ]
bl_map = [ ]
i1_bl = 0
for ish in self . ishells :
_shell = self . shells [ ish ]
nion , ns , nk , nlm , nb_max = _shell . proj_win . shape
2020-04-08 21:35:59 +02:00
for ion in range ( nion ) :
2015-11-19 11:47:59 +01:00
i2_bl = i1_bl + nlm
block = { ' bmat_range ' : ( i1_bl , i2_bl ) }
block [ ' shell_ion ' ] = ( ish , ion )
bl_map . append ( block )
i1_bl = i2_bl
ndim = i2_bl
block_maps . append ( bl_map )
2015-11-19 14:48:45 +01:00
return block_maps , ndim
2015-11-13 18:15:21 +01:00
2015-11-13 19:09:25 +01:00
################################################################################
#
# orthogonalize_projector_matrix()
#
################################################################################
def orthogonalize_projector_matrix ( self , p_matrix ) :
"""
Orthogonalizes a projector defined by a rectangular matrix ` p_matrix ` .
Parameters
- - - - - - - - - -
p_matrix ( numpy . array [ complex ] ) : matrix ` Nm x Nb ` , where ` Nm ` is
the number of orbitals , ` Nb ` number of bands
Returns
- - - - - - -
Orthogonalized projector matrix , initial overlap matrix and its eigenvalues .
"""
2015-11-21 14:23:30 +01:00
# TODO: check the precision of the calculations below,
# it seems to be inferior to that of Fortran implementation
2015-11-13 19:09:25 +01:00
# Overlap matrix O_{m m'} = \sum_{v} P_{m v} P^{*}_{v m'}
overlap = np . dot ( p_matrix , p_matrix . conj ( ) . T )
# Calculate [O^{-1/2}]_{m m'}
eig , eigv = np . linalg . eigh ( overlap )
2022-09-08 14:14:40 +02:00
assert np . all ( eig > 0.0 ) , ( " Negative eigenvalues of the overlap matrix: "
" projectors are ill-defined " )
2015-11-21 14:23:30 +01:00
sqrt_eig = 1.0 / np . sqrt ( eig )
shalf = np . dot ( eigv * sqrt_eig , eigv . conj ( ) . T )
2015-11-13 19:09:25 +01:00
# Apply \tilde{P}_{m v} = \sum_{m'} [O^{-1/2}]_{m m'} P_{m' v}
p_ortho = np . dot ( shalf , p_matrix )
return ( p_ortho , overlap , eig )
################################################################################
#
# select_bands()
#
################################################################################
def select_bands ( self , eigvals ) :
"""
Select a subset of bands lying within a given energy window .
The band energies are assumed to be sorted in an ascending order .
Parameters
- - - - - - - - - -
2019-12-04 18:32:00 +01:00
2015-11-13 19:09:25 +01:00
eigvals ( numpy . array ) : all eigenvalues
emin , emax ( float ) : energy window
Returns
- - - - - - -
2019-12-04 18:32:00 +01:00
ib_win , nb_min , nb_max : lowest and highest indices of the selected bands
2015-11-13 19:09:25 +01:00
"""
# Sanity check
if self . emin > eigvals . max ( ) or self . emax < eigvals . min ( ) :
raise Exception ( " Energy window does not overlap with the band structure " )
nk , nband , ns_band = eigvals . shape
2023-01-23 21:40:57 +01:00
ib_win = np . zeros ( ( nk , ns_band , 2 ) , dtype = int )
2015-11-13 19:09:25 +01:00
ib_min = 10000000
ib_max = 0
2020-04-08 21:35:59 +02:00
for isp in range ( ns_band ) :
for ik in range ( nk ) :
for ib in range ( nband ) :
2015-11-13 19:09:25 +01:00
en = eigvals [ ik , ib , isp ]
if en > = self . emin :
break
ib1 = ib
2020-04-08 21:35:59 +02:00
for ib in range ( ib1 , nband ) :
2015-11-13 19:09:25 +01:00
en = eigvals [ ik , ib , isp ]
if en > self . emax :
break
else :
# If we reached the last band add 1 to get the correct bound
ib + = 1
ib2 = ib - 1
assert ib1 < = ib2 , " No bands inside the window for ik = %s " % ( ik )
ib_win [ ik , isp , 0 ] = ib1
ib_win [ ik , isp , 1 ] = ib2
ib_min = min ( ib_min , ib1 )
ib_max = max ( ib_max , ib2 )
return ib_win , ib_min , ib_max