# Copyright (c) 2013-2017 Commissariat à l'énergie atomique et aux énergies alternatives (CEA)
# Copyright (c) 2013-2017 Centre national de la recherche scientifique (CNRS)
# Copyright (c) 2020-2023 Simons Foundation
# Copyright (c) 2017 Hugo U.R. Strand
#
# This program 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.
#
# This program 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 may obtain a copy of the License at
# https:#www.gnu.org/licenses/gpl-3.0.txt
#
# Authors: John Bonini, Michel Ferrero, Alexander Hampel, Olivier Parcollet, Hugo U. R. Strand, Nils Wentzell
from triqs.gf import *
import triqs.utility.mpi as mpi
from itertools import *
import inspect
import copy,numpy
[docs]
class SumkDiscrete:
"""
INTERNAL USE
The function to compute \[ G \leftarrow \sum_k (\omega + \mu - eps_k - Sigma(k,\omega))^{-1} \]
for GF functions with blocks of the size of the matrix eps_k with a discrete sum.
The class contains the discretized hoppings and points in the arrays
hopping, bz_points,bz_weights,mu_pattern,overlap (IF non orthogonal)
It can also generate a grid (ComputeGrid) for a regular grid or a Gauss-Legendre sum.
"""
[docs]
def __init__ (self, dim, gf_struct, orthogonal_basis = True ):
"""
Just constructs the arrays, but without initializing them
- dim is the dimension
- gf_struct: Indices of the Green function
- orthogonal_basis: True by default
"""
self.__GFBLOC_Structure = copy.deepcopy(gf_struct)
self.orthogonal_basis,self.dim = orthogonal_basis,dim
#-------------------------------------------------------------
[docs]
def resize_arrays (self, nk):
"""
Just constructs the arrays, but without initializing them
- nk: total number of k points
"""
# constructs the arrays.
no = len(self.__GFBLOC_Structure)
self.hopping = numpy.zeros([nk,no,no],numpy.complex128) # t(k_index,a,b)
self.bz_points = numpy.zeros([nk,self.dim],numpy.float64) # k(k_index,:)
self.bz_weights = numpy.ones([nk],numpy.float64)/ float(nk) # w(k_kindex) , default normalisation
self.mu_pattern = numpy.identity(no,numpy.complex128) if self.orthogonal_basis else numpy.zeros([no,no,nk],numpy.complex128)
self.overlap = numpy.array(self.mu_pattern, copy=True)
#-------------------------------------------------------------
def __get_GFBloc_Structure(self):
"""Returns the ONLY block indices accepted for the G and Sigma argument of the
SumK function"""
return self.__GFBLOC_Structure
GFBlocIndices = property(__get_GFBloc_Structure)
#-------------------------------------------------------------
def __call__ (self, Sigma, mu=0, field=None, epsilon_hat=None, result=None, selected_blocks=()):
"""
- Computes:
result <- \[ \sum_k (\omega + \mu - field - t(k) - Sigma(k,\omega)) \]
if result is None, it returns a new GF with the results.
otherwise, result must be a GF, in which the calculation is done, and which is then returned.
(this allows chain calculation: SK(mu = mu,Sigma = Sigma, result = G).total_density()
which computes the sumK into G, and returns the density of G.
- Sigma can be a X, or a function k-> X or a function k,eps ->X where:
- k is expected to be a 1d-numpy array of size self.dim of float,
containing the k vector in the basis of the RBZ (i.e. -0.5< k_i <0.5)
- eps is t(k)
- X is anything such that X[BlockName] can be added/subtracted to a GFBloc for BlockName in selected_blocks.
e.g. X can be a BlockGf(with at least the selected_blocks), or a dictionnary Blockname -> array
if the array has the same dimension as the GF blocks (for example to add a static Sigma).
Each block of X has to have the same shape as self.hopping or
epsilon_hat(self.hopping[i]).
- field: Any k independant object to be added to the GF
- epsilon_hat: a function of eps_k returning a matrix with the same matrix-dimensions as each block in Sigma
- selected_blocks: The calculation is done with the SAME t(k) for all blocks. If this list is not None
only the blocks in this list are calculated.
e.g. G and Sigma have block indices 'up' and 'down'.
if selected_blocks ==None: 'up' and 'down' are calculated
if selected_blocks == ['up']: only 'up' is calculated. 'down' is 0.
"""
assert selected_blocks == (), "selected_blocks not supported for now"
#S = Sigma.view_selected_blocks(selected_blocks) if selected_blocks else Sigma
#Gres = result if result else Sigma.copy()
#G = Gres.view_selected_blocks(selected_blocks) if selected_blocks else Gres
# check Sigma
# case 1) Sigma is a BlockGf
if isinstance(Sigma, BlockGf):
model = Sigma
Sigma_fnt = False
# case 2) Sigma is a function returning a BlockGf
else:
assert callable(Sigma), "If Sigma is not a BlockGf it must be a function"
Sigma_Nargs = len(inspect.getfullargspec(Sigma)[0])
assert Sigma_Nargs <= 2, "Sigma must be a function of k or of k and epsilon"
if Sigma_Nargs == 1:
model = Sigma(self.bz_points[0])
elif Sigma_Nargs == 2:
model = Sigma(self.bz_points[0], self.hopping[0])
Sigma_fnt = True
G = result if result else model.copy()
assert isinstance(G,BlockGf), "G must be a BlockGf"
assert isinstance(G.mesh, MeshImFreq), "G.mesh must be MeshImFreq but is {}".format(type(G.mesh))
# check input
assert self.orthogonal_basis, "Local_G: must be orthogonal. non ortho cases not checked."
# check that each block has the same size
assert len(list(set([g.target_shape[0] for i,g in G]))) == 1
assert self.bz_weights.shape[0] == self.n_kpts(), "Internal Error"
no = list(set([g.target_shape[0] for i,g in G]))[0]
# check that the target shape of each block matches self.hopping
eps_hat = epsilon_hat(self.hopping[0]) if epsilon_hat else self.hopping[0]
assert (no,no) == eps_hat.shape, (f"Target shape of each block in Sigma: {(no,no)} does not to match orbital dimension of the hopping matrix: {eps_hat.shape}.")
# Initialize
G.zero()
tmp,tmp2 = G.copy(),G.copy()
mupat = mu * numpy.identity(no, numpy.complex128)
tmp << iOmega_n
if field != None: tmp -= field
if not Sigma_fnt: tmp -= Sigma # substract Sigma once for all
# Loop on k points...
for w, k, eps_k in zip(*[mpi.slice_array(A) for A in [self.bz_weights, self.bz_points, self.hopping]]):
eps_hat = epsilon_hat(eps_k) if epsilon_hat else eps_k
tmp2 << tmp
tmp2 -= tmp2.n_blocks * [eps_hat - mupat]
if Sigma_fnt:
if Sigma_Nargs == 1: tmp2 -= Sigma(k)
elif Sigma_Nargs == 2: tmp2 -= Sigma(k,eps_k)
tmp2.invert()
tmp2 *= w
G += tmp2
G << mpi.all_reduce(G)
mpi.barrier()
return G
#-------------------------------------------------------------
[docs]
def n_kpts(self):
""" Returns the number of k points"""
return self.bz_points.shape[0]