From b76735f0aa5c39e198de92bdd08c2a62240ca111 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 4 Jan 2024 16:56:47 +0800 Subject: [PATCH 001/209] create poisson_grid_init.py --- dptb/negf/poisson_grid_init.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 dptb/negf/poisson_grid_init.py diff --git a/dptb/negf/poisson_grid_init.py b/dptb/negf/poisson_grid_init.py new file mode 100644 index 00000000..8c0867fe --- /dev/null +++ b/dptb/negf/poisson_grid_init.py @@ -0,0 +1,2 @@ +import numpy as np + From 1f05ecf9484fda797549e692bf2cff6103104375 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 4 Jan 2024 17:22:21 +0800 Subject: [PATCH 002/209] add init to grid --- dptb/negf/poisson_grid_init.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/dptb/negf/poisson_grid_init.py b/dptb/negf/poisson_grid_init.py index 8c0867fe..210c1f70 100644 --- a/dptb/negf/poisson_grid_init.py +++ b/dptb/negf/poisson_grid_init.py @@ -1,2 +1,19 @@ import numpy as np + +class grid(object): + def __init__(self,xg,yg,zg,xa,ya,za): + # xg,yg,zg are the coordinates of the basic grid points + self.xg = np.around(xg,decimals=5) + self.yg = np.around(yg,decimals=5) + self.zg = np.around(zg,decimals=5) + # xa,ya,za are the coordinates of the atoms + uxa = np.unique(xa) + uya = np.unique(ya) + uza = np.unique(za) + # x,y,z are the coordinates of the grid points + self.x = np.unique(np.concatenate((uxa,self.xg),0)).sort() + self.y = np.unique(np.concatenate((uya,self.yg),0)).sort() + self.z = np.unique(np.concatenate((uza,self.zg),0)).sort() + self.Np = int(len(self.x)*len(self.y)*len(self.z)) + print('Number of grid points: ',self.Np) \ No newline at end of file From 5eaad71aaf37ec19954e1efd56d22083e0ffb011 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 4 Jan 2024 17:31:28 +0800 Subject: [PATCH 003/209] add find_atom_index --- dptb/negf/poisson_grid_init.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/dptb/negf/poisson_grid_init.py b/dptb/negf/poisson_grid_init.py index 210c1f70..ca77faeb 100644 --- a/dptb/negf/poisson_grid_init.py +++ b/dptb/negf/poisson_grid_init.py @@ -8,6 +8,7 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.yg = np.around(yg,decimals=5) self.zg = np.around(zg,decimals=5) # xa,ya,za are the coordinates of the atoms + self.Na = len(xa) # number of atoms uxa = np.unique(xa) uya = np.unique(ya) uza = np.unique(za) @@ -16,4 +17,15 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.y = np.unique(np.concatenate((uya,self.yg),0)).sort() self.z = np.unique(np.concatenate((uza,self.zg),0)).sort() self.Np = int(len(self.x)*len(self.y)*len(self.z)) - print('Number of grid points: ',self.Np) \ No newline at end of file + print('Number of grid points: ',self.Np) + + self.atom_index = self.find_atom_index(xa,ya,za) + + def find_atom_index(self,xa,ya,za): + # find the index of the atoms in the grid + swap = np.zeros(self.Na) + for i in range(self.Na): + for j in range(self.Np): + if xa[i]==self.x[j] and ya[i]==self.y[j] and za[i]==self.z[j]: + swap[i] = j + return swap From e8a78a035d8a477af6bc3465c7287d2464f3c1f6 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 5 Jan 2024 17:04:01 +0800 Subject: [PATCH 004/209] add interface3D class --- dptb/negf/poisson_grid_init.py | 43 ++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/dptb/negf/poisson_grid_init.py b/dptb/negf/poisson_grid_init.py index ca77faeb..b1005856 100644 --- a/dptb/negf/poisson_grid_init.py +++ b/dptb/negf/poisson_grid_init.py @@ -13,19 +13,48 @@ def __init__(self,xg,yg,zg,xa,ya,za): uya = np.unique(ya) uza = np.unique(za) # x,y,z are the coordinates of the grid points - self.x = np.unique(np.concatenate((uxa,self.xg),0)).sort() - self.y = np.unique(np.concatenate((uya,self.yg),0)).sort() - self.z = np.unique(np.concatenate((uza,self.zg),0)).sort() - self.Np = int(len(self.x)*len(self.y)*len(self.z)) + self.xall = np.unique(np.concatenate((uxa,self.xg),0)) # unique results are sorted + self.yall = np.unique(np.concatenate((uya,self.yg),0)) + self.zall = np.unique(np.concatenate((uza,self.zg),0)) + + assert len(self.xall) == len(self.yall) + assert len(self.yall) == len(self.zall) + # create meshgrid + xmesh,ymesh,zmesh = np.meshgrid(self.xall,self.yall,self.zall) + self.xmesh = xmesh.flatten() + self.ymesh = ymesh.flatten() + self.zmesh = zmesh.flatten() + + self.Np = int(len(self.xall)*len(self.yall)*len(self.zall)) + assert self.Np == len(self.xmesh) + print('Number of grid points: ',self.Np) self.atom_index = self.find_atom_index(xa,ya,za) def find_atom_index(self,xa,ya,za): # find the index of the atoms in the grid - swap = np.zeros(self.Na) + swap = {} for i in range(self.Na): for j in range(self.Np): - if xa[i]==self.x[j] and ya[i]==self.y[j] and za[i]==self.z[j]: - swap[i] = j + if xa[i]==self.xmesh[j] and ya[i]==self.ymesh[j] and za[i]==self.zmesh[j]: + swap.update({i:j}) return swap + +class gate(object): + def __init__(self): + self.Ef = 0.0 + + + + +class interface3D(object): + def __init__(self,grid,*args): + self.grid = grid + + region_name = ['gate','medium'] + + for i in range(0,len(args)): + if not args[i].__class__.__name__ in region_name: + raise ValueError('Unknown region type: ',args[i]) + \ No newline at end of file From a505c4a5c3f1a3c383eb60c6a216d45326e6a171 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 5 Jan 2024 17:10:10 +0800 Subject: [PATCH 005/209] add gate potential and boundary_potential --- dptb/negf/poisson_grid_init.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dptb/negf/poisson_grid_init.py b/dptb/negf/poisson_grid_init.py index b1005856..650ca466 100644 --- a/dptb/negf/poisson_grid_init.py +++ b/dptb/negf/poisson_grid_init.py @@ -50,11 +50,17 @@ def __init__(self): class interface3D(object): def __init__(self,grid,*args): + assert grid.__class__.__name__ == 'grid' self.grid = grid + self.eps = np.zeros(grid.Np) + self.boudnary_points = np.full(grid.Np,False,dtype=bool) # initialize all points to be inner points + self.gate_potential = np.zeros(grid.Np) # no gate potential initially; only would be non-zero in gate region region_name = ['gate','medium'] for i in range(0,len(args)): if not args[i].__class__.__name__ in region_name: raise ValueError('Unknown region type: ',args[i]) + + \ No newline at end of file From 4ec3d0f2bb5205660a89da7fa83f1bf8b05ee94c Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 8 Jan 2024 15:59:00 +0800 Subject: [PATCH 006/209] add contents to interface3D --- dptb/negf/poisson_grid_init.py | 79 +++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/dptb/negf/poisson_grid_init.py b/dptb/negf/poisson_grid_init.py index 650ca466..15ed77b4 100644 --- a/dptb/negf/poisson_grid_init.py +++ b/dptb/negf/poisson_grid_init.py @@ -2,12 +2,18 @@ class grid(object): + # define the grid in 3D space def __init__(self,xg,yg,zg,xa,ya,za): # xg,yg,zg are the coordinates of the basic grid points self.xg = np.around(xg,decimals=5) self.yg = np.around(yg,decimals=5) self.zg = np.around(zg,decimals=5) # xa,ya,za are the coordinates of the atoms + # atom should be within the grid + assert xa.all() >= np.min(xg) and xa.all() <= np.max(xg) + assert ya.all() >= np.min(yg) and ya.all() <= np.max(yg) + assert za.all() >= np.min(zg) and za.all() <= np.max(zg) + self.Na = len(xa) # number of atoms uxa = np.unique(xa) uya = np.unique(ya) @@ -17,16 +23,17 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.yall = np.unique(np.concatenate((uya,self.yg),0)) self.zall = np.unique(np.concatenate((uza,self.zg),0)) - assert len(self.xall) == len(self.yall) - assert len(self.yall) == len(self.zall) + # create meshgrid xmesh,ymesh,zmesh = np.meshgrid(self.xall,self.yall,self.zall) self.xmesh = xmesh.flatten() self.ymesh = ymesh.flatten() self.zmesh = zmesh.flatten() + self.gird_coord = np.array([self.xmesh,self.ymesh,self.zmesh]).T #(Np,3) self.Np = int(len(self.xall)*len(self.yall)*len(self.zall)) assert self.Np == len(self.xmesh) + assert self.gird_coord.shape[0] == self.Np print('Number of grid points: ',self.Np) @@ -45,22 +52,74 @@ class gate(object): def __init__(self): self.Ef = 0.0 - +class medium(object): + def __init__(self): + self.eps = 1.0 class interface3D(object): def __init__(self,grid,*args): assert grid.__class__.__name__ == 'grid' - self.grid = grid - self.eps = np.zeros(grid.Np) - self.boudnary_points = np.full(grid.Np,False,dtype=bool) # initialize all points to be inner points - self.gate_potential = np.zeros(grid.Np) # no gate potential initially; only would be non-zero in gate region region_name = ['gate','medium'] - for i in range(0,len(args)): if not args[i].__class__.__name__ in region_name: - raise ValueError('Unknown region type: ',args[i]) - + raise ValueError('Unknown region type: ',args[i].__class__.__name__) + + self.grid = grid + self.eps = np.zeros(grid.Np) # dielectric permittivity + self.phi = np.zeros(grid.Np) # potential + self.free_charge = np.zeros(grid.Np) # free charge density + self.fixed_charge = np.zeros(grid.Np) # fixed charge density + + self.boudnary_points = {i:"in" for i in range(self.grid.Np)} # initially set all points as internal + self.boudnary_points_init() + + self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially; only would be non-zero in gate region + self.gatepotential_eps_init(args) + + + + def boudnary_points_init(self): + # set the boundary points + for i in range(self.grid.Np): + if self.grid.xmesh[i] == np.min(self.grid.xall): + self.boudnary_points[i] = "xmin" + elif self.grid.xmesh[i] == np.max(self.grid.xall): + self.boudnary_points[i] = "xmax" + elif self.grid.ymesh[i] == np.min(self.grid.yall): + self.boudnary_points[i] = "ymin" + elif self.grid.ymesh[i] == np.max(self.grid.yall): + self.boudnary_points[i] = "ymax" + elif self.grid.zmesh[i] == np.min(self.grid.zall): + self.boudnary_points[i] = "zmin" + elif self.grid.zmesh[i] == np.max(self.grid.zall): + self.boudnary_points[i] = "zmax" + internal_NP = 0 + for i in range(self.grid.Np): + if self.boudnary_points[i] == "in": + internal_NP += 1 + self.internal_NP = internal_NP + + def gatepotential_eps_init(self,args): + # set the gate potential + for i in range(len(args)): + if args[i].__class__.__name__ == 'gate' or args[i].__class__.__name__ == 'medium': + + # find gate region in grid + index=np.nonzero((args[i].xmin<=self.grid_coord[0])&(args[i].xmax>=self.grid_coord[0])& + (args[i].ymin<=self.grid_coord[1])&(args[i].ymax>=self.grid_coord[1])& + (args[i].zmin<=self.grid_coord[2])&(args[i].zmax>=self.grid_coord[2])) + if args[i].__class__.__name__ == 'gate': #attribute gate potential to the corresponding grid points + self.lead_gate_potential[index] = args[i].Ef + else: + self.eps[index] = args[i].eps + + + + + + + \ No newline at end of file From 66b7c3b36c48b80ef74492b5da76790a42260a7e Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 8 Jan 2024 16:14:58 +0800 Subject: [PATCH 007/209] rename poisson_gird_init to poisson_init --- dptb/negf/{poisson_grid_init.py => poisson_init.py} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename dptb/negf/{poisson_grid_init.py => poisson_init.py} (98%) diff --git a/dptb/negf/poisson_grid_init.py b/dptb/negf/poisson_init.py similarity index 98% rename from dptb/negf/poisson_grid_init.py rename to dptb/negf/poisson_init.py index 15ed77b4..0c9b7c06 100644 --- a/dptb/negf/poisson_grid_init.py +++ b/dptb/negf/poisson_init.py @@ -75,10 +75,9 @@ def __init__(self,grid,*args): self.boudnary_points = {i:"in" for i in range(self.grid.Np)} # initially set all points as internal self.boudnary_points_init() - self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially; only would be non-zero in gate region + self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially self.gatepotential_eps_init(args) - def boudnary_points_init(self): # set the boundary points @@ -119,6 +118,8 @@ def gatepotential_eps_init(self,args): + + From a4891ba44ef0cb030654559c5cb2962aad498832 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 8 Jan 2024 16:49:41 +0800 Subject: [PATCH 008/209] add region definition to gate and medium --- dptb/negf/poisson_init.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 0c9b7c06..9f39f159 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -1,5 +1,5 @@ import numpy as np - +from pyamg.gallery import poisson class grid(object): # define the grid in 3D space @@ -29,13 +29,13 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.xmesh = xmesh.flatten() self.ymesh = ymesh.flatten() self.zmesh = zmesh.flatten() - self.gird_coord = np.array([self.xmesh,self.ymesh,self.zmesh]).T #(Np,3) + self.grid_coord = np.array([self.xmesh,self.ymesh,self.zmesh]).T #(Np,3) self.Np = int(len(self.xall)*len(self.yall)*len(self.zall)) assert self.Np == len(self.xmesh) - assert self.gird_coord.shape[0] == self.Np + assert self.grid_coord.shape[0] == self.Np - print('Number of grid points: ',self.Np) + print('Number of grid points: ',self.Np,' grid shape: ',self.grid_coord.shape,' Number of atoms: ',self.Na) self.atom_index = self.find_atom_index(xa,ya,za) @@ -49,13 +49,20 @@ def find_atom_index(self,xa,ya,za): return swap class gate(object): - def __init__(self): + def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): self.Ef = 0.0 + # gate region + self.xmin = xmin; self.xmax = xmax + self.ymin = ymin; self.ymax = ymax + self.zmin = zmin; self.zmax = zmax class medium(object): - def __init__(self): + def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): self.eps = 1.0 - + # gate region + self.xmin = xmin; self.xmax = xmax + self.ymin = ymin; self.ymax = ymax + self.zmin = zmin; self.zmax = zmax class interface3D(object): def __init__(self,grid,*args): @@ -76,7 +83,7 @@ def __init__(self,grid,*args): self.boudnary_points_init() self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially - self.gatepotential_eps_init(args) + self.gate_potential_eps_init(args) def boudnary_points_init(self): @@ -100,15 +107,16 @@ def boudnary_points_init(self): internal_NP += 1 self.internal_NP = internal_NP - def gatepotential_eps_init(self,args): + def gate_potential_eps_init(self,args): # set the gate potential + # ingore the lead potential temporarily for i in range(len(args)): if args[i].__class__.__name__ == 'gate' or args[i].__class__.__name__ == 'medium': # find gate region in grid - index=np.nonzero((args[i].xmin<=self.grid_coord[0])&(args[i].xmax>=self.grid_coord[0])& - (args[i].ymin<=self.grid_coord[1])&(args[i].ymax>=self.grid_coord[1])& - (args[i].zmin<=self.grid_coord[2])&(args[i].zmax>=self.grid_coord[2])) + index=np.nonzero((args[i].xmin<=self.grid.grid_coord[0])&(args[i].xmax>=self.grid.grid_coord[0])& + (args[i].ymin<=self.grid.grid_coord[1])&(args[i].ymax>=self.grid.grid_coord[1])& + (args[i].zmin<=self.grid.grid_coord[2])&(args[i].zmax>=self.grid.grid_coord[2])) if args[i].__class__.__name__ == 'gate': #attribute gate potential to the corresponding grid points self.lead_gate_potential[index] = args[i].Ef else: From d4a700033d5154c2f516567838594c028d28f974 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 8 Jan 2024 22:49:25 +0800 Subject: [PATCH 009/209] add grid-point surface along x-axis --- dptb/negf/poisson_init.py | 61 +++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 9f39f159..bdb2f4a5 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -5,9 +5,7 @@ class grid(object): # define the grid in 3D space def __init__(self,xg,yg,zg,xa,ya,za): # xg,yg,zg are the coordinates of the basic grid points - self.xg = np.around(xg,decimals=5) - self.yg = np.around(yg,decimals=5) - self.zg = np.around(zg,decimals=5) + self.xg = np.around(xg,decimals=5);self.yg = np.around(yg,decimals=5);self.zg = np.around(zg,decimals=5) # xa,ya,za are the coordinates of the atoms # atom should be within the grid assert xa.all() >= np.min(xg) and xa.all() <= np.max(xg) @@ -15,13 +13,12 @@ def __init__(self,xg,yg,zg,xa,ya,za): assert za.all() >= np.min(zg) and za.all() <= np.max(zg) self.Na = len(xa) # number of atoms - uxa = np.unique(xa) - uya = np.unique(ya) - uza = np.unique(za) + uxa = np.unique(xa);uya = np.unique(ya);uza = np.unique(za) # x,y,z are the coordinates of the grid points self.xall = np.unique(np.concatenate((uxa,self.xg),0)) # unique results are sorted self.yall = np.unique(np.concatenate((uya,self.yg),0)) self.zall = np.unique(np.concatenate((uza,self.zg),0)) + self.shape = (len(self.xall),len(self.yall),len(self.zall)) # create meshgrid @@ -30,6 +27,9 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.ymesh = ymesh.flatten() self.zmesh = zmesh.flatten() self.grid_coord = np.array([self.xmesh,self.ymesh,self.zmesh]).T #(Np,3) + sorted_indices = np.lexsort((self.xmesh , self.ymesh , self.zmesh)) + self.grid_coord = self.grid_coord[sorted_indices] # sort the grid points firstly along x, then y, lastly z + self.Np = int(len(self.xall)*len(self.yall)*len(self.zall)) assert self.Np == len(self.xmesh) @@ -39,6 +39,20 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.atom_index = self.find_atom_index(xa,ya,za) + + # create surface area for each grid point + surface_grid = np.zeros((self.Np,3)) + x_vorlen = self.cal_vorlen(self.xall);y_vorlen = self.cal_vorlen(self.yall);z_vorlen = self.cal_vorlen(self.zall) + + ## surface along x-axis (yz-plane) + XD,YD = np.meshgrid(x_vorlen,y_vorlen) + ax,bx = np.meshgrid(YD.flatten(),z_vorlen) + surface_grid[:,0] = abs((ax*bx).flatten()) + ## surface along y-axis (xz-plane) + + + + def find_atom_index(self,xa,ya,za): # find the index of the atoms in the grid swap = {} @@ -47,6 +61,16 @@ def find_atom_index(self,xa,ya,za): if xa[i]==self.xmesh[j] and ya[i]==self.ymesh[j] and za[i]==self.zmesh[j]: swap.update({i:j}) return swap + + def cal_vorlen(self,x): + # compute the length of the Voronoi segment of a one-dimensional array x + xd = np.zeros(len(x)) + xd[0] = abs(x[0]-x[1])/2 + xd[-1] = abs(x[-1]-x[-2])/2 + for i in range(1,len(x)-1): + xd[i] = (abs(x[i]-x[i-1])+abs(x[i]-x[i+1]))/2 + return xd + class gate(object): def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): @@ -122,10 +146,35 @@ def gate_potential_eps_init(self,args): else: self.eps[index] = args[i].eps + def to_pyamg(self,dtype=None): + # convert to amg format A,b matrix + if dtype == None: + dtype = np.float64 + A = poisson(self.grid.shape,format='csr',dtype=dtype) + b = np.zeros(A.shape[0],dtype=A.dtype) + self.set_amg_boundary(A,b) + + return A,b + + def set_amg_boundary(self,A,b): + + def Dirichlet(idx,A,b): #第一类边界条件 + # Default pyamg Poisson matrix has Dirichlet BC + b[idx] = 0.0 #为何要将边界点的值设为0 + # def Neumann(idx_bc, idx_p1): #第二类边界条件 + # # Set all boundary equations to 0 + # s = _a.array_arange(A.indptr[idx_bc], A.indptr[idx_bc + 1]) + # A.data[s] = 0 + # # force the boundary cells to equal the neighbouring cell + # A[idx_bc, idx_bc] = 1 + # A[idx_bc, idx_p1] = -1 + # A.eliminate_zeros() + # b[idx_bc] = 0.0 + From 2b19aa137c1ea879c855d6024d5126afc98c498e Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 9 Jan 2024 10:35:08 +0800 Subject: [PATCH 010/209] add surface_grid --- dptb/negf/poisson_init.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index bdb2f4a5..99177cdb 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -8,9 +8,9 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.xg = np.around(xg,decimals=5);self.yg = np.around(yg,decimals=5);self.zg = np.around(zg,decimals=5) # xa,ya,za are the coordinates of the atoms # atom should be within the grid - assert xa.all() >= np.min(xg) and xa.all() <= np.max(xg) - assert ya.all() >= np.min(yg) and ya.all() <= np.max(yg) - assert za.all() >= np.min(zg) and za.all() <= np.max(zg) + assert (xa-np.min(xg)).all() and (xa-np.max(xg)).all() + assert (ya-np.min(yg)).all() and (ya-np.max(yg)).all() + assert (za-np.min(zg)).all() and (za-np.max(zg)).all() self.Na = len(xa) # number of atoms uxa = np.unique(xa);uya = np.unique(ya);uza = np.unique(za) @@ -29,26 +29,34 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.grid_coord = np.array([self.xmesh,self.ymesh,self.zmesh]).T #(Np,3) sorted_indices = np.lexsort((self.xmesh , self.ymesh , self.zmesh)) self.grid_coord = self.grid_coord[sorted_indices] # sort the grid points firstly along x, then y, lastly z - - + ## check the number of grid points self.Np = int(len(self.xall)*len(self.yall)*len(self.zall)) assert self.Np == len(self.xmesh) assert self.grid_coord.shape[0] == self.Np print('Number of grid points: ',self.Np,' grid shape: ',self.grid_coord.shape,' Number of atoms: ',self.Na) + # find the index of the atoms in the grid self.atom_index = self.find_atom_index(xa,ya,za) - # create surface area for each grid point + # create surface area for each grid point along x,y,z axis + # each grid point corresponds to a Voronoi cell(box) surface_grid = np.zeros((self.Np,3)) x_vorlen = self.cal_vorlen(self.xall);y_vorlen = self.cal_vorlen(self.yall);z_vorlen = self.cal_vorlen(self.zall) - ## surface along x-axis (yz-plane) XD,YD = np.meshgrid(x_vorlen,y_vorlen) + ## surface along x-axis (yz-plane) ax,bx = np.meshgrid(YD.flatten(),z_vorlen) surface_grid[:,0] = abs((ax*bx).flatten()) ## surface along y-axis (xz-plane) + ay,by = np.meshgrid(XD.flatten(),z_vorlen) + surface_grid[:,1] = abs((ay*by).flatten()) + ## surface along z-axis (xy-plane) + az,_ = np.meshgrid((XD*YD).flatten(),self.zall) + surface_grid[:,2] = abs(az.flatten()) + + self.surface_grid = surface_grid # grid points order are the same as that of self.grid_coord From 10b28d86697d90ab349501cca959fa3fe3cdec04 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 9 Jan 2024 11:09:58 +0800 Subject: [PATCH 011/209] add flux terms --- dptb/negf/poisson_init.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 99177cdb..d97a38fd 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -1,6 +1,9 @@ import numpy as np from pyamg.gallery import poisson +eps0 = 8.854187817e-12 # vacuum permittivity + + class grid(object): # define the grid in 3D space def __init__(self,xg,yg,zg,xa,ya,za): @@ -160,16 +163,33 @@ def to_pyamg(self,dtype=None): dtype = np.float64 A = poisson(self.grid.shape,format='csr',dtype=dtype) b = np.zeros(A.shape[0],dtype=A.dtype) - self.set_amg_boundary(A,b) + self.construct_poisson(A,b) return A,b - def set_amg_boundary(self,A,b): + def construct_poisson(self,A,b): def Dirichlet(idx,A,b): #第一类边界条件 # Default pyamg Poisson matrix has Dirichlet BC b[idx] = 0.0 #为何要将边界点的值设为0 + Nx = self.grid.shape[0];Ny = self.grid.shape[1];Nz = self.grid.shape[2] + for gp_index in range(self.grid.Np): + if self.boudnary_points[gp_index] == "in": + flux_xm = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index-1]+self.eps[gp_index])*0.5\ + *(self.phi[gp_index-1]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index,0]-self.grid.grid_coord[gp_index-1,0]) + flux_xp = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index+1]+self.eps[gp_index])*0.5\ + *(self.phi[gp_index+1]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+1,0]-self.grid.grid_coord[gp_index,0]) + + flux_ym = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index-Nx]+self.eps[gp_index])*0.5\ + *(self.phi[gp_index-Nx]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index-Nx,1]-self.grid.grid_coord[gp_index,1]) + flux_yp = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index+Nx]+self.eps[gp_index])*0.5\ + *(self.phi[gp_index+Nx]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+Nx,1]-self.grid.grid_coord[gp_index,1]) + + flux_zm = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index-Nx*Ny]+self.eps[gp_index])*0.5\ + *(self.phi[gp_index-Nx*Ny]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index-Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) + flux_zp = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index+Nx*Ny]+self.eps[gp_index])*0.5\ + *(self.phi[gp_index+Nx*Ny]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) # def Neumann(idx_bc, idx_p1): #第二类边界条件 From 7800d51a1e4a2b3820d7bde935900b61cec213d1 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 9 Jan 2024 14:17:05 +0800 Subject: [PATCH 012/209] add func construction_poisson --- dptb/negf/poisson_init.py | 103 +++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 28 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index d97a38fd..c1234197 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -1,7 +1,10 @@ import numpy as np from pyamg.gallery import poisson +from utils.constants import elementary_charge as q +from utils.constants import Boltzmann +from scipy.constants import epsilon_0 as eps0 #TODO:later add to untils.constants.py + -eps0 = 8.854187817e-12 # vacuum permittivity class grid(object): @@ -114,14 +117,18 @@ def __init__(self,grid,*args): self.free_charge = np.zeros(grid.Np) # free charge density self.fixed_charge = np.zeros(grid.Np) # fixed charge density + self.Temperature = 300.0 # temperature in unit of Kelvin + self.KBT = Boltzmann*self.Temperature # thermal energy + + # store the boundary information: xmin,xmax,ymin,ymax,zmin,zmax,gate self.boudnary_points = {i:"in" for i in range(self.grid.Np)} # initially set all points as internal - self.boudnary_points_init() + self.boudnary_points_get() - self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially - self.gate_potential_eps_init(args) + self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially, all grid points are set to zero + self.gate_potential_eps_get(*args) - def boudnary_points_init(self): + def boudnary_points_get(self): # set the boundary points for i in range(self.grid.Np): if self.grid.xmesh[i] == np.min(self.grid.xall): @@ -142,7 +149,7 @@ def boudnary_points_init(self): internal_NP += 1 self.internal_NP = internal_NP - def gate_potential_eps_init(self,args): + def gate_potential_eps_get(self,*args): # set the gate potential # ingore the lead potential temporarily for i in range(len(args)): @@ -153,6 +160,7 @@ def gate_potential_eps_init(self,args): (args[i].ymin<=self.grid.grid_coord[1])&(args[i].ymax>=self.grid.grid_coord[1])& (args[i].zmin<=self.grid.grid_coord[2])&(args[i].zmax>=self.grid.grid_coord[2])) if args[i].__class__.__name__ == 'gate': #attribute gate potential to the corresponding grid points + self.boudnary_points[index] = "gate" self.lead_gate_potential[index] = args[i].Ef else: self.eps[index] = args[i].eps @@ -163,44 +171,83 @@ def to_pyamg(self,dtype=None): dtype = np.float64 A = poisson(self.grid.shape,format='csr',dtype=dtype) b = np.zeros(A.shape[0],dtype=A.dtype) + A.data[:] = 0 # set all elements to zero + # later we set non-zero elements to A, the indices and indptr are not changed as the default grid order in pyamg + # is the same as that of self.grid.grid_coord self.construct_poisson(A,b) return A,b def construct_poisson(self,A,b): - - def Dirichlet(idx,A,b): #第一类边界条件 - # Default pyamg Poisson matrix has Dirichlet BC - b[idx] = 0.0 #为何要将边界点的值设为0 - + # construct the Poisson equation by adding boundary conditions and free charge to the matrix A and vector b Nx = self.grid.shape[0];Ny = self.grid.shape[1];Nz = self.grid.shape[2] for gp_index in range(self.grid.Np): if self.boudnary_points[gp_index] == "in": + # flux_xm = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index-1]+self.eps[gp_index])*0.5\ + # *(self.phi[gp_index-1]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index,0]-self.grid.grid_coord[gp_index-1,0]) + # flux_xp = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index+1]+self.eps[gp_index])*0.5\ + # *(self.phi[gp_index+1]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+1,0]-self.grid.grid_coord[gp_index,0]) + + # flux_ym = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index-Nx]+self.eps[gp_index])*0.5\ + # *(self.phi[gp_index-Nx]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index-Nx,1]-self.grid.grid_coord[gp_index,1]) + # flux_yp = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index+Nx]+self.eps[gp_index])*0.5\ + # *(self.phi[gp_index+Nx]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+Nx,1]-self.grid.grid_coord[gp_index,1]) + + # flux_zm = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index-Nx*Ny]+self.eps[gp_index])*0.5\ + # *(self.phi[gp_index-Nx*Ny]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index-Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) + # flux_zp = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index+Nx*Ny]+self.eps[gp_index])*0.5\ + # *(self.phi[gp_index+Nx*Ny]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) flux_xm = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index-1]+self.eps[gp_index])*0.5\ - *(self.phi[gp_index-1]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index,0]-self.grid.grid_coord[gp_index-1,0]) + /abs(self.grid.grid_coord[gp_index,0]-self.grid.grid_coord[gp_index-1,0]) flux_xp = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index+1]+self.eps[gp_index])*0.5\ - *(self.phi[gp_index+1]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+1,0]-self.grid.grid_coord[gp_index,0]) + /abs(self.grid.grid_coord[gp_index+1,0]-self.grid.grid_coord[gp_index,0]) flux_ym = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index-Nx]+self.eps[gp_index])*0.5\ - *(self.phi[gp_index-Nx]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index-Nx,1]-self.grid.grid_coord[gp_index,1]) + /abs(self.grid.grid_coord[gp_index-Nx,1]-self.grid.grid_coord[gp_index,1]) flux_yp = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index+Nx]+self.eps[gp_index])*0.5\ - *(self.phi[gp_index+Nx]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+Nx,1]-self.grid.grid_coord[gp_index,1]) + /abs(self.grid.grid_coord[gp_index+Nx,1]-self.grid.grid_coord[gp_index,1]) flux_zm = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index-Nx*Ny]+self.eps[gp_index])*0.5\ - *(self.phi[gp_index-Nx*Ny]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index-Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) + /abs(self.grid.grid_coord[gp_index-Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) flux_zp = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index+Nx*Ny]+self.eps[gp_index])*0.5\ - *(self.phi[gp_index+Nx*Ny]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) - - - # def Neumann(idx_bc, idx_p1): #第二类边界条件 - # # Set all boundary equations to 0 - # s = _a.array_arange(A.indptr[idx_bc], A.indptr[idx_bc + 1]) - # A.data[s] = 0 - # # force the boundary cells to equal the neighbouring cell - # A[idx_bc, idx_bc] = 1 - # A[idx_bc, idx_p1] = -1 - # A.eliminate_zeros() - # b[idx_bc] = 0.0 + /abs(self.grid.grid_coord[gp_index+Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) + + # add flux term to matrix A + A[gp_index,gp_index] = -(flux_xm+flux_xp+flux_ym+flux_yp+flux_zm+flux_zp) + A[gp_index,gp_index-1] = flux_xm + A[gp_index,gp_index+1] = flux_xp + A[gp_index,gp_index-Nx] = flux_ym + A[gp_index,gp_index+Nx] = flux_yp + A[gp_index,gp_index-Nx*Ny] = flux_zm + A[gp_index,gp_index+Nx*Ny] = flux_zp + + b[gp_index] = -q*self.free_charge[gp_index]\ + *np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.KBT)\ + -q*self.fixed_charge[gp_index] + # the above free_charge form accelerate the convergence of the Poisson equation + # only internal points have non-zero free_charge and fixed_charge + + else:# boundary points + A[gp_index,gp_index] = 1.0 + if self.boudnary_points[gp_index] == "xmin": + A[gp_index,gp_index+1] = -1.0 + elif self.boudnary_points[gp_index] == "xmax": + A[gp_index,gp_index-1] = -1.0 + elif self.boudnary_points[gp_index] == "ymin": + A[gp_index,gp_index+Nx] = -1.0 + elif self.boudnary_points[gp_index] == "ymax": + A[gp_index,gp_index-Nx] = -1.0 + elif self.boudnary_points[gp_index] == "zmin": + A[gp_index,gp_index+Nx*Ny] = -1.0 + elif self.boudnary_points[gp_index] == "zmax": + A[gp_index,gp_index-Nx*Ny] = -1.0 + elif self.boudnary_points[gp_index] == "gate": + b[gp_index] = -1*self.lead_gate_potential[gp_index] + + #TODO: add lead potential. For dptb-negf, we only need to change zmin and zmax as lead + + + From 9ee0d25482c78b41374d52b163dbcc3da76df32b Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 9 Jan 2024 17:08:13 +0800 Subject: [PATCH 013/209] add poisson_scf.py --- dptb/negf/poisson_init.py | 75 +++++++++++++++++++++++++++++++-------- dptb/negf/poisson_scf.py | 18 ++++++++++ 2 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 dptb/negf/poisson_scf.py diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index c1234197..755240dd 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -1,4 +1,5 @@ import numpy as np +import pyamg from pyamg.gallery import poisson from utils.constants import elementary_charge as q from utils.constants import Boltzmann @@ -7,7 +8,7 @@ -class grid(object): +class Grid(object): # define the grid in 3D space def __init__(self,xg,yg,zg,xa,ya,za): # xg,yg,zg are the coordinates of the basic grid points @@ -86,7 +87,7 @@ def cal_vorlen(self,x): return xd -class gate(object): +class Gate(object): def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): self.Ef = 0.0 # gate region @@ -94,7 +95,7 @@ def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): self.ymin = ymin; self.ymax = ymax self.zmin = zmin; self.zmax = zmax -class medium(object): +class Medium(object): def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): self.eps = 1.0 # gate region @@ -102,11 +103,17 @@ def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): self.ymin = ymin; self.ymax = ymax self.zmin = zmin; self.zmax = zmax -class interface3D(object): + + + + + + +class Interface3D(object): def __init__(self,grid,*args): - assert grid.__class__.__name__ == 'grid' + assert grid.__class__.__name__ == 'Grid' - region_name = ['gate','medium'] + region_name = ['Gate','Medium'] for i in range(0,len(args)): if not args[i].__class__.__name__ in region_name: raise ValueError('Unknown region type: ',args[i].__class__.__name__) @@ -114,18 +121,19 @@ def __init__(self,grid,*args): self.grid = grid self.eps = np.zeros(grid.Np) # dielectric permittivity self.phi = np.zeros(grid.Np) # potential + self.phi_old = np.zeros(grid.Np) # potential in the previous iteration self.free_charge = np.zeros(grid.Np) # free charge density self.fixed_charge = np.zeros(grid.Np) # fixed charge density self.Temperature = 300.0 # temperature in unit of Kelvin - self.KBT = Boltzmann*self.Temperature # thermal energy + self.kBT = Boltzmann*self.Temperature # thermal energy # store the boundary information: xmin,xmax,ymin,ymax,zmin,zmax,gate self.boudnary_points = {i:"in" for i in range(self.grid.Np)} # initially set all points as internal self.boudnary_points_get() self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially, all grid points are set to zero - self.gate_potential_eps_get(*args) + self.potential_eps_get(*args) def boudnary_points_get(self): @@ -149,18 +157,18 @@ def boudnary_points_get(self): internal_NP += 1 self.internal_NP = internal_NP - def gate_potential_eps_get(self,*args): + def potential_eps_get(self,*args): # set the gate potential # ingore the lead potential temporarily for i in range(len(args)): - if args[i].__class__.__name__ == 'gate' or args[i].__class__.__name__ == 'medium': + if args[i].__class__.__name__ == 'Gate' or args[i].__class__.__name__ == 'Medium': # find gate region in grid index=np.nonzero((args[i].xmin<=self.grid.grid_coord[0])&(args[i].xmax>=self.grid.grid_coord[0])& (args[i].ymin<=self.grid.grid_coord[1])&(args[i].ymax>=self.grid.grid_coord[1])& (args[i].zmin<=self.grid.grid_coord[2])&(args[i].zmax>=self.grid.grid_coord[2])) - if args[i].__class__.__name__ == 'gate': #attribute gate potential to the corresponding grid points - self.boudnary_points[index] = "gate" + if args[i].__class__.__name__ == 'Gate': #attribute gate potential to the corresponding grid points + self.boudnary_points[index] = "Gate" self.lead_gate_potential[index] = args[i].Ef else: self.eps[index] = args[i].eps @@ -222,7 +230,7 @@ def construct_poisson(self,A,b): A[gp_index,gp_index+Nx*Ny] = flux_zp b[gp_index] = -q*self.free_charge[gp_index]\ - *np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.KBT)\ + *np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT)\ -q*self.fixed_charge[gp_index] # the above free_charge form accelerate the convergence of the Poisson equation # only internal points have non-zero free_charge and fixed_charge @@ -241,12 +249,51 @@ def construct_poisson(self,A,b): A[gp_index,gp_index+Nx*Ny] = -1.0 elif self.boudnary_points[gp_index] == "zmax": A[gp_index,gp_index-Nx*Ny] = -1.0 - elif self.boudnary_points[gp_index] == "gate": + elif self.boudnary_points[gp_index] == "Gate": b[gp_index] = -1*self.lead_gate_potential[gp_index] #TODO: add lead potential. For dptb-negf, we only need to change zmin and zmax as lead + def solve_poisson_pyamg(self,A,b,tolerance=1e-12,accel=None): + # solve the Poisson equation + print('Solve Poisson equation by pyamg') + pyamg_solver = pyamg.aggregation.smoothed_aggregation_solver(A, max_levels=1000) + del A + print('Poisson equation solver: ',pyamg_solver) + residuals = [] + + def callback(x): + # residuals calculated in solver is a pre-conditioned residual + # residuals.append(np.linalg.norm(b - A.dot(x)) ** 0.5) + print( + " {:4d} residual = {:.5e} x0-residual = {:.5e}".format( + len(residuals) - 1, residuals[-1], residuals[-1] / residuals[0] + ) + ) + + x = pyamg_solver( + b, + tol=tolerance, + callback=callback, + residuals=residuals, + accel=accel, + cycle="W", + maxiter=1e7, + ) + print("Done solving the Poisson equation!") + return x + + + def solve_poisson(self,method='pyamg'): + # solve poisson equation: + if method == 'pyamg': + A,b = self.to_pyamg() + self.phi = self.solve_poisson_pyamg(A,b) + self.phi_old = self.phi.copy() + else: + raise ValueError('Unknown Poisson solver: ',method) + diff --git a/dptb/negf/poisson_scf.py b/dptb/negf/poisson_scf.py new file mode 100644 index 00000000..cd2f3adc --- /dev/null +++ b/dptb/negf/poisson_scf.py @@ -0,0 +1,18 @@ + + + + +def Poisson_NEGF_SCF(grid,interface,device,acc=1e-6,max_iter=100): + + interface.phi_old = interface.phi.copy() + scf_count = 0 + + max_diff = 1e30 + while max_diff > acc and scf_count < max_iter: + scf_count += 1 + print('SCF iteration: ',scf_count) + device.phi = interface.phi[interface.grid.atom_index] + + + max_diff = np.max(np.abs(device.phi - interface.phi_old)) + interface.phi_old = device.phi.copy() From 223ec7e80c63aa169d7291d61e8cabf7fb192860 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 9 Jan 2024 22:03:29 +0800 Subject: [PATCH 014/209] add vbias term to device_preperty.py --- dptb/negf/device_property.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index f6a83583..b8cb0fa8 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -105,7 +105,7 @@ def set_leadLR(self, lead_L, lead_R): self.lead_R = lead_R self.mu = self.efermi - 0.5*(self.lead_L.voltage + self.lead_R.voltage) - def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=True): + def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=True, Vbias=None): ''' computes the Green's function for a given energy and k-point in device. the tags used here to identify different Green's functions follows the NEGF theory @@ -139,12 +139,15 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr # else: # HD_ = HD - if os.path.exists(os.path.join(self.results_path, "POTENTIAL.pth")): - self.V = torch.load(os.path.join(self.results_path, "POTENTIAL.pth")) - elif abs(self.mu - self.efermi) > 1e-7: - self.V = self.efermi - self.mu + if Vbias is None: + if os.path.exists(os.path.join(self.results_path, "POTENTIAL.pth")): + self.V = torch.load(os.path.join(self.results_path, "POTENTIAL.pth")) + elif abs(self.mu - self.efermi) > 1e-7: + self.V = self.efermi - self.mu + else: + self.V = 0. else: - self.V = 0. + self.V = Vbias if not hasattr(self, "hd") or not hasattr(self, "sd"): self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) From 2d12aaaeb08b3d0ffae1bd40fe460ba3efda29d9 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 9 Jan 2024 22:04:30 +0800 Subject: [PATCH 015/209] rename Dielectric --- dptb/negf/poisson_init.py | 47 ++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 755240dd..208004e3 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -87,6 +87,7 @@ def cal_vorlen(self,x): return xd + class Gate(object): def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): self.Ef = 0.0 @@ -95,7 +96,7 @@ def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): self.ymin = ymin; self.ymax = ymax self.zmin = zmin; self.zmax = zmax -class Medium(object): +class Dielectric(object): def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): self.eps = 1.0 # gate region @@ -110,14 +111,17 @@ def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): class Interface3D(object): - def __init__(self,grid,*args): + def __init__(self,grid,gate_list,dielectric_list): assert grid.__class__.__name__ == 'Grid' - region_name = ['Gate','Medium'] - for i in range(0,len(args)): - if not args[i].__class__.__name__ in region_name: - raise ValueError('Unknown region type: ',args[i].__class__.__name__) - + + for i in range(0,len(gate_list)): + if not gate_list[i].__class__.__name__ == 'Gate': + raise ValueError('Unknown region type: ',gate_list[i].__class__.__name__) + for i in range(0,len(dielectric_list)): + if not dielectric_list[i].__class__.__name__ == 'Dielectric': + raise ValueError('Unknown region type: ',dielectric_list[i].__class__.__name__) + self.grid = grid self.eps = np.zeros(grid.Np) # dielectric permittivity self.phi = np.zeros(grid.Np) # potential @@ -133,7 +137,8 @@ def __init__(self,grid,*args): self.boudnary_points_get() self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially, all grid points are set to zero - self.potential_eps_get(*args) + self.potential_eps_get(gate_list) + self.potential_eps_get(dielectric_list) def boudnary_points_get(self): @@ -157,21 +162,21 @@ def boudnary_points_get(self): internal_NP += 1 self.internal_NP = internal_NP - def potential_eps_get(self,*args): + def potential_eps_get(self,region_list): # set the gate potential # ingore the lead potential temporarily - for i in range(len(args)): - if args[i].__class__.__name__ == 'Gate' or args[i].__class__.__name__ == 'Medium': - - # find gate region in grid - index=np.nonzero((args[i].xmin<=self.grid.grid_coord[0])&(args[i].xmax>=self.grid.grid_coord[0])& - (args[i].ymin<=self.grid.grid_coord[1])&(args[i].ymax>=self.grid.grid_coord[1])& - (args[i].zmin<=self.grid.grid_coord[2])&(args[i].zmax>=self.grid.grid_coord[2])) - if args[i].__class__.__name__ == 'Gate': #attribute gate potential to the corresponding grid points - self.boudnary_points[index] = "Gate" - self.lead_gate_potential[index] = args[i].Ef - else: - self.eps[index] = args[i].eps + for i in range(len(region_list)): + # find gate region in grid + index=np.nonzero((region_list[i].xmin<=self.grid.grid_coord[0])&(region_list[i].xmax>=self.grid.grid_coord[0])& + (region_list[i].ymin<=self.grid.grid_coord[1])&(region_list[i].ymax>=self.grid.grid_coord[1])& + (region_list[i].zmin<=self.grid.grid_coord[2])&(region_list[i].zmax>=self.grid.grid_coord[2])) + if region_list[i].__class__.__name__ == 'Gate': #attribute gate potential to the corresponding grid points + self.boudnary_points[index] = "Gate" + self.lead_gate_potential[index] = region_list[i].Ef + elif region_list[i].__class__.__name__ == 'Dielectric': + self.eps[index] = region_list[i].eps + else: + raise ValueError('Unknown region type: ',region_list[i].__class__.__name__) def to_pyamg(self,dtype=None): # convert to amg format A,b matrix From 1c7643f1831e255b02db954b1c97bbc3b7664e0d Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 9 Jan 2024 22:05:23 +0800 Subject: [PATCH 016/209] add poisson_negf_scf to NEGF.py --- dptb/negf/poisson_scf.py | 2 +- dptb/postprocess/NEGF.py | 127 ++++++++++++++++++++++++++++++--------- 2 files changed, 100 insertions(+), 29 deletions(-) diff --git a/dptb/negf/poisson_scf.py b/dptb/negf/poisson_scf.py index cd2f3adc..39927ea3 100644 --- a/dptb/negf/poisson_scf.py +++ b/dptb/negf/poisson_scf.py @@ -2,7 +2,7 @@ -def Poisson_NEGF_SCF(grid,interface,device,acc=1e-6,max_iter=100): +def poisson_negf_scf(grid,interface,device,acc=1e-6,max_iter=100): interface.phi_old = interface.phi.copy() scf_count = 0 diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index dac7b02a..6b871f92 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -19,6 +19,8 @@ import numpy as np from dptb.utils.make_kpoints import kmesh_sampling import logging +from negf.poisson_scf import poisson_negf_scf # TODO : move this to dptb.negf +from negf.poisson_init import Grid,Interface3D,Gate,Dielectric log = logging.getLogger(__name__) @@ -105,6 +107,11 @@ def __init__(self, apiHrk, run_opt, jdata): self.generate_energy_grid() self.out = {} + ## Poisson equation + self.poisson_grid = jdata["poisson_grid"] + self.gate_region = jdata["gate_region"] + self.dielectric_region = jdata["dielectric_region"] + def generate_energy_grid(self): @@ -146,13 +153,75 @@ def generate_energy_grid(self): def compute(self): - # check if scf is required if self.scf: - # perform k-point sampling and scf calculation to get the converged density - for k in self.kpoints: - pass + if not self.out_density: + raise RuntimeError("Error! scf calculation requires density matrix. Please set out_density to True") + self.poisson_negf_scf() else: - pass + self.negf_compute(scf_require=False) + + + def poisson_negf_scf(self,acc=1e-6,max_iter=100): + + + # create grid + xg,yg,zg,xa,ya,za = self.read_grid(self.structase, self.poisson_grid) #TODO:write read_grid + grid = Grid(xg,yg,zg,xa,ya,za) + # create gate + gate_list = [] + gates = self.gate_region.keys() + for gg in gates: + if gg.startswith("gate"): + xmin,xmax = self.gate_region[gg].get("x_range",None).split('-') + ymin,ymax = self.gate_region[gg].get("y_range",None).split('-') + zmin,zmax = self.gate_region[gg].get("z_range",None).split('-') + gate_init = Gate(xmin,xmax,ymin,ymax,zmin,zmax) + gate_init.Ef = self.gate_region[gg].get("Ef",None) + gate_list.append(gate_init) + + # create dielectric + dielectric_list = [] + dielectric = self.dielectric_region.keys() + for dd in dielectric: + if dd.startswith("dielectric"): + xmin,xmax = self.dielectric_region[dd].get("x_range",None).split('-') + ymin,ymax = self.dielectric_region[dd].get("y_range",None).split('-') + zmin,zmax = self.dielectric_region[dd].get("z_range",None).split('-') + dielectric_init = Dielectric(xmin,xmax,ymin,ymax,zmin,zmax) + dielectric_init.eps = self.dielectric_region[dd].get("Ef",None) + dielectric_list.append(dielectric_init) + + # create interface + interface_poisson = Interface3D(grid,gate_list,dielectric_list) + + normad = 1e30 + while normad > acc: + + # update Hamiltonian by modifying onsite energy with potential + potential_grid_atom = interface_poisson.phi[interface_poisson.grid.atom_index] # a vector with length of number of atoms + device_atom_norbs = self.negf_hamiltonian.atom_norbs[self.negf_hamiltonian.proj_device_id[0]:self.negf_hamiltonian.proj_device_id[1]] + + potential_list = [] + for i in range(len(device_atom_norbs)): + potential_list.append(potential_grid_atom[i]*torch.ones(device_atom_norbs[i])) + potential_tensor = torch.cat(potential_list) + self.negf_compute(scf_require=True,Vbias=potential_tensor) + + + + + interface_poisson.solve_poisson(method='pyamg') + + + + def negf_compute(self,scf_require=False,Vbias=None): + # check if scf is required + # if self.scf: + # # perform k-point sampling and scf calculation to get the converged density + # for k in self.kpoints: + # pass + # else: + # pass # computing output properties for ik, k in enumerate(self.kpoints): @@ -178,31 +247,32 @@ def compute(self): energy=e, kpoint=k, eta_device=self.jdata["eta_device"], - block_tridiagonal=self.block_tridiagonal + block_tridiagonal=self.block_tridiagonal, + Vbias=Vbias ) - - if self.out_dos: - prop = self.out.setdefault("DOS", []) - prop.append(self.compute_DOS(k)) - if self.out_tc or self.out_current_nscf: - prop = self.out.setdefault("TC", []) - prop.append(self.compute_TC(k)) - if self.out_ldos: - prop = self.out.setdefault("LDOS", []) - prop.append(self.compute_LDOS(k)) - - if self.out_dos: - self.out["DOS"] = torch.stack(self.out["DOS"]) - if self.out_tc or self.out_current_nscf: - self.out["TC"] = torch.stack(self.out["TC"]) + if scf_require==False: + if self.out_dos: + prop = self.out.setdefault("DOS", []) + prop.append(self.compute_DOS(k)) + if self.out_tc or self.out_current_nscf: + prop = self.out.setdefault("TC", []) + prop.append(self.compute_TC(k)) + if self.out_ldos: + prop = self.out.setdefault("LDOS", []) + prop.append(self.compute_LDOS(k)) - if self.out_current_nscf: - self.out["BIAS_POTENTIAL_NSCF"], self.out["CURRENT_NSCF"] = self.compute_current_nscf(k, self.uni_grid, self.out["TC"]) + if scf_require==False: + if self.out_dos: + self.out["DOS"] = torch.stack(self.out["DOS"]) + if self.out_tc or self.out_current_nscf: + self.out["TC"] = torch.stack(self.out["TC"]) + if self.out_current_nscf: + self.out["BIAS_POTENTIAL_NSCF"], self.out["CURRENT_NSCF"] = self.compute_current_nscf(k, self.uni_grid, self.out["TC"]) + # computing properties that are not functions of E (improvement can be made here in properties related to integration of energy window of fermi functions) + if self.out_current: + pass - # computing properties that are not functions of E (improvement can be made here in properties related to integration of energy window of fermi functions) - if self.out_current: - pass - + # whether scf_require is True or False, the following properties are computed if self.out_density or self.out_potential: self.out["DM_eq"], self.out["DM_neq"] = self.compute_density(k) @@ -233,7 +303,8 @@ def compute(self): lcurrent += self.int_weight[i] * self.compute_lcurrent(k) self.out["LOCAL_CURRENT"] = lcurrent - torch.save(self.out, self.results_path+"/negf.k{}.out.pth".format(ik)) + if scf_require == False: + torch.save(self.out, self.results_path+"/negf.k{}.out.pth".format(ik)) # plotting From b29e137d86d657ce333bdf15233f0a617d044baa Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 9 Jan 2024 22:19:08 +0800 Subject: [PATCH 017/209] find potential on each atom gird through atom_index_dict --- dptb/negf/poisson_init.py | 10 +++++----- dptb/postprocess/NEGF.py | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 208004e3..657a2e42 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -44,7 +44,7 @@ def __init__(self,xg,yg,zg,xa,ya,za): print('Number of grid points: ',self.Np,' grid shape: ',self.grid_coord.shape,' Number of atoms: ',self.Na) # find the index of the atoms in the grid - self.atom_index = self.find_atom_index(xa,ya,za) + self.atom_index_dict = self.find_atom_index(xa,ya,za) # create surface area for each grid point along x,y,z axis @@ -71,10 +71,10 @@ def __init__(self,xg,yg,zg,xa,ya,za): def find_atom_index(self,xa,ya,za): # find the index of the atoms in the grid swap = {} - for i in range(self.Na): - for j in range(self.Np): - if xa[i]==self.xmesh[j] and ya[i]==self.ymesh[j] and za[i]==self.zmesh[j]: - swap.update({i:j}) + for atom_index in range(self.Na): + for gp_index in range(self.Np): + if xa[atom_index]==self.xmesh[gp_index] and ya[atom_index]==self.ymesh[gp_index] and za[atom_index]==self.zmesh[gp_index]: + swap.update({atom_index:gp_index}) return swap def cal_vorlen(self,x): diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 6b871f92..df32412d 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -198,12 +198,14 @@ def poisson_negf_scf(self,acc=1e-6,max_iter=100): while normad > acc: # update Hamiltonian by modifying onsite energy with potential - potential_grid_atom = interface_poisson.phi[interface_poisson.grid.atom_index] # a vector with length of number of atoms + atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) + potential_atom = interface_poisson.phi[atom_gridpoint_index] # a vector with length of number of atoms + # number of orbitals on atoms in device region device_atom_norbs = self.negf_hamiltonian.atom_norbs[self.negf_hamiltonian.proj_device_id[0]:self.negf_hamiltonian.proj_device_id[1]] potential_list = [] for i in range(len(device_atom_norbs)): - potential_list.append(potential_grid_atom[i]*torch.ones(device_atom_norbs[i])) + potential_list.append(potential_atom[i]*torch.ones(device_atom_norbs[i])) potential_tensor = torch.cat(potential_list) self.negf_compute(scf_require=True,Vbias=potential_tensor) From 47347466a83a60a47d38529776cd9a943e277dc0 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 9 Jan 2024 22:39:01 +0800 Subject: [PATCH 018/209] add update electron density for solving Poisson equation in poisson_negf_scf --- dptb/negf/poisson_init.py | 4 ++ dptb/postprocess/NEGF.py | 79 ++++++++++++++++++++++----------------- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 657a2e42..e6ab34d4 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -295,7 +295,11 @@ def solve_poisson(self,method='pyamg'): if method == 'pyamg': A,b = self.to_pyamg() self.phi = self.solve_poisson_pyamg(A,b) + + max_diff = np.max(abs(self.phi-self.phi_old)) self.phi_old = self.phi.copy() + + return max_diff else: raise ValueError('Unknown Poisson solver: ',method) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index df32412d..37303785 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -194,8 +194,8 @@ def poisson_negf_scf(self,acc=1e-6,max_iter=100): # create interface interface_poisson = Interface3D(grid,gate_list,dielectric_list) - normad = 1e30 - while normad > acc: + max_diff = 1e30 + while max_diff > acc: # update Hamiltonian by modifying onsite energy with potential atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) @@ -210,9 +210,18 @@ def poisson_negf_scf(self,acc=1e-6,max_iter=100): self.negf_compute(scf_require=True,Vbias=potential_tensor) + # update electron density for solving Poisson equation + DM_eq,DM_neq = self.out["DM_eq"], self.out["DM_neq"] + DM = DM_eq + DM_neq + elec_density = torch.diag(DM) + density_list = [] + pre_atom_orbs = 0 + for i in range(len(device_atom_norbs)): + density_list.append(torch.sum(elec_density[pre_atom_orbs : pre_atom_orbs+atom_gridpoint_index[i]])) + pre_atom_orbs += device_atom_norbs[i] - - interface_poisson.solve_poisson(method='pyamg') + interface_poisson.free_charge[atom_gridpoint_index] = np.array(density_list) + max_diff = interface_poisson.solve_poisson(method='pyamg') @@ -262,7 +271,15 @@ def negf_compute(self,scf_require=False,Vbias=None): if self.out_ldos: prop = self.out.setdefault("LDOS", []) prop.append(self.compute_LDOS(k)) + + + # whether scf_require is True or False, density are computed for Poisson-NEGF SCF + if self.out_density or self.out_potential: + self.out["DM_eq"], self.out["DM_neq"] = self.compute_density(k) + if self.out_potential: + pass + if scf_require==False: if self.out_dos: self.out["DOS"] = torch.stack(self.out["DOS"]) @@ -273,37 +290,31 @@ def negf_compute(self,scf_require=False,Vbias=None): # computing properties that are not functions of E (improvement can be made here in properties related to integration of energy window of fermi functions) if self.out_current: pass - - # whether scf_require is True or False, the following properties are computed - if self.out_density or self.out_potential: - self.out["DM_eq"], self.out["DM_neq"] = self.compute_density(k) - - if self.out_potential: - pass + + if self.out_lcurrent: + lcurrent = 0 + for i, e in enumerate(self.int_grid): + log.info(msg="computing green's function at e = {:.3f}".format(float(e))) + leads = self.stru_options.keys() + for ll in leads: + if ll.startswith("lead"): + getattr(self.deviceprop, ll).self_energy( + energy=e, + kpoint=k, + eta_lead=self.jdata["eta_lead"], + method=self.jdata["sgf_solver"] + ) + + self.deviceprop.cal_green_function( + energy=e, + kpoint=k, + eta_device=self.jdata["eta_device"], + block_tridiagonal=self.block_tridiagonal + ) + + lcurrent += self.int_weight[i] * self.compute_lcurrent(k) + self.out["LOCAL_CURRENT"] = lcurrent - if self.out_lcurrent: - lcurrent = 0 - for i, e in enumerate(self.int_grid): - log.info(msg="computing green's function at e = {:.3f}".format(float(e))) - leads = self.stru_options.keys() - for ll in leads: - if ll.startswith("lead"): - getattr(self.deviceprop, ll).self_energy( - energy=e, - kpoint=k, - eta_lead=self.jdata["eta_lead"], - method=self.jdata["sgf_solver"] - ) - - self.deviceprop.cal_green_function( - energy=e, - kpoint=k, - eta_device=self.jdata["eta_device"], - block_tridiagonal=self.block_tridiagonal - ) - - lcurrent += self.int_weight[i] * self.compute_lcurrent(k) - self.out["LOCAL_CURRENT"] = lcurrent if scf_require == False: torch.save(self.out, self.results_path+"/negf.k{}.out.pth".format(ik)) From 69f48d716776178744b6068f55a5e57a5745834d Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 9 Jan 2024 22:43:29 +0800 Subject: [PATCH 019/209] add scf break operation in poisson_negf_scf --- dptb/negf/poisson_init.py | 2 -- dptb/postprocess/NEGF.py | 14 +++++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index e6ab34d4..d4fa9731 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -297,8 +297,6 @@ def solve_poisson(self,method='pyamg'): self.phi = self.solve_poisson_pyamg(A,b) max_diff = np.max(abs(self.phi-self.phi_old)) - self.phi_old = self.phi.copy() - return max_diff else: raise ValueError('Unknown Poisson solver: ',method) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 37303785..151ae241 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -161,7 +161,7 @@ def compute(self): self.negf_compute(scf_require=False) - def poisson_negf_scf(self,acc=1e-6,max_iter=100): + def poisson_negf_scf(self,diff_acc=1e-6,max_iter=100,mix_rate=0.3): # create grid @@ -194,8 +194,8 @@ def poisson_negf_scf(self,acc=1e-6,max_iter=100): # create interface interface_poisson = Interface3D(grid,gate_list,dielectric_list) - max_diff = 1e30 - while max_diff > acc: + max_diff = 1e30; iter_count=0 + while max_diff > diff_acc: # update Hamiltonian by modifying onsite energy with potential atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) @@ -223,6 +223,14 @@ def poisson_negf_scf(self,acc=1e-6,max_iter=100): interface_poisson.free_charge[atom_gridpoint_index] = np.array(density_list) max_diff = interface_poisson.solve_poisson(method='pyamg') + interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi - interface_poisson.phi_old) + interface_poisson.phi_old = interface_poisson.phi.copy() + + iter_count += 1 + print('Poisson iteration: ',iter_count,' max_diff: ',max_diff) + if iter_count > max_iter: + print('Poisson iteration exceeds max_iter') + break def negf_compute(self,scf_require=False,Vbias=None): From 79ddcc2308a136a61e10fc93907a57611b625019 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 9 Jan 2024 22:46:36 +0800 Subject: [PATCH 020/209] add negf_compute in the final part of poisson_negf_scf --- dptb/postprocess/NEGF.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 151ae241..45273f8b 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -162,8 +162,7 @@ def compute(self): def poisson_negf_scf(self,diff_acc=1e-6,max_iter=100,mix_rate=0.3): - - + # create grid xg,yg,zg,xa,ya,za = self.read_grid(self.structase, self.poisson_grid) #TODO:write read_grid grid = Grid(xg,yg,zg,xa,ya,za) @@ -229,8 +228,11 @@ def poisson_negf_scf(self,diff_acc=1e-6,max_iter=100,mix_rate=0.3): iter_count += 1 print('Poisson iteration: ',iter_count,' max_diff: ',max_diff) if iter_count > max_iter: - print('Poisson iteration exceeds max_iter') - break + raise RuntimeError('Poisson iteration exceeds max_iter') + + + # calculate transport properties with converged potential + self.negf_compute(scf_require=False) def negf_compute(self,scf_require=False,Vbias=None): From 0cd5be352028039e69c5c218d8ee28fd8dea5ee4 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 10 Jan 2024 10:46:27 +0800 Subject: [PATCH 021/209] add poisson related json and read operation in NEGF --- dptb/negf/poisson_init.py | 4 +-- dptb/postprocess/NEGF.py | 75 +++++++++++++++++++++++---------------- 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index d4fa9731..edb4f6f3 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -117,10 +117,10 @@ def __init__(self,grid,gate_list,dielectric_list): for i in range(0,len(gate_list)): if not gate_list[i].__class__.__name__ == 'Gate': - raise ValueError('Unknown region type: ',gate_list[i].__class__.__name__) + raise ValueError('Unknown region type in Gate list: ',gate_list[i].__class__.__name__) for i in range(0,len(dielectric_list)): if not dielectric_list[i].__class__.__name__ == 'Dielectric': - raise ValueError('Unknown region type: ',dielectric_list[i].__class__.__name__) + raise ValueError('Unknown region type in Dielectric list: ',dielectric_list[i].__class__.__name__) self.grid = grid self.eps = np.zeros(grid.Np) # dielectric permittivity diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 45273f8b..e398aaf3 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -108,9 +108,10 @@ def __init__(self, apiHrk, run_opt, jdata): self.out = {} ## Poisson equation - self.poisson_grid = jdata["poisson_grid"] - self.gate_region = jdata["gate_region"] - self.dielectric_region = jdata["dielectric_region"] + self.poisson_options = j_must_have(jdata, "poisson_options") + self.gate_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("gate")] + self.dielectric_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("dielectric")] + def generate_energy_grid(self): @@ -156,42 +157,38 @@ def compute(self): if self.scf: if not self.out_density: raise RuntimeError("Error! scf calculation requires density matrix. Please set out_density to True") - self.poisson_negf_scf() + self.poisson_negf_scf(diff_acc=self.poisson_options['err']) else: self.negf_compute(scf_require=False) - def poisson_negf_scf(self,diff_acc=1e-6,max_iter=100,mix_rate=0.3): + def poisson_negf_scf(self,diff_acc=1e-6,max_iter=1000,mix_rate=0.3): # create grid - xg,yg,zg,xa,ya,za = self.read_grid(self.structase, self.poisson_grid) #TODO:write read_grid - grid = Grid(xg,yg,zg,xa,ya,za) + grid = self.read_grid(self.poisson_options["grid"],self.deviceprop.structure) + # create gate - gate_list = [] - gates = self.gate_region.keys() - for gg in gates: - if gg.startswith("gate"): - xmin,xmax = self.gate_region[gg].get("x_range",None).split('-') - ymin,ymax = self.gate_region[gg].get("y_range",None).split('-') - zmin,zmax = self.gate_region[gg].get("z_range",None).split('-') - gate_init = Gate(xmin,xmax,ymin,ymax,zmin,zmax) - gate_init.Ef = self.gate_region[gg].get("Ef",None) - gate_list.append(gate_init) + Gate_list = [] + for gg in range(len(self.gate_region)): + xmin,xmax = self.gate_region[gg].get("x_range",None).split('-') + ymin,ymax = self.gate_region[gg].get("y_range",None).split('-') + zmin,zmax = self.gate_region[gg].get("z_range",None).split('-') + gate_init = Gate(xmin,xmax,ymin,ymax,zmin,zmax) + gate_init.Ef = self.gate_region[gg].get("Ef",None) + Gate_list.append(gate_init) # create dielectric - dielectric_list = [] - dielectric = self.dielectric_region.keys() - for dd in dielectric: - if dd.startswith("dielectric"): - xmin,xmax = self.dielectric_region[dd].get("x_range",None).split('-') - ymin,ymax = self.dielectric_region[dd].get("y_range",None).split('-') - zmin,zmax = self.dielectric_region[dd].get("z_range",None).split('-') - dielectric_init = Dielectric(xmin,xmax,ymin,ymax,zmin,zmax) - dielectric_init.eps = self.dielectric_region[dd].get("Ef",None) - dielectric_list.append(dielectric_init) + Dielectric_list = [] + for dd in range(len(self.dielectric_region)): + xmin,xmax = self.dielectric_region[dd].get("x_range",None).split('-') + ymin,ymax = self.dielectric_region[dd].get("y_range",None).split('-') + zmin,zmax = self.dielectric_region[dd].get("z_range",None).split('-') + dielectric_init = Dielectric(xmin,xmax,ymin,ymax,zmin,zmax) + dielectric_init.eps = self.dielectric_region[dd].get("Ef",None) + Dielectric_list.append(dielectric_init) # create interface - interface_poisson = Interface3D(grid,gate_list,dielectric_list) + interface_poisson = Interface3D(grid,Gate_list,Dielectric_list) max_diff = 1e30; iter_count=0 while max_diff > diff_acc: @@ -220,7 +217,7 @@ def poisson_negf_scf(self,diff_acc=1e-6,max_iter=100,mix_rate=0.3): pre_atom_orbs += device_atom_norbs[i] interface_poisson.free_charge[atom_gridpoint_index] = np.array(density_list) - max_diff = interface_poisson.solve_poisson(method='pyamg') + max_diff = interface_poisson.solve_poisson(method=self.poisson_options['solver']) interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi - interface_poisson.phi_old) interface_poisson.phi_old = interface_poisson.phi.copy() @@ -228,8 +225,8 @@ def poisson_negf_scf(self,diff_acc=1e-6,max_iter=100,mix_rate=0.3): iter_count += 1 print('Poisson iteration: ',iter_count,' max_diff: ',max_diff) if iter_count > max_iter: - raise RuntimeError('Poisson iteration exceeds max_iter') - + log.info(msg="Warning! Poisson iteration exceeds max_iter {}".format(int(max_iter))) + break # calculate transport properties with converged potential self.negf_compute(scf_require=False) @@ -331,6 +328,22 @@ def negf_compute(self,scf_require=False,Vbias=None): # plotting + + def read_grid(self,grid_info,structase): + x_start,x_end,x_step = grid_info.get("x_range",None).split('-') + xg = np.linspace(float(x_start),float(x_end),int(x_step)) + + y_start,y_end,y_step = grid_info.get("y_range",None).split('-') + yg = np.linspace(float(y_start),float(y_end),int(y_step)) + + z_start,z_end,z_step = grid_info.get("z_range",None).split('-') + zg = np.linspace(float(z_start),float(z_end),int(z_step)) + + device_atom_coords = structase.get_positions() + xa,ya,za = device_atom_coords[:,0],device_atom_coords[:,1],device_atom_coords[:,2] + + grid = Grid(xg,yg,zg,xa,ya,za) + return grid def fermi_dirac(self, x) -> torch.Tensor: return 1 / (1 + torch.exp(x / self.kBT)) From 5d0c2e6e6dedd00787b0ba1180711e8e04ac5935 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 10 Jan 2024 16:46:37 +0800 Subject: [PATCH 022/209] add eps0 in unit of F/Angstrom --- dptb/negf/poisson_init.py | 8 +++++--- dptb/postprocess/NEGF.py | 19 +++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index edb4f6f3..d74fbb66 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -2,10 +2,12 @@ import pyamg from pyamg.gallery import poisson from utils.constants import elementary_charge as q -from utils.constants import Boltzmann +from dptb.utils.constants import Boltzmann, eV2J from scipy.constants import epsilon_0 as eps0 #TODO:later add to untils.constants.py - +#eps0 = 8.854187817e-12 # in the unit of F/m +# As length in deeptb is in the unit of Angstrom, the unit of eps0 is F/Angstrom +eps0 = eps0*1e-10 # in the unit of F/Angstrom class Grid(object): @@ -130,7 +132,7 @@ def __init__(self,grid,gate_list,dielectric_list): self.fixed_charge = np.zeros(grid.Np) # fixed charge density self.Temperature = 300.0 # temperature in unit of Kelvin - self.kBT = Boltzmann*self.Temperature # thermal energy + self.kBT = Boltzmann*self.Temperature/eV2J # thermal energy in unit of eV # store the boundary information: xmin,xmax,ymin,ymax,zmin,zmax,gate self.boudnary_points = {i:"in" for i in range(self.grid.Np)} # initially set all points as internal diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index e398aaf3..482c83d5 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -45,7 +45,7 @@ def __init__(self, apiHrk, run_opt, jdata): # get the parameters self.ele_T = jdata["ele_T"] - self.kBT = Boltzmann * self.ele_T / eV2J + self.kBT = Boltzmann * self.ele_T / eV2J # change to eV self.e_fermi = jdata["e_fermi"] self.stru_options = j_must_have(jdata, "stru_options") self.pbc = self.stru_options["pbc"] @@ -157,12 +157,12 @@ def compute(self): if self.scf: if not self.out_density: raise RuntimeError("Error! scf calculation requires density matrix. Please set out_density to True") - self.poisson_negf_scf(diff_acc=self.poisson_options['err']) + self.poisson_negf_scf(err=self.poisson_options['err']) else: self.negf_compute(scf_require=False) - def poisson_negf_scf(self,diff_acc=1e-6,max_iter=1000,mix_rate=0.3): + def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3): # create grid grid = self.read_grid(self.poisson_options["grid"],self.deviceprop.structure) @@ -174,7 +174,7 @@ def poisson_negf_scf(self,diff_acc=1e-6,max_iter=1000,mix_rate=0.3): ymin,ymax = self.gate_region[gg].get("y_range",None).split('-') zmin,zmax = self.gate_region[gg].get("z_range",None).split('-') gate_init = Gate(xmin,xmax,ymin,ymax,zmin,zmax) - gate_init.Ef = self.gate_region[gg].get("Ef",None) + gate_init.Ef = self.gate_region[gg].get("voltage",None) #TODO: check the unit Gate_list.append(gate_init) # create dielectric @@ -184,14 +184,14 @@ def poisson_negf_scf(self,diff_acc=1e-6,max_iter=1000,mix_rate=0.3): ymin,ymax = self.dielectric_region[dd].get("y_range",None).split('-') zmin,zmax = self.dielectric_region[dd].get("z_range",None).split('-') dielectric_init = Dielectric(xmin,xmax,ymin,ymax,zmin,zmax) - dielectric_init.eps = self.dielectric_region[dd].get("Ef",None) + dielectric_init.eps = self.dielectric_region[dd].get("relative permittivity",None) Dielectric_list.append(dielectric_init) # create interface interface_poisson = Interface3D(grid,Gate_list,Dielectric_list) max_diff = 1e30; iter_count=0 - while max_diff > diff_acc: + while max_diff > err: # update Hamiltonian by modifying onsite energy with potential atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) @@ -208,12 +208,11 @@ def poisson_negf_scf(self,diff_acc=1e-6,max_iter=1000,mix_rate=0.3): # update electron density for solving Poisson equation DM_eq,DM_neq = self.out["DM_eq"], self.out["DM_neq"] - DM = DM_eq + DM_neq - elec_density = torch.diag(DM) + elec_density = torch.diag(DM_eq+DM_neq) density_list = [] pre_atom_orbs = 0 for i in range(len(device_atom_norbs)): - density_list.append(torch.sum(elec_density[pre_atom_orbs : pre_atom_orbs+atom_gridpoint_index[i]])) + density_list.append(torch.sum(elec_density[pre_atom_orbs : pre_atom_orbs+device_atom_norbs[i]]).numpy()) pre_atom_orbs += device_atom_norbs[i] interface_poisson.free_charge[atom_gridpoint_index] = np.array(density_list) @@ -229,7 +228,7 @@ def poisson_negf_scf(self,diff_acc=1e-6,max_iter=1000,mix_rate=0.3): break # calculate transport properties with converged potential - self.negf_compute(scf_require=False) + self.negf_compute(scf_require=False,Vbias=potential_tensor) def negf_compute(self,scf_require=False,Vbias=None): From a61c0ae7776f1fbdd9bd19e1cc2cc2cbcb170050 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 10 Jan 2024 16:47:20 +0800 Subject: [PATCH 023/209] delete poisson_scf.py, which has been added to NEGF.py --- dptb/negf/poisson_scf.py | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 dptb/negf/poisson_scf.py diff --git a/dptb/negf/poisson_scf.py b/dptb/negf/poisson_scf.py deleted file mode 100644 index 39927ea3..00000000 --- a/dptb/negf/poisson_scf.py +++ /dev/null @@ -1,18 +0,0 @@ - - - - -def poisson_negf_scf(grid,interface,device,acc=1e-6,max_iter=100): - - interface.phi_old = interface.phi.copy() - scf_count = 0 - - max_diff = 1e30 - while max_diff > acc and scf_count < max_iter: - scf_count += 1 - print('SCF iteration: ',scf_count) - device.phi = interface.phi[interface.grid.atom_index] - - - max_diff = np.max(np.abs(device.phi - interface.phi_old)) - interface.phi_old = device.phi.copy() From 161e396dda4763e236d84c3328c1413c7ce8694a Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Jan 2024 22:15:46 +0800 Subject: [PATCH 024/209] clean import --- dptb/postprocess/NEGF.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 482c83d5..bf269581 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -19,7 +19,6 @@ import numpy as np from dptb.utils.make_kpoints import kmesh_sampling import logging -from negf.poisson_scf import poisson_negf_scf # TODO : move this to dptb.negf from negf.poisson_init import Grid,Interface3D,Gate,Dielectric log = logging.getLogger(__name__) @@ -107,7 +106,7 @@ def __init__(self, apiHrk, run_opt, jdata): self.generate_energy_grid() self.out = {} - ## Poisson equation + ## Poisson equation settings self.poisson_options = j_must_have(jdata, "poisson_options") self.gate_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("gate")] self.dielectric_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("dielectric")] From c4b1e777155936d070268a290e421a87991cf114 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 12 Jan 2024 14:01:32 +0800 Subject: [PATCH 025/209] fix import issue and str2float in gate and dieelctric definition --- dptb/negf/poisson_init.py | 4 ++-- dptb/postprocess/NEGF.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index d74fbb66..c2bb472a 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -1,7 +1,7 @@ import numpy as np -import pyamg +import pyamg #TODO: later add it to optional dependencies,like sisl from pyamg.gallery import poisson -from utils.constants import elementary_charge as q +from dptb.utils.constants import elementary_charge as q from dptb.utils.constants import Boltzmann, eV2J from scipy.constants import epsilon_0 as eps0 #TODO:later add to untils.constants.py diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index bf269581..2a9727c2 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -19,7 +19,7 @@ import numpy as np from dptb.utils.make_kpoints import kmesh_sampling import logging -from negf.poisson_init import Grid,Interface3D,Gate,Dielectric +from dptb.negf.poisson_init import Grid,Interface3D,Gate,Dielectric log = logging.getLogger(__name__) @@ -172,7 +172,7 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3): xmin,xmax = self.gate_region[gg].get("x_range",None).split('-') ymin,ymax = self.gate_region[gg].get("y_range",None).split('-') zmin,zmax = self.gate_region[gg].get("z_range",None).split('-') - gate_init = Gate(xmin,xmax,ymin,ymax,zmin,zmax) + gate_init = Gate(float(xmin),float(xmax),float(ymin),float(ymax),float(zmin),float(zmax)) gate_init.Ef = self.gate_region[gg].get("voltage",None) #TODO: check the unit Gate_list.append(gate_init) @@ -182,8 +182,8 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3): xmin,xmax = self.dielectric_region[dd].get("x_range",None).split('-') ymin,ymax = self.dielectric_region[dd].get("y_range",None).split('-') zmin,zmax = self.dielectric_region[dd].get("z_range",None).split('-') - dielectric_init = Dielectric(xmin,xmax,ymin,ymax,zmin,zmax) - dielectric_init.eps = self.dielectric_region[dd].get("relative permittivity",None) + dielectric_init = Dielectric(float(xmin),float(xmax),float(ymin),float(ymax),float(zmin),float(zmax)) + dielectric_init.eps = float(self.dielectric_region[dd].get("relative permittivity",None)) Dielectric_list.append(dielectric_init) # create interface From 4d18843d041513997d25ac5af7881666d9c90b47 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 12 Jan 2024 14:34:09 +0800 Subject: [PATCH 026/209] add argcheck for gate and dielectric --- dptb/postprocess/NEGF.py | 20 ++++++++++---------- dptb/utils/argcheck.py | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 2a9727c2..3b6f4a80 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -161,7 +161,7 @@ def compute(self): self.negf_compute(scf_require=False) - def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3): + def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3): #TODO: add max_iter and mix_rate to jdata # create grid grid = self.read_grid(self.poisson_options["grid"],self.deviceprop.structure) @@ -169,9 +169,9 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3): # create gate Gate_list = [] for gg in range(len(self.gate_region)): - xmin,xmax = self.gate_region[gg].get("x_range",None).split('-') - ymin,ymax = self.gate_region[gg].get("y_range",None).split('-') - zmin,zmax = self.gate_region[gg].get("z_range",None).split('-') + xmin,xmax = self.gate_region[gg].get("x_range",None).split(':') + ymin,ymax = self.gate_region[gg].get("y_range",None).split(':') + zmin,zmax = self.gate_region[gg].get("z_range",None).split(':') gate_init = Gate(float(xmin),float(xmax),float(ymin),float(ymax),float(zmin),float(zmax)) gate_init.Ef = self.gate_region[gg].get("voltage",None) #TODO: check the unit Gate_list.append(gate_init) @@ -179,9 +179,9 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3): # create dielectric Dielectric_list = [] for dd in range(len(self.dielectric_region)): - xmin,xmax = self.dielectric_region[dd].get("x_range",None).split('-') - ymin,ymax = self.dielectric_region[dd].get("y_range",None).split('-') - zmin,zmax = self.dielectric_region[dd].get("z_range",None).split('-') + xmin,xmax = self.dielectric_region[dd].get("x_range",None).split(':') + ymin,ymax = self.dielectric_region[dd].get("y_range",None).split(':') + zmin,zmax = self.dielectric_region[dd].get("z_range",None).split(':') dielectric_init = Dielectric(float(xmin),float(xmax),float(ymin),float(ymax),float(zmin),float(zmax)) dielectric_init.eps = float(self.dielectric_region[dd].get("relative permittivity",None)) Dielectric_list.append(dielectric_init) @@ -328,13 +328,13 @@ def negf_compute(self,scf_require=False,Vbias=None): def read_grid(self,grid_info,structase): - x_start,x_end,x_step = grid_info.get("x_range",None).split('-') + x_start,x_end,x_step = grid_info.get("x_range",None).split(':') xg = np.linspace(float(x_start),float(x_end),int(x_step)) - y_start,y_end,y_step = grid_info.get("y_range",None).split('-') + y_start,y_end,y_step = grid_info.get("y_range",None).split(':') yg = np.linspace(float(y_start),float(y_end),int(y_step)) - z_start,z_end,z_step = grid_info.get("z_range",None).split('-') + z_start,z_end,z_step = grid_info.get("z_range",None).split(':') zg = np.linspace(float(z_start),float(z_end),int(z_step)) device_atom_coords = structase.get_positions() diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 8b4e19b7..1adf203e 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -601,8 +601,10 @@ def PDIIS(): def poisson_options(): doc_solver = "" doc_fmm = "" + doc_pyamg= "" return Variant("solver", [ - Argument("fmm", dict, fmm(), doc=doc_fmm) + Argument("fmm", dict, fmm(), doc=doc_fmm), + Argument("pyamg", dict, pyamg(), doc=doc_pyamg) ], optional=True, default_tag="fmm", doc=doc_solver) def density_options(): @@ -629,6 +631,41 @@ def fmm(): Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err) ] +def pyamg(): + doc_err = "" + doc_gate="" + doc_dielectric="" + return [ + Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err), + Argument("grid_top", dict, optional=False, sub_fields=gate(), doc=doc_gate), + Argument("grid_bottom", dict, optional=False, sub_fields=gate(), doc=doc_gate), + Argument("dielectric_region", dict, optional=False, sub_fields=dielectric(), doc=doc_dielectric) + ] + +def gate(): + doc_xrange="" + doc_yrange="" + doc_zrange="" + doc_voltage="" + return [ + Argument("x_range", str, optional=False, doc=doc_xrange), + Argument("y_range", str, optional=False, doc=doc_yrange), + Argument("z_range", str, optional=False, doc=doc_zrange), + Argument("voltage", [int, float], optional=False, doc=doc_voltage) + ] + +def dielectric(): + doc_xrange="" + doc_yrange="" + doc_zrange="" + doc_permittivity="" + return [ + Argument("x_range", str, optional=False, doc=doc_xrange), + Argument("y_range", str, optional=False, doc=doc_yrange), + Argument("z_range", str, optional=False, doc=doc_zrange), + Argument("relative permittivity", [int, float], optional=False, doc=doc_permittivity) + ] + def normalize_run(data): doc_property = "" doc_model_options = "" From 800264b4dd57108f0231c42a89f1487ffb6efff5 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 12 Jan 2024 14:40:09 +0800 Subject: [PATCH 027/209] add grid --- dptb/utils/argcheck.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 1adf203e..ee595777 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -633,15 +633,27 @@ def fmm(): def pyamg(): doc_err = "" + doc_grid="" doc_gate="" doc_dielectric="" return [ Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err), - Argument("grid_top", dict, optional=False, sub_fields=gate(), doc=doc_gate), - Argument("grid_bottom", dict, optional=False, sub_fields=gate(), doc=doc_gate), + Argument("grid", dict, optional=False, sub_fields=grid(), doc=doc_grid), + Argument("gate_top", dict, optional=False, sub_fields=gate(), doc=doc_gate), + Argument("gate_bottom", dict, optional=False, sub_fields=gate(), doc=doc_gate), Argument("dielectric_region", dict, optional=False, sub_fields=dielectric(), doc=doc_dielectric) ] +def grid(): + doc_xrange="" + doc_yrange="" + doc_zrange="" + return [ + Argument("x_range", str, optional=False, doc=doc_xrange), + Argument("y_range", str, optional=False, doc=doc_yrange), + Argument("z_range", str, optional=False, doc=doc_zrange), + ] + def gate(): doc_xrange="" doc_yrange="" From 06779b9354166e40bc4da721430b156aa57a3716 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 12 Jan 2024 15:43:49 +0800 Subject: [PATCH 028/209] update for run --- dptb/negf/negf_hamiltonian_init.py | 1 + dptb/negf/poisson_init.py | 11 +++++++---- dptb/postprocess/NEGF.py | 12 ++++++------ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index a6f6ffe5..ee0b31e8 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -102,6 +102,7 @@ def initialize(self, kpoints, block_tridiagnal=False): proj_device_id = [0,0] proj_device_id[0] = n_proj_atom_pre proj_device_id[1] = n_proj_atom_pre + n_proj_atom_device + self.proj_device_id = proj_device_id projatoms = self.apiH.structure.projatoms self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index c2bb472a..ac8c1ddf 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -169,11 +169,14 @@ def potential_eps_get(self,region_list): # ingore the lead potential temporarily for i in range(len(region_list)): # find gate region in grid - index=np.nonzero((region_list[i].xmin<=self.grid.grid_coord[0])&(region_list[i].xmax>=self.grid.grid_coord[0])& - (region_list[i].ymin<=self.grid.grid_coord[1])&(region_list[i].ymax>=self.grid.grid_coord[1])& - (region_list[i].zmin<=self.grid.grid_coord[2])&(region_list[i].zmax>=self.grid.grid_coord[2])) + index=np.nonzero((region_list[i].xmin<=self.grid.grid_coord[:,0])& + (region_list[i].xmax>=self.grid.grid_coord[:,0])& + (region_list[i].ymin<=self.grid.grid_coord[:,1])& + (region_list[i].ymax>=self.grid.grid_coord[:,1])& + (region_list[i].zmin<=self.grid.grid_coord[:,2])& + (region_list[i].zmax>=self.grid.grid_coord[:,2]))[0] if region_list[i].__class__.__name__ == 'Gate': #attribute gate potential to the corresponding grid points - self.boudnary_points[index] = "Gate" + self.boudnary_points[tuple(index)] = "Gate" self.lead_gate_potential[index] = region_list[i].Ef elif region_list[i].__class__.__name__ == 'Dielectric': self.eps[index] = region_list[i].eps diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 3b6f4a80..141904a7 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -328,14 +328,14 @@ def negf_compute(self,scf_require=False,Vbias=None): def read_grid(self,grid_info,structase): - x_start,x_end,x_step = grid_info.get("x_range",None).split(':') - xg = np.linspace(float(x_start),float(x_end),int(x_step)) + x_start,x_end,x_num = grid_info.get("x_range",None).split(':') + xg = np.linspace(float(x_start),float(x_end),int(x_num)) - y_start,y_end,y_step = grid_info.get("y_range",None).split(':') - yg = np.linspace(float(y_start),float(y_end),int(y_step)) + y_start,y_end,y_num = grid_info.get("y_range",None).split(':') + yg = np.linspace(float(y_start),float(y_end),int(y_num)) - z_start,z_end,z_step = grid_info.get("z_range",None).split(':') - zg = np.linspace(float(z_start),float(z_end),int(z_step)) + z_start,z_end,z_num = grid_info.get("z_range",None).split(':') + zg = np.linspace(float(z_start),float(z_end),int(z_num)) device_atom_coords = structase.get_positions() xa,ya,za = device_atom_coords[:,0],device_atom_coords[:,1],device_atom_coords[:,2] From 281a2175a6d7b1399a81d6cbefef5ec6cd42f617 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 12 Jan 2024 15:45:05 +0800 Subject: [PATCH 029/209] fix pyamg_solver.solve bug --- dptb/negf/poisson_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index ac8c1ddf..a3e8e0ef 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -282,7 +282,7 @@ def callback(x): ) ) - x = pyamg_solver( + x = pyamg_solver.solve( b, tol=tolerance, callback=callback, From a5ed3ecc3ffba3382d5f355bce9082ed16606cac Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 12 Jan 2024 19:46:23 +0800 Subject: [PATCH 030/209] add tolerance et. to argcheck.py --- dptb/negf/poisson_init.py | 14 ++++++-------- dptb/postprocess/NEGF.py | 9 +++++---- dptb/utils/argcheck.py | 6 ++++++ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index a3e8e0ef..f9f78ca2 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -4,7 +4,7 @@ from dptb.utils.constants import elementary_charge as q from dptb.utils.constants import Boltzmann, eV2J from scipy.constants import epsilon_0 as eps0 #TODO:later add to untils.constants.py - +from scipy.sparse import csr_matrix #eps0 = 8.854187817e-12 # in the unit of F/m # As length in deeptb is in the unit of Angstrom, the unit of eps0 is F/Angstrom eps0 = eps0*1e-10 # in the unit of F/Angstrom @@ -187,11 +187,9 @@ def to_pyamg(self,dtype=None): # convert to amg format A,b matrix if dtype == None: dtype = np.float64 - A = poisson(self.grid.shape,format='csr',dtype=dtype) + # A = poisson(self.grid.shape,format='csr',dtype=dtype) + A = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) b = np.zeros(A.shape[0],dtype=A.dtype) - A.data[:] = 0 # set all elements to zero - # later we set non-zero elements to A, the indices and indptr are not changed as the default grid order in pyamg - # is the same as that of self.grid.grid_coord self.construct_poisson(A,b) return A,b @@ -265,7 +263,7 @@ def construct_poisson(self,A,b): #TODO: add lead potential. For dptb-negf, we only need to change zmin and zmax as lead - def solve_poisson_pyamg(self,A,b,tolerance=1e-12,accel=None): + def solve_poisson_pyamg(self,A,b,tolerance=1e-7,accel=None): # solve the Poisson equation print('Solve Poisson equation by pyamg') pyamg_solver = pyamg.aggregation.smoothed_aggregation_solver(A, max_levels=1000) @@ -295,11 +293,11 @@ def callback(x): return x - def solve_poisson(self,method='pyamg'): + def solve_poisson(self,method='pyamg',tolerance=1e-7): # solve poisson equation: if method == 'pyamg': A,b = self.to_pyamg() - self.phi = self.solve_poisson_pyamg(A,b) + self.phi = self.solve_poisson_pyamg(A,b,tolerance) max_diff = np.max(abs(self.phi-self.phi_old)) return max_diff diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 141904a7..5cbb056e 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -156,12 +156,13 @@ def compute(self): if self.scf: if not self.out_density: raise RuntimeError("Error! scf calculation requires density matrix. Please set out_density to True") - self.poisson_negf_scf(err=self.poisson_options['err']) + self.poisson_negf_scf(err=self.poisson_options['err'],tolerance=self.poisson_options['tolerance'],\ + max_iter=self.poisson_options['max_iter'],mix_rate=self.poisson_options['mix_rate']) else: self.negf_compute(scf_require=False) - def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3): #TODO: add max_iter and mix_rate to jdata + def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): #TODO: add max_iter and mix_rate to jdata # create grid grid = self.read_grid(self.poisson_options["grid"],self.deviceprop.structure) @@ -173,7 +174,7 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3): #TODO: add max_i ymin,ymax = self.gate_region[gg].get("y_range",None).split(':') zmin,zmax = self.gate_region[gg].get("z_range",None).split(':') gate_init = Gate(float(xmin),float(xmax),float(ymin),float(ymax),float(zmin),float(zmax)) - gate_init.Ef = self.gate_region[gg].get("voltage",None) #TODO: check the unit + gate_init.Ef = float(self.gate_region[gg].get("voltage",None)) # in unit of volt Gate_list.append(gate_init) # create dielectric @@ -215,7 +216,7 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3): #TODO: add max_i pre_atom_orbs += device_atom_norbs[i] interface_poisson.free_charge[atom_gridpoint_index] = np.array(density_list) - max_diff = interface_poisson.solve_poisson(method=self.poisson_options['solver']) + max_diff = interface_poisson.solve_poisson(method=self.poisson_options['solver'],tolerance=tolerance) interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi - interface_poisson.phi_old) interface_poisson.phi_old = interface_poisson.phi.copy() diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index ee595777..76de1e86 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -633,11 +633,17 @@ def fmm(): def pyamg(): doc_err = "" + doc_tolerance="" doc_grid="" doc_gate="" doc_dielectric="" + doc_max_iter="" + doc_mix_rate="" return [ Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err), + Argument("tolerance", [int, float], optional=True, default=1e-7, doc=doc_tolerance), + Argument("max_iter", int, optional=True, default=100, doc=doc_max_iter), + Argument("mix_rate", int, optional=True, default=0.25, doc=doc_mix_rate), Argument("grid", dict, optional=False, sub_fields=grid(), doc=doc_grid), Argument("gate_top", dict, optional=False, sub_fields=gate(), doc=doc_gate), Argument("gate_bottom", dict, optional=False, sub_fields=gate(), doc=doc_gate), From 10dbc2b4619e75a7c12695f3c24523895a702151 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 22 Jan 2024 22:22:20 +0800 Subject: [PATCH 031/209] add some comments in NEGF.py and poisson_init --- dptb/negf/poisson_init.py | 32 +++++++++++++++++--------------- dptb/postprocess/NEGF.py | 11 ++++++----- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index f9f78ca2..34f16003 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -32,15 +32,15 @@ def __init__(self,xg,yg,zg,xa,ya,za): # create meshgrid xmesh,ymesh,zmesh = np.meshgrid(self.xall,self.yall,self.zall) - self.xmesh = xmesh.flatten() - self.ymesh = ymesh.flatten() - self.zmesh = zmesh.flatten() - self.grid_coord = np.array([self.xmesh,self.ymesh,self.zmesh]).T #(Np,3) - sorted_indices = np.lexsort((self.xmesh , self.ymesh , self.zmesh)) + xmesh = xmesh.flatten() + ymesh = ymesh.flatten() + zmesh = zmesh.flatten() + self.grid_coord = np.array([xmesh,ymesh,zmesh]).T #(Np,3) + sorted_indices = np.lexsort((xmesh,ymesh,zmesh)) self.grid_coord = self.grid_coord[sorted_indices] # sort the grid points firstly along x, then y, lastly z ## check the number of grid points self.Np = int(len(self.xall)*len(self.yall)*len(self.zall)) - assert self.Np == len(self.xmesh) + assert self.Np == len(xmesh) assert self.grid_coord.shape[0] == self.Np print('Number of grid points: ',self.Np,' grid shape: ',self.grid_coord.shape,' Number of atoms: ',self.Na) @@ -75,7 +75,9 @@ def find_atom_index(self,xa,ya,za): swap = {} for atom_index in range(self.Na): for gp_index in range(self.Np): - if xa[atom_index]==self.xmesh[gp_index] and ya[atom_index]==self.ymesh[gp_index] and za[atom_index]==self.zmesh[gp_index]: + if xa[atom_index]==self.grid_coord[gp_index][0] and \ + ya[atom_index]==self.grid_coord[gp_index][1] and \ + za[atom_index]==self.grid_coord[gp_index][2]: swap.update({atom_index:gp_index}) return swap @@ -125,7 +127,7 @@ def __init__(self,grid,gate_list,dielectric_list): raise ValueError('Unknown region type in Dielectric list: ',dielectric_list[i].__class__.__name__) self.grid = grid - self.eps = np.zeros(grid.Np) # dielectric permittivity + self.eps = np.ones(grid.Np) # dielectric permittivity self.phi = np.zeros(grid.Np) # potential self.phi_old = np.zeros(grid.Np) # potential in the previous iteration self.free_charge = np.zeros(grid.Np) # free charge density @@ -146,17 +148,17 @@ def __init__(self,grid,gate_list,dielectric_list): def boudnary_points_get(self): # set the boundary points for i in range(self.grid.Np): - if self.grid.xmesh[i] == np.min(self.grid.xall): + if self.grid.grid_coord[i,0] == np.min(self.grid.xall): self.boudnary_points[i] = "xmin" - elif self.grid.xmesh[i] == np.max(self.grid.xall): + elif self.grid.grid_coord[i,0] == np.max(self.grid.xall): self.boudnary_points[i] = "xmax" - elif self.grid.ymesh[i] == np.min(self.grid.yall): + elif self.grid.grid_coord[i,1] == np.min(self.grid.yall): self.boudnary_points[i] = "ymin" - elif self.grid.ymesh[i] == np.max(self.grid.yall): + elif self.grid.grid_coord[i,1] == np.max(self.grid.yall): self.boudnary_points[i] = "ymax" - elif self.grid.zmesh[i] == np.min(self.grid.zall): + elif self.grid.grid_coord[i,2] == np.min(self.grid.zall): self.boudnary_points[i] = "zmin" - elif self.grid.zmesh[i] == np.max(self.grid.zall): + elif self.grid.grid_coord[i,2] == np.max(self.grid.zall): self.boudnary_points[i] = "zmax" internal_NP = 0 for i in range(self.grid.Np): @@ -176,7 +178,7 @@ def potential_eps_get(self,region_list): (region_list[i].zmin<=self.grid.grid_coord[:,2])& (region_list[i].zmax>=self.grid.grid_coord[:,2]))[0] if region_list[i].__class__.__name__ == 'Gate': #attribute gate potential to the corresponding grid points - self.boudnary_points[tuple(index)] = "Gate" + self.boudnary_points.update({index[i]: "Gate" for i in range(len(index))}) self.lead_gate_potential[index] = region_list[i].Ef elif region_list[i].__class__.__name__ == 'Dielectric': self.eps[index] = region_list[i].eps diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 5cbb056e..96e8764b 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -162,10 +162,10 @@ def compute(self): self.negf_compute(scf_require=False) - def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): #TODO: add max_iter and mix_rate to jdata + def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # create grid - grid = self.read_grid(self.poisson_options["grid"],self.deviceprop.structure) + grid = self.get_grid(self.poisson_options["grid"],self.deviceprop.structure) # create gate Gate_list = [] @@ -204,9 +204,10 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # potential_list.append(potential_atom[i]*torch.ones(device_atom_norbs[i])) potential_tensor = torch.cat(potential_list) self.negf_compute(scf_require=True,Vbias=potential_tensor) - + # Vbias makes sense for orthogonal basis as in NanoTCAD + # TODO: check if Vbias makes sense for non-orthogonal basis - # update electron density for solving Poisson equation + # update electron density for solving Poisson equation SCF DM_eq,DM_neq = self.out["DM_eq"], self.out["DM_neq"] elec_density = torch.diag(DM_eq+DM_neq) density_list = [] @@ -328,7 +329,7 @@ def negf_compute(self,scf_require=False,Vbias=None): # plotting - def read_grid(self,grid_info,structase): + def get_grid(self,grid_info,structase): x_start,x_end,x_num = grid_info.get("x_range",None).split(':') xg = np.linspace(float(x_start),float(x_end),int(x_num)) From 45746674d7f4bb982e06accb1142c7dae7d4d4d5 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 23 Jan 2024 17:46:57 +0800 Subject: [PATCH 032/209] update poisson_negf_scf --- dptb/postprocess/NEGF.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 96e8764b..1a4fa4fd 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -108,6 +108,7 @@ def __init__(self, apiHrk, run_opt, jdata): ## Poisson equation settings self.poisson_options = j_must_have(jdata, "poisson_options") + self.poisson_zero_charge = {} self.gate_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("gate")] self.dielectric_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("dielectric")] @@ -159,8 +160,8 @@ def compute(self): self.poisson_negf_scf(err=self.poisson_options['err'],tolerance=self.poisson_options['tolerance'],\ max_iter=self.poisson_options['max_iter'],mix_rate=self.poisson_options['mix_rate']) else: - self.negf_compute(scf_require=False) - + potential_add = None + self.negf_compute(scf_require=False,Vbias=potential_add) def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): @@ -190,7 +191,8 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # create interface interface_poisson = Interface3D(grid,Gate_list,Dielectric_list) - max_diff = 1e30; iter_count=0 + max_diff = 1e30; max_diff_list = [] + iter_count=0 while max_diff > err: # update Hamiltonian by modifying onsite energy with potential @@ -210,13 +212,16 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # update electron density for solving Poisson equation SCF DM_eq,DM_neq = self.out["DM_eq"], self.out["DM_neq"] elec_density = torch.diag(DM_eq+DM_neq) - density_list = [] + + + elec_density_per_atom = [] pre_atom_orbs = 0 for i in range(len(device_atom_norbs)): - density_list.append(torch.sum(elec_density[pre_atom_orbs : pre_atom_orbs+device_atom_norbs[i]]).numpy()) + elec_density_per_atom.append(torch.sum(elec_density[pre_atom_orbs : pre_atom_orbs+device_atom_norbs[i]]).numpy()) pre_atom_orbs += device_atom_norbs[i] - interface_poisson.free_charge[atom_gridpoint_index] = np.array(density_list) + interface_poisson.free_charge[atom_gridpoint_index] =\ + -1*np.array(elec_density_per_atom)+self.poisson_zero_charge[str(self.kpoints[0])].numpy() max_diff = interface_poisson.solve_poisson(method=self.poisson_options['solver'],tolerance=tolerance) interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi - interface_poisson.phi_old) @@ -224,10 +229,20 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): iter_count += 1 print('Poisson iteration: ',iter_count,' max_diff: ',max_diff) + max_diff_list.append(max_diff) if iter_count > max_iter: log.info(msg="Warning! Poisson iteration exceeds max_iter {}".format(int(max_iter))) break + self.poisson_out = {} + self.poisson_out['potential'] = torch.tensor(interface_poisson.phi) + self.poisson_out['grid_point_number'] = interface_poisson.grid.Np + self.poisson_out['grid'] = torch.tensor(interface_poisson.grid.grid_coord) + self.poisson_out['free_charge_at_atom'] = torch.tensor(interface_poisson.free_charge[atom_gridpoint_index]) + self.poisson_out['max_diff_list'] = torch.tensor(max_diff_list) + + torch.save(self.poisson_out, self.results_path+"/poisson.out.pth") + # calculate transport properties with converged potential self.negf_compute(scf_require=False,Vbias=potential_tensor) From 565af97a38af4849a881e156f208bd7bc2d4b8f4 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 23 Jan 2024 17:50:21 +0800 Subject: [PATCH 033/209] update negf_compute --- dptb/postprocess/NEGF.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 1a4fa4fd..c683f3b5 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -264,6 +264,8 @@ def negf_compute(self,scf_require=False,Vbias=None): # computing properties that is functions of E if hasattr(self, "uni_grid"): self.out["k"] = k + dE = abs(self.uni_grid[1] - self.uni_grid[0]) + self.poisson_zero_charge.update({str(k):0}) # reset zero charge for each k for e in self.uni_grid: log.info(msg="computing green's function at e = {:.3f}".format(float(e))) leads = self.stru_options.keys() @@ -293,7 +295,9 @@ def negf_compute(self,scf_require=False,Vbias=None): if self.out_ldos: prop = self.out.setdefault("LDOS", []) prop.append(self.compute_LDOS(k)) - + else: + if e < 0: + self.poisson_zero_charge[str(k)] += self.compute_LDOS(k)*dE # whether scf_require is True or False, density are computed for Poisson-NEGF SCF if self.out_density or self.out_potential: From f326a0efca0374cf29856dcb54b574a8c894af04 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Jan 2024 09:53:41 +0800 Subject: [PATCH 034/209] update NEGF.py and poisson_init.py --- dptb/negf/poisson_init.py | 4 ++++ dptb/postprocess/NEGF.py | 11 +++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 34f16003..55fa93c7 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -169,6 +169,7 @@ def boudnary_points_get(self): def potential_eps_get(self,region_list): # set the gate potential # ingore the lead potential temporarily + gate_point = 0 for i in range(len(region_list)): # find gate region in grid index=np.nonzero((region_list[i].xmin<=self.grid.grid_coord[:,0])& @@ -180,10 +181,12 @@ def potential_eps_get(self,region_list): if region_list[i].__class__.__name__ == 'Gate': #attribute gate potential to the corresponding grid points self.boudnary_points.update({index[i]: "Gate" for i in range(len(index))}) self.lead_gate_potential[index] = region_list[i].Ef + gate_point += len(index) elif region_list[i].__class__.__name__ == 'Dielectric': self.eps[index] = region_list[i].eps else: raise ValueError('Unknown region type: ',region_list[i].__class__.__name__) + print('Number of gate points: ',gate_point) def to_pyamg(self,dtype=None): # convert to amg format A,b matrix @@ -247,6 +250,7 @@ def construct_poisson(self,A,b): else:# boundary points A[gp_index,gp_index] = 1.0 + assert b[gp_index] == 0.0 if self.boudnary_points[gp_index] == "xmin": A[gp_index,gp_index+1] = -1.0 elif self.boudnary_points[gp_index] == "xmax": diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index c683f3b5..07298c4f 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -220,6 +220,9 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): elec_density_per_atom.append(torch.sum(elec_density[pre_atom_orbs : pre_atom_orbs+device_atom_norbs[i]]).numpy()) pre_atom_orbs += device_atom_norbs[i] + # TODO: check the sign of free_charge + # TODO: check the spin degenracy + # TODO: add k summation operation interface_poisson.free_charge[atom_gridpoint_index] =\ -1*np.array(elec_density_per_atom)+self.poisson_zero_charge[str(self.kpoints[0])].numpy() max_diff = interface_poisson.solve_poisson(method=self.poisson_options['solver'],tolerance=tolerance) @@ -296,12 +299,12 @@ def negf_compute(self,scf_require=False,Vbias=None): prop = self.out.setdefault("LDOS", []) prop.append(self.compute_LDOS(k)) else: - if e < 0: + if e < 0: # 0 is the fermi level read from jdata self.poisson_zero_charge[str(k)] += self.compute_LDOS(k)*dE # whether scf_require is True or False, density are computed for Poisson-NEGF SCF if self.out_density or self.out_potential: - self.out["DM_eq"], self.out["DM_neq"] = self.compute_density(k) + self.out["DM_eq"], self.out["DM_neq"] = self.compute_density(k,Vbias) if self.out_potential: pass @@ -389,8 +392,8 @@ def compute_LDOS(self, kpoint): def compute_current_nscf(self, kpoint, ee, tc): return self.deviceprop._cal_current_nscf_(ee, tc) - def compute_density(self, kpoint): - DM_eq, DM_neq = self.density.integrate(deviceprop=self.deviceprop, kpoint=kpoint) + def compute_density(self, kpoint,Vbias): + DM_eq, DM_neq = self.density.integrate(deviceprop=self.deviceprop, kpoint=kpoint, Vbias=Vbias) return DM_eq, DM_neq def compute_current(self, kpoint): From eb7b49216deb7ab63d6346613967a51823d19c3f Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Jan 2024 09:57:36 +0800 Subject: [PATCH 035/209] update density.py --- dptb/negf/density.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index ea8b29d5..dd263dfb 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -140,7 +140,7 @@ def __init__(self, R, M_cut, n_gauss): self.R = R self.n_gauss = n_gauss - def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.): + def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.,Vbias=None): '''calculates the equilibrium and non-equilibrium density matrices for a given k-point. Parameters @@ -166,13 +166,15 @@ def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.): poles = 1j* self.poles * kBT + deviceprop.lead_L.mu - deviceprop.mu # left lead expression for rho_eq deviceprop.lead_L.self_energy(kpoint=kpoint, energy=1j*self.R-deviceprop.mu) deviceprop.lead_R.self_energy(kpoint=kpoint, energy=1j*self.R-deviceprop.mu) - deviceprop.cal_green_function(energy=1j*self.R-deviceprop.mu, kpoint=kpoint, block_tridiagonal=False) + deviceprop.cal_green_function(energy=1j*self.R-deviceprop.mu, kpoint=kpoint, block_tridiagonal=False, + Vbias = Vbias) g0 = deviceprop.grd[0] DM_eq = 1.0j * self.R * g0 for i, e in enumerate(poles): deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) - deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device) + deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device,\ + Vbias = Vbias) term = ((-4 * 1j * kBT) * deviceprop.grd[0] * self.residues[i]).imag DM_eq -= term From 6ed2bb989a6696898ea80da84965443755ac91a1 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Jan 2024 10:07:04 +0800 Subject: [PATCH 036/209] update device_property.py --- dptb/negf/device_property.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index b8cb0fa8..a0fb4c51 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -149,8 +149,8 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr else: self.V = Vbias - if not hasattr(self, "hd") or not hasattr(self, "sd"): - self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) + # if not hasattr(self, "hd") or not hasattr(self, "sd"): + self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) s_in = [torch.zeros(i.shape).cdouble() for i in self.hd] # for i, e in tqdm(enumerate(ee), desc="Compute green functions: "): From 3ba5b13aebe48220c1a666c841acd5163ef02598 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Jan 2024 11:17:41 +0800 Subject: [PATCH 037/209] find the bug calculating different kpoints but get same result --- dptb/negf/device_property.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index a0fb4c51..ba5c0f4f 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -149,7 +149,8 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr else: self.V = Vbias - # if not hasattr(self, "hd") or not hasattr(self, "sd"): + # if not hasattr(self, "hd") or not hasattr(self, "sd"): + #maybe the reason why different kpoint has different green function self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) s_in = [torch.zeros(i.shape).cdouble() for i in self.hd] From 0f156cecb853f87bd8523f82c1255f74b69fb570 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Jan 2024 14:25:55 +0800 Subject: [PATCH 038/209] add newK_flag and newV_flag --- dptb/negf/device_property.py | 38 ++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index ba5c0f4f..d35bac02 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -87,6 +87,9 @@ def __init__(self, hamiltonian, structure, results_path, e_T=300, efermi=0.) -> self.e_T = e_T self.efermi = efermi self.mu = self.efermi + self.kpoint = None # kpoint for cal_green_function + self.newK_flag = None # whether the kpoint is new or not in cal_green_function + self.newV_flag = None # whether the voltage is new or not in cal_green_function def set_leadLR(self, lead_L, lead_R): '''initialize the left and right lead in Device object @@ -132,26 +135,53 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr energy = torch.tensor(energy, dtype=torch.complex128) self.block_tridiagonal = block_tridiagonal - self.kpoint = kpoint + if self.kpoint is None: + self.kpoint = kpoint + self.newK_flag = True + elif abs(self.kpoint - kpoint).sum() > 1e-5: + self.kpoint = kpoint + self.newK_flag = True + else: + self.newK_flag = False + # if V is not None: # HD_ = self.attachPotential(HD, SD, V) # else: # HD_ = HD + if hasattr(self, "V"): + self.oldV = self.V + else: + self.oldV = None if Vbias is None: if os.path.exists(os.path.join(self.results_path, "POTENTIAL.pth")): self.V = torch.load(os.path.join(self.results_path, "POTENTIAL.pth")) elif abs(self.mu - self.efermi) > 1e-7: - self.V = self.efermi - self.mu + self.V = torch.tensor(self.efermi - self.mu) else: - self.V = 0. + self.V = torch.tensor(0.) else: self.V = Vbias + assert torch.is_tensor(self.V) + if not self.oldV is None: + if abs(self.V - self.oldV).sum() > 1e-5: + self.newV_flag = True + else: + self.newV_flag = False + else: + self.newV_flag = False + + # if not hasattr(self, "hd") or not hasattr(self, "sd"): #maybe the reason why different kpoint has different green function - self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) + + if not hasattr(self, "hd") or not hasattr(self, "sd"): + self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) + if self.newK_flag or self.newV_flag: + self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) + s_in = [torch.zeros(i.shape).cdouble() for i in self.hd] # for i, e in tqdm(enumerate(ee), desc="Compute green functions: "): From 616a2005f1ded6596f9657d98ca452ddab871782 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Jan 2024 15:52:56 +0800 Subject: [PATCH 039/209] add comments --- dptb/negf/device_property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index d35bac02..f081c81d 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -179,7 +179,7 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr if not hasattr(self, "hd") or not hasattr(self, "sd"): self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) - if self.newK_flag or self.newV_flag: + if self.newK_flag or self.newV_flag: # check whether kpoints or Vbias change or not self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) s_in = [torch.zeros(i.shape).cdouble() for i in self.hd] From 3603b7bfe2eaf287ffc8a9ac2a78b5c65b79864d Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 25 Jan 2024 16:44:48 +0800 Subject: [PATCH 040/209] make Hartree2eV unit constant the same as that for dptb-negf --- dptb/postprocess/tbtrans_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/postprocess/tbtrans_init.py b/dptb/postprocess/tbtrans_init.py index 8814b071..60d8f4e3 100644 --- a/dptb/postprocess/tbtrans_init.py +++ b/dptb/postprocess/tbtrans_init.py @@ -605,7 +605,7 @@ def hamiltonian_get(self,allbonds:torch.tensor,hamil_block:torch.tensor,overlap_ unit_constant = 1 elif energy_unit_option=='eV': - unit_constant = 27.2107 + unit_constant = 13.605662285137 * 2 else: raise RuntimeError("energy_unit_option should be 'Hartree' or 'eV'") From ef21a124a9315acdbf86554d3d7436bc92223b5d Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 1 Feb 2024 14:45:48 +0800 Subject: [PATCH 041/209] fix self energy transpose in lead_property.py --- dptb/negf/lead_property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 14b9c958..8b6f35c4 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -126,7 +126,7 @@ def sigmaLR2Gamma(self, se): The Gamma function. ''' - return -1j * (se - se.conj()) + return -1j * (se - se.conj().T) def fermi_dirac(self, x) -> torch.Tensor: return 1 / (1 + torch.exp((x - self.mu)/ self.kBT)) From 947eb57d7d6d0d7e70e6a5110323d0dde091b0ca Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 7 Mar 2024 20:46:09 +0800 Subject: [PATCH 042/209] update NEGF.py --- dptb/postprocess/NEGF.py | 138 +++++++++++++++++++++++++++++++-------- 1 file changed, 109 insertions(+), 29 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 07298c4f..12aba3ba 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -92,7 +92,11 @@ def __init__(self, apiHrk, run_opt, jdata): self.density = Ozaki(R=self.density_options["R"], M_cut=self.density_options["M_cut"], n_gauss=self.density_options["n_gauss"]) else: raise ValueError - + + # number of orbitals on atoms in device region + self.device_atom_norbs = self.negf_hamiltonian.atom_norbs[self.negf_hamiltonian.proj_device_id[0]:self.negf_hamiltonian.proj_device_id[1]] + np.save(self.results_path+"/device_atom_norbs.npy",self.device_atom_norbs) + # geting the output settings self.out_tc = jdata["out_tc"] self.out_dos = jdata["out_dos"] @@ -108,7 +112,8 @@ def __init__(self, apiHrk, run_opt, jdata): ## Poisson equation settings self.poisson_options = j_must_have(jdata, "poisson_options") - self.poisson_zero_charge = {} + self.LDOS_integral = {} + self.free_charge_nanotcad = {} self.gate_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("gate")] self.dielectric_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("dielectric")] @@ -141,6 +146,7 @@ def generate_energy_grid(self): cal_int_grid = True if self.out_dos or self.out_tc or self.out_current_nscf or self.out_ldos: + # Energy relative to Fermi level self.uni_grid = torch.linspace(start=self.jdata["emin"], end=self.jdata["emax"], steps=int((self.jdata["emax"]-self.jdata["emin"])/self.jdata["espacing"])) if cal_pole: @@ -191,48 +197,64 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # create interface interface_poisson = Interface3D(grid,Gate_list,Dielectric_list) + #initial guess for electrostatic potential + log.info(msg="-----Initial guess for electrostatic potential----") + interface_poisson.solve_poisson(method=self.poisson_options['solver'],tolerance=tolerance) + np.save(self.results_path+"/initial_guess_phi.npy",interface_poisson.phi) + atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) + np.save(self.results_path+"/initial_guess_phi_at_atom.npy",interface_poisson.phi[atom_gridpoint_index]) + log.info(msg="-------------------------------------------\n") + max_diff = 1e30; max_diff_list = [] iter_count=0 while max_diff > err: # update Hamiltonian by modifying onsite energy with potential atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) - potential_atom = interface_poisson.phi[atom_gridpoint_index] # a vector with length of number of atoms - # number of orbitals on atoms in device region - device_atom_norbs = self.negf_hamiltonian.atom_norbs[self.negf_hamiltonian.proj_device_id[0]:self.negf_hamiltonian.proj_device_id[1]] - + np.save(self.results_path+"/atom_gridpoint_index.npy",atom_gridpoint_index) + self.potential_at_atom = interface_poisson.phi[atom_gridpoint_index] # a vector with length of number of atoms + potential_list = [] - for i in range(len(device_atom_norbs)): - potential_list.append(potential_atom[i]*torch.ones(device_atom_norbs[i])) - potential_tensor = torch.cat(potential_list) - self.negf_compute(scf_require=True,Vbias=potential_tensor) + for i in range(len(self.device_atom_norbs)): + potential_list.append(self.potential_at_atom[i]*torch.ones(self.device_atom_norbs[i])) + self.potential_tensor = torch.cat(potential_list) + torch.save(self.potential_tensor, self.results_path+"/potential_tensor.pth") + + #TODO: check the sign of potential_tensor: -1 is right or not. + self.negf_compute(scf_require=True,Vbias=self.potential_tensor) # Vbias makes sense for orthogonal basis as in NanoTCAD # TODO: check if Vbias makes sense for non-orthogonal basis # update electron density for solving Poisson equation SCF - DM_eq,DM_neq = self.out["DM_eq"], self.out["DM_neq"] - elec_density = torch.diag(DM_eq+DM_neq) + # DM_eq,DM_neq = self.out["DM_eq"], self.out["DM_neq"] + # elec_density = torch.diag(DM_eq+DM_neq) - elec_density_per_atom = [] - pre_atom_orbs = 0 - for i in range(len(device_atom_norbs)): - elec_density_per_atom.append(torch.sum(elec_density[pre_atom_orbs : pre_atom_orbs+device_atom_norbs[i]]).numpy()) - pre_atom_orbs += device_atom_norbs[i] + # elec_density_per_atom = [] + # pre_atom_orbs = 0 + # for i in range(len(device_atom_norbs)): + # elec_density_per_atom.append(torch.sum(elec_density[pre_atom_orbs : pre_atom_orbs+device_atom_norbs[i]]).numpy()) + # pre_atom_orbs += device_atom_norbs[i] # TODO: check the sign of free_charge # TODO: check the spin degenracy # TODO: add k summation operation interface_poisson.free_charge[atom_gridpoint_index] =\ - -1*np.array(elec_density_per_atom)+self.poisson_zero_charge[str(self.kpoints[0])].numpy() - max_diff = interface_poisson.solve_poisson(method=self.poisson_options['solver'],tolerance=tolerance) + np.real(self.free_charge_nanotcad[str(self.kpoints[0])].numpy()) + - interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi - interface_poisson.phi_old) interface_poisson.phi_old = interface_poisson.phi.copy() + max_diff = interface_poisson.solve_poisson(method=self.poisson_options['solver'],tolerance=tolerance) + + interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi_old-interface_poisson.phi) + + iter_count += 1 print('Poisson iteration: ',iter_count,' max_diff: ',max_diff) max_diff_list.append(max_diff) + + if iter_count > max_iter: log.info(msg="Warning! Poisson iteration exceeds max_iter {}".format(int(max_iter))) break @@ -247,7 +269,7 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): torch.save(self.poisson_out, self.results_path+"/poisson.out.pth") # calculate transport properties with converged potential - self.negf_compute(scf_require=False,Vbias=potential_tensor) + self.negf_compute(scf_require=False,Vbias=self.potential_tensor) def negf_compute(self,scf_require=False,Vbias=None): @@ -262,32 +284,52 @@ def negf_compute(self,scf_require=False,Vbias=None): # computing output properties for ik, k in enumerate(self.kpoints): self.out = {} + self.out["lead_L_se"] = {} + self.out["lead_R_se"] = {} + self.out["gtrans"] = {} + self.out['uni_grid'] = self.uni_grid log.info(msg="Properties computation at k = [{:.4f},{:.4f},{:.4f}]".format(float(k[0]),float(k[1]),float(k[2]))) # computing properties that is functions of E if hasattr(self, "uni_grid"): self.out["k"] = k dE = abs(self.uni_grid[1] - self.uni_grid[0]) - self.poisson_zero_charge.update({str(k):0}) # reset zero charge for each k - for e in self.uni_grid: - log.info(msg="computing green's function at e = {:.3f}".format(float(e))) + self.free_charge_nanotcad.update({str(k):torch.zeros_like(torch.tensor(self.device_atom_norbs),dtype=torch.complex128)}) + + output_freq = int(len(self.uni_grid)/10) + for ie, e in enumerate(self.uni_grid): + + if ie % output_freq == 0: + log.info(msg="computing green's function at e = {:.3f}".format(float(e))) leads = self.stru_options.keys() for ll in leads: if ll.startswith("lead"): + # TODO: temporarily set the voltage to -1*potential_tensor[0] and -1*potential_tensor[-1] + if Vbias is not None: + if ll == 'lead_L' : + getattr(self.deviceprop, ll).voltage = Vbias[0] + else: + getattr(self.deviceprop, ll).voltage = Vbias[-1] + + getattr(self.deviceprop, ll).self_energy( energy=e, kpoint=k, eta_lead=self.jdata["eta_lead"], method=self.jdata["sgf_solver"] ) + self.out[str(ll)+"_se"][str(e.numpy())] = getattr(self.deviceprop, ll).se - self.deviceprop.cal_green_function( + gtrans = self.deviceprop.cal_green_function( energy=e, kpoint=k, eta_device=self.jdata["eta_device"], block_tridiagonal=self.block_tridiagonal, Vbias=Vbias ) + + self.out["gtrans"][str(e.numpy())] = gtrans + if scf_require==False: if self.out_dos: prop = self.out.setdefault("DOS", []) @@ -299,8 +341,9 @@ def negf_compute(self,scf_require=False,Vbias=None): prop = self.out.setdefault("LDOS", []) prop.append(self.compute_LDOS(k)) else: - if e < 0: # 0 is the fermi level read from jdata - self.poisson_zero_charge[str(k)] += self.compute_LDOS(k)*dE + + self.get_density_nanotcad(e, k, dE) + # whether scf_require is True or False, density are computed for Poisson-NEGF SCF if self.out_density or self.out_potential: @@ -364,7 +407,8 @@ def get_grid(self,grid_info,structase): device_atom_coords = structase.get_positions() xa,ya,za = device_atom_coords[:,0],device_atom_coords[:,1],device_atom_coords[:,2] - grid = Grid(xg,yg,zg,xa,ya,za) + # grid = Grid(xg,yg,zg,xa,ya,za) + grid = Grid(xg,yg,za,xa,ya,za) #TODO: change back to zg return grid def fermi_dirac(self, x) -> torch.Tensor: @@ -405,4 +449,40 @@ def compute_lcurrent(self, kpoint): def SCF(self): - pass \ No newline at end of file + pass + + + def get_density_nanotcad(self,e,kpoint,dE,eta_lead=1e-5, eta_device=0.,Vbias=None): + + tx, ty = self.deviceprop.g_trans.shape + lx, ly = self.deviceprop.lead_L.se.shape + rx, ry = self.deviceprop.lead_R.se.shape + x0 = min(lx, tx) + x1 = min(rx, ty) + + gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) + gammaL[:x0, :x0] += self.deviceprop.lead_L.gamma[:x0, :x0] + gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) + gammaR[-x1:, -x1:] += self.deviceprop.lead_R.gamma[-x1:, -x1:] + + A_L = torch.mm(torch.mm(self.deviceprop.g_trans,gammaL),self.deviceprop.g_trans.conj().T) + A_R = torch.mm(torch.mm(self.deviceprop.g_trans,gammaR),self.deviceprop.g_trans.conj().T) + + # Vbias = -1 * potential_tensor + for Ei_index, Ei_at_atom in enumerate(-1*self.potential_at_atom): + pre_orbs = sum(self.device_atom_norbs[:Ei_index]) + + # electron density + if e >= Ei_at_atom: + for j in range(self.device_atom_norbs[Ei_index]): + self.free_charge_nanotcad[str(kpoint)][Ei_index] +=\ + 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*self.deviceprop.lead_L.fermi_dirac(e+self.deviceprop.lead_L.efermi) \ + +A_R[pre_orbs+j,pre_orbs+j]*self.deviceprop.lead_R.fermi_dirac(e+self.deviceprop.lead_R.efermi))*dE + # 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*self.deviceprop.lead_L.fermi_dirac(e+self.deviceprop.lead_L.mu) \ + # +A_R[pre_orbs+j,pre_orbs+j]*self.deviceprop.lead_R.fermi_dirac(e+self.deviceprop.lead_R.mu))*dE + # hole density + else: + for j in range(self.device_atom_norbs[Ei_index]): + self.free_charge_nanotcad[str(kpoint)][Ei_index] +=\ + 2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-self.deviceprop.lead_L.fermi_dirac(e+self.deviceprop.lead_L.efermi)) \ + +A_R[pre_orbs+j,pre_orbs+j]*(1-self.deviceprop.lead_R.fermi_dirac(e+self.deviceprop.lead_R.efermi)))*dE \ No newline at end of file From 2aa9081b798aa402a21481938f48ca4d05817493 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 7 Mar 2024 20:48:11 +0800 Subject: [PATCH 043/209] update density.py --- dptb/negf/density.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index dd263dfb..2081ce51 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -189,7 +189,7 @@ def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.,Vbias=None) for i, e in enumerate(xs): deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) - deviceprop.cal_green_function(e=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device) + deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device) gr_gamma_ga = torch.mm(torch.mm(deviceprop.grd[0], deviceprop.lead_R.gamma), deviceprop.grd[0].conj().T).real gr_gamma_ga = gr_gamma_ga * (deviceprop.lead_R.fermi_dirac(e+deviceprop.mu) - deviceprop.lead_L.fermi_dirac(e+deviceprop.mu)) DM_neq = DM_neq + wlg[i] * gr_gamma_ga From 8f4790c177db6436029dec1b7b144805e1013f88 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 7 Mar 2024 20:57:52 +0800 Subject: [PATCH 044/209] update device_property.py --- dptb/negf/device_property.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index f081c81d..e722266a 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -106,7 +106,8 @@ def set_leadLR(self, lead_L, lead_R): ''' self.lead_L = lead_L self.lead_R = lead_R - self.mu = self.efermi - 0.5*(self.lead_L.voltage + self.lead_R.voltage) + # self.mu = self.efermi - 0.5*(self.lead_L.voltage + self.lead_R.voltage) # temporarily for NanoTCAD + def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=True, Vbias=None): ''' computes the Green's function for a given energy and k-point in device. @@ -166,7 +167,7 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr assert torch.is_tensor(self.V) if not self.oldV is None: - if abs(self.V - self.oldV).sum() > 1e-5: + if torch.abs(self.V - self.oldV).sum() > 1e-5: self.newV_flag = True else: self.newV_flag = False @@ -179,7 +180,7 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr if not hasattr(self, "hd") or not hasattr(self, "sd"): self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) - if self.newK_flag or self.newV_flag: # check whether kpoints or Vbias change or not + elif self.newK_flag or self.newV_flag: # check whether kpoints or Vbias change or not self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) s_in = [torch.zeros(i.shape).cdouble() for i in self.hd] @@ -351,7 +352,8 @@ def _cal_ldos_(self): accmap = np.cumsum(norbs) ldos = torch.stack([ldos[accmap[i]:accmap[i+1]].sum() for i in range(len(accmap)-1)]) - return ldos*2 + # return ldos*2 + return ldos #temporarily return the ldos without spin degeneracy def _cal_local_current_(self): '''calculate the local current between different atoms From 363d8d2d27073e746981b30902a6b58968ed5e9c Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 7 Mar 2024 21:02:36 +0800 Subject: [PATCH 045/209] update lead_property.py --- dptb/negf/lead_property.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 8b6f35c4..0580975b 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -92,10 +92,11 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S assert len(np.array(kpoint).reshape(-1)) == 3 # according to given kpoint and e_mesh, calculating or loading the self energy and surface green function to self. if not isinstance(energy, torch.Tensor): - energy = torch.tensor(energy) + energy = torch.tensor(energy) # energy relative to Ef - if not hasattr(self, "HL"): - self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) + # if not hasattr(self, "HL"): + #TODO: check here whether it is necessary to calculate the self energy every time + self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) self.se, _ = selfEnergy( ee=energy, @@ -105,7 +106,7 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S sLL=self.SLL, hDL=self.HDL, sDL=self.SDL, - chemiPot=self.mu, + chemiPot=self.efermi, # temmporarily change to self.efermi for the case in which applying lead bias to corresponding to Nanotcad etaLead=eta_lead, method=method ) @@ -126,7 +127,7 @@ def sigmaLR2Gamma(self, se): The Gamma function. ''' - return -1j * (se - se.conj().T) + return 1j * (se - se.conj().T) def fermi_dirac(self, x) -> torch.Tensor: return 1 / (1 + torch.exp((x - self.mu)/ self.kBT)) From 27e1ebbfb1e85290284758e71aac6cd332a38fd1 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 7 Mar 2024 21:34:27 +0800 Subject: [PATCH 046/209] add notes in lead_property.py --- dptb/negf/lead_property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 0580975b..877b3fd2 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -105,7 +105,7 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S sL=self.SL, sLL=self.SLL, hDL=self.HDL, - sDL=self.SDL, + sDL=self.SDL, #TODO: check chemiPot settiing is correct or not chemiPot=self.efermi, # temmporarily change to self.efermi for the case in which applying lead bias to corresponding to Nanotcad etaLead=eta_lead, method=method From 567a57a6c157d240560eeb9191226c3b1e24bcf4 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 7 Mar 2024 21:51:58 +0800 Subject: [PATCH 047/209] update negf_hamiltonian_init.py --- dptb/negf/negf_hamiltonian_init.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index ee0b31e8..699db8b1 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -6,7 +6,7 @@ from dptb.negf.negf_utils import quad, gauss_xw,update_kmap,leggauss from dptb.negf.ozaki_res_cal import ozaki_residues from dptb.negf.areshkin_pole_sum import pole_maker -from ase.io import read +from ase.io import read,write from dptb.negf.poisson import Density2Potential, getImg from dptb.negf.scf_method import SCFMethod import logging @@ -106,7 +106,10 @@ def initialize(self, kpoints, block_tridiagnal=False): projatoms = self.apiH.structure.projatoms self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] - self.apiH.get_HR() + allbonds,hamil_block,_ =self.apiH.get_HR() + torch.save(allbonds, os.path.join(self.results_path, "allbonds"+".pth")) + torch.save(hamil_block, os.path.join(self.results_path, "hamil_block"+".pth")) + H, S = self.apiH.get_HK(kpoints=kpoints) d_start = int(np.sum(self.atom_norbs[:proj_device_id[0]])) d_end = int(np.sum(self.atom_norbs)-np.sum(self.atom_norbs[proj_device_id[1]:])) @@ -126,6 +129,7 @@ def initialize(self, kpoints, block_tridiagnal=False): if kk.startswith("lead"): HS_leads = {} stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] + write(os.path.join(self.results_path, "stru_"+kk+".vasp"), stru_lead) self.apiH.update_struct(stru_lead, mode="lead", stru_options=self.stru_options.get(kk), pbc=self.stru_options["pbc"]) # update lead id n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]][projatoms[:self.lead_ids[kk][0]]].sum() @@ -136,7 +140,7 @@ def initialize(self, kpoints, block_tridiagnal=False): l_start = int(np.sum(self.atom_norbs[:proj_lead_id[0]])) l_end = int(l_start + np.sum(self.atom_norbs[proj_lead_id[0]:proj_lead_id[1]]) / 2) - HL, SL = H[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian + HL, SL = H[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian in one principal layer HDL, SDL = H[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] # device and lead's hopping HS_leads.update({ "HL":HL.cdouble()*self.h_factor, @@ -147,7 +151,10 @@ def initialize(self, kpoints, block_tridiagnal=False): structure_leads[kk] = self.apiH.structure.struct - self.apiH.get_HR() + allbonds_lead,hamil_block_lead,_ = self.apiH.get_HR() + torch.save(allbonds_lead, os.path.join(self.results_path, "allbonds_"+kk+".pth")) + torch.save(hamil_block_lead, os.path.join(self.results_path, "hamil_block_"+kk+".pth")) + h, s = self.apiH.get_HK(kpoints=kpoints) nL = int(h.shape[1] / 2) HLL, SLL = h[:, :nL, nL:], s[:, :nL, nL:] # H_{L_first2L_second} @@ -205,6 +212,9 @@ def get_hs_device(self, kpoint, V, block_tridiagonal=False): if block_tridiagonal: return hd, sd, hl, su, sl, hu else: + print('HD shape:', HD.shape) + print('SD shape:', SD.shape) + print('V shape:', V.shape) return [HD - V*SD], [SD], [], [], [], [] def get_hs_lead(self, kpoint, tab, v): @@ -239,7 +249,7 @@ def get_hs_lead(self, kpoint, tab, v): f["SL"][ix], f["SLL"][ix], f["SDL"][ix] - return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL + return hL-v*sL, hLL+v*sLL, hDL, sL, sLL, sDL # TODO: check hLL+v*sLL is correct or not def attach_potential(): pass From 3cd8a8bd8dd75efb0d290cbd0d236e601338d6bc Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 7 Mar 2024 22:10:27 +0800 Subject: [PATCH 048/209] update a series of py files in negf, including poisson_init.py --- dptb/negf/poisson_init.py | 172 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 163 insertions(+), 9 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 55fa93c7..e4e36cf0 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -1,10 +1,11 @@ import numpy as np import pyamg #TODO: later add it to optional dependencies,like sisl from pyamg.gallery import poisson -from dptb.utils.constants import elementary_charge as q +from dptb.utils.constants import elementary_charge from dptb.utils.constants import Boltzmann, eV2J from scipy.constants import epsilon_0 as eps0 #TODO:later add to untils.constants.py from scipy.sparse import csr_matrix +from scipy.sparse.linalg import spsolve #eps0 = 8.854187817e-12 # in the unit of F/m # As length in deeptb is in the unit of Angstrom, the unit of eps0 is F/Angstrom eps0 = eps0*1e-10 # in the unit of F/Angstrom @@ -14,12 +15,15 @@ class Grid(object): # define the grid in 3D space def __init__(self,xg,yg,zg,xa,ya,za): # xg,yg,zg are the coordinates of the basic grid points - self.xg = np.around(xg,decimals=5);self.yg = np.around(yg,decimals=5);self.zg = np.around(zg,decimals=5) + # self.xg = np.around(xg,decimals=5);self.yg = np.around(yg,decimals=5);self.zg = np.around(zg,decimals=5) + self.xg = xg + self.yg = yg + self.zg = zg # xa,ya,za are the coordinates of the atoms # atom should be within the grid - assert (xa-np.min(xg)).all() and (xa-np.max(xg)).all() - assert (ya-np.min(yg)).all() and (ya-np.max(yg)).all() - assert (za-np.min(zg)).all() and (za-np.max(zg)).all() + assert np.min(xa) >= np.min(xg) and np.max(xa) <= np.max(xg) + assert np.min(ya) >= np.min(yg) and np.max(ya) <= np.max(yg) + assert np.min(za) >= np.min(zg) and np.max(za) <= np.max(zg) self.Na = len(xa) # number of atoms uxa = np.unique(xa);uya = np.unique(ya);uza = np.unique(za) @@ -29,6 +33,7 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.zall = np.unique(np.concatenate((uza,self.zg),0)) self.shape = (len(self.xall),len(self.yall),len(self.zall)) + print('unique len of zall:',len(np.unique(self.zall))) # create meshgrid xmesh,ymesh,zmesh = np.meshgrid(self.xall,self.yall,self.zall) @@ -199,6 +204,19 @@ def to_pyamg(self,dtype=None): return A,b + + def to_scipy(self,dtype=None): + # convert to amg format A,b matrix + if dtype == None: + dtype = np.float64 + # A = poisson(self.grid.shape,format='csr',dtype=dtype) + Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) + B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) + self.NR_construct_Jacobian(Jacobian) + self.NR_construct_B(B) + # self.construct_poisson(A,b) + return Jacobian,B + def construct_poisson(self,A,b): # construct the Poisson equation by adding boundary conditions and free charge to the matrix A and vector b Nx = self.grid.shape[0];Ny = self.grid.shape[1];Nz = self.grid.shape[2] @@ -242,9 +260,17 @@ def construct_poisson(self,A,b): A[gp_index,gp_index-Nx*Ny] = flux_zm A[gp_index,gp_index+Nx*Ny] = flux_zp - b[gp_index] = -q*self.free_charge[gp_index]\ - *np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT)\ - -q*self.fixed_charge[gp_index] + # b[gp_index] = -elementary_charge*self.free_charge[gp_index]\ + # *np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT)\ + # -elementary_charge*self.fixed_charge[gp_index] + + # TODO: remove damping factor temporarily + # b[gp_index] = -elementary_charge*self.free_charge[gp_index]\ + # *np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_initial[gp_index])/self.kBT)\ + # -elementary_charge*self.fixed_charge[gp_index] + b[gp_index] = -elementary_charge*self.free_charge[gp_index] -elementary_charge*self.fixed_charge[gp_index] + + # free charge and fixed charge are number of electrons in real-space grid points # the above free_charge form accelerate the convergence of the Poisson equation # only internal points have non-zero free_charge and fixed_charge @@ -304,7 +330,38 @@ def solve_poisson(self,method='pyamg',tolerance=1e-7): if method == 'pyamg': A,b = self.to_pyamg() self.phi = self.solve_poisson_pyamg(A,b,tolerance) - + max_diff = np.max(abs(self.phi-self.phi_old)) + return max_diff + elif method == 'scipy': + + print('Solve Poisson equation by scipy') + # NR iteration + self.phi_initial = self.phi.copy() # tilde_phi in paper + norm_avp = 1.0; NR_circle_count = 0 + while norm_avp > 1e-3 and NR_circle_count < 100: + Jacobian,B = self.to_scipy() + norm_B = np.linalg.norm(B) + + delta_phi = spsolve(Jacobian,B) + self.phi_oldstep = self.phi.copy() + + max_diff_NR = np.max(abs(delta_phi)) + print('max_diff_NR: ',max_diff_NR) + norm_avp = np.linalg.norm(delta_phi) + self.phi += delta_phi + if norm_avp > 1e-3: + _,B = self.to_scipy() + norm_B_new = np.linalg.norm(B) + print('norm_B_new: ',norm_B_new) + control_count = 1 + while norm_B_new > norm_B and control_count < 2: + self.phi -= delta_phi/np.power(2,control_count) + _,B = self.to_scipy() + norm_B_new = np.linalg.norm(B) + control_count += 1 + print('control_count: ',control_count,' norm_B_new: ',norm_B_new) + NR_circle_count += 1 + print('NR circle: ',NR_circle_count,' norm_avp: ', norm_avp) max_diff = np.max(abs(self.phi-self.phi_old)) return max_diff else: @@ -312,6 +369,103 @@ def solve_poisson(self,method='pyamg',tolerance=1e-7): + def NR_construct_Jacobian(self,J): + # construct the Jacobian matrix for the Poisson equation + + Nx = self.grid.shape[0];Ny = self.grid.shape[1];Nz = self.grid.shape[2] + for gp_index in range(self.grid.Np): + if self.boudnary_points[gp_index] == "in": + flux_xm = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index-1]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index,0]-self.grid.grid_coord[gp_index-1,0]) + flux_xp = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index+1]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index+1,0]-self.grid.grid_coord[gp_index,0]) + + flux_ym = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index-Nx]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index-Nx,1]-self.grid.grid_coord[gp_index,1]) + flux_yp = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index+Nx]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index+Nx,1]-self.grid.grid_coord[gp_index,1]) + + flux_zm = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index-Nx*Ny]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index-Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) + flux_zp = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index+Nx*Ny]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index+Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) + + # add flux term to matrix A + J[gp_index,gp_index] = -(flux_xm+flux_xp+flux_ym+flux_yp+flux_zm+flux_zp)\ + +elementary_charge*self.free_charge[gp_index]*(-np.sign(self.free_charge[gp_index]))/self.kBT*np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT) + J[gp_index,gp_index-1] = flux_xm + J[gp_index,gp_index+1] = flux_xp + J[gp_index,gp_index-Nx] = flux_ym + J[gp_index,gp_index+Nx] = flux_yp + J[gp_index,gp_index-Nx*Ny] = flux_zm + J[gp_index,gp_index+Nx*Ny] = flux_zp + + else:# boundary points + J[gp_index,gp_index] = elementary_charge + + if self.boudnary_points[gp_index] == "xmin": + J[gp_index,gp_index+1] = -1.0*elementary_charge + elif self.boudnary_points[gp_index] == "xmax": + J[gp_index,gp_index-1] = -1.0*elementary_charge + elif self.boudnary_points[gp_index] == "ymin": + J[gp_index,gp_index+Nx] = -1.0*elementary_charge + elif self.boudnary_points[gp_index] == "ymax": + J[gp_index,gp_index-Nx] = -1.0*elementary_charge + elif self.boudnary_points[gp_index] == "zmin": + J[gp_index,gp_index+Nx*Ny] = -1.0*elementary_charge + elif self.boudnary_points[gp_index] == "zmax": + J[gp_index,gp_index-Nx*Ny] = -1.0*elementary_charge + elif self.boudnary_points[gp_index] == "Gate": + J[gp_index,gp_index] = elementary_charge + + def NR_construct_B(self,B): + # construct the -B matrix in NR iteration + # Note that the sign of B has been changed for convenience in later NR iteration + # return -B + Nx = self.grid.shape[0];Ny = self.grid.shape[1];Nz = self.grid.shape[2] + for gp_index in range(self.grid.Np): + if self.boudnary_points[gp_index] == "in": + flux_xm = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index-1]+self.eps[gp_index])*0.5\ + *(self.phi[gp_index-1]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index,0]-self.grid.grid_coord[gp_index-1,0]) + flux_xp = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index+1]+self.eps[gp_index])*0.5\ + *(self.phi[gp_index+1]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+1,0]-self.grid.grid_coord[gp_index,0]) + + flux_ym = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index-Nx]+self.eps[gp_index])*0.5\ + *(self.phi[gp_index-Nx]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index-Nx,1]-self.grid.grid_coord[gp_index,1]) + flux_yp = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index+Nx]+self.eps[gp_index])*0.5\ + *(self.phi[gp_index+Nx]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+Nx,1]-self.grid.grid_coord[gp_index,1]) + + flux_zm = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index-Nx*Ny]+self.eps[gp_index])*0.5\ + *(self.phi[gp_index-Nx*Ny]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index-Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) + flux_zp = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index+Nx*Ny]+self.eps[gp_index])*0.5\ + *(self.phi[gp_index+Nx*Ny]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) + + # add flux term to matrix B + B[gp_index] = (flux_xm+flux_xp+flux_ym+flux_yp+flux_zm+flux_zp) + B[gp_index] += elementary_charge*self.free_charge[gp_index]*np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT)\ + +elementary_charge*self.fixed_charge[gp_index] + + + else:# boundary points + + if self.boudnary_points[gp_index] == "xmin": + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+1])*elementary_charge + elif self.boudnary_points[gp_index] == "xmax": + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-1])*elementary_charge + elif self.boudnary_points[gp_index] == "ymin": + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+Nx])*elementary_charge + elif self.boudnary_points[gp_index] == "ymax": + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx])*elementary_charge + elif self.boudnary_points[gp_index] == "zmin": + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+Nx*Ny])*elementary_charge + elif self.boudnary_points[gp_index] == "zmax": + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx*Ny])*elementary_charge + elif self.boudnary_points[gp_index] == "Gate": + B[gp_index] = (self.phi[gp_index]+self.lead_gate_potential[gp_index])*elementary_charge + #TODO: add lead potential. For dptb-negf, we only need to change zmin and zmax as lead + + if B[gp_index]!=0: # for convenience change the sign of B in later NR iteration + B[gp_index] = -B[gp_index] From 98197fd7d9cb8dca8352c3f10aa7f7509fb20acf Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 7 Mar 2024 22:13:43 +0800 Subject: [PATCH 049/209] update recursive_green_cal.py --- dptb/negf/recursive_green_cal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dptb/negf/recursive_green_cal.py b/dptb/negf/recursive_green_cal.py index bba73933..39a3d45c 100644 --- a/dptb/negf/recursive_green_cal.py +++ b/dptb/negf/recursive_green_cal.py @@ -249,6 +249,7 @@ def recursive_gf(energy, hl, hd, hu, sd, su, sl, left_se, right_se, seP=None, ch """ shift_energy = energy + chemiPot + shift_energy = torch.scalar_tensor(shift_energy, dtype=torch.complex128) temp_mat_d_list = [hd[i] * 1. for i in range(len(hd))] temp_mat_l_list = [hl[i] * 1. for i in range(len(hl))] From aab802e3e881d3b88386f0e5ca68387a14236391 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 7 Mar 2024 22:16:03 +0800 Subject: [PATCH 050/209] update surface_green.py --- dptb/negf/surface_green.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dptb/negf/surface_green.py b/dptb/negf/surface_green.py index 6f560f18..ecd1d826 100644 --- a/dptb/negf/surface_green.py +++ b/dptb/negf/surface_green.py @@ -24,7 +24,8 @@ class SurfaceGreen(torch.autograd.Function): def forward(ctx, H, h01, S, s01, ee, method='Lopez-Sancho'): # ''' # gs = [A_l - A_{l,l-1} gs A_{l-1,l}]^{-1} - # + # H : HL + # h01 : HLL # 1. ee can be a list, to handle a batch of samples # ''' From fc9090dded9c1d1b1f507a8f2c6510a57012f678 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 7 Mar 2024 22:34:03 +0800 Subject: [PATCH 051/209] update argcheck.py --- dptb/utils/argcheck.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 76de1e86..1362a74d 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -602,9 +602,11 @@ def poisson_options(): doc_solver = "" doc_fmm = "" doc_pyamg= "" + doc_scipy= "" return Variant("solver", [ Argument("fmm", dict, fmm(), doc=doc_fmm), - Argument("pyamg", dict, pyamg(), doc=doc_pyamg) + Argument("pyamg", dict, pyamg(), doc=doc_pyamg), + Argument("scipy", dict, scipy(), doc=doc_scipy) ], optional=True, default_tag="fmm", doc=doc_solver) def density_options(): @@ -650,6 +652,26 @@ def pyamg(): Argument("dielectric_region", dict, optional=False, sub_fields=dielectric(), doc=doc_dielectric) ] + +def scipy(): + doc_err = "" + doc_tolerance="" + doc_grid="" + doc_gate="" + doc_dielectric="" + doc_max_iter="" + doc_mix_rate="" + return [ + Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err), + Argument("tolerance", [int, float], optional=True, default=1e-7, doc=doc_tolerance), + Argument("max_iter", int, optional=True, default=100, doc=doc_max_iter), + Argument("mix_rate", int, optional=True, default=0.25, doc=doc_mix_rate), + Argument("grid", dict, optional=False, sub_fields=grid(), doc=doc_grid), + Argument("gate_top", dict, optional=False, sub_fields=gate(), doc=doc_gate), + Argument("gate_bottom", dict, optional=False, sub_fields=gate(), doc=doc_gate), + Argument("dielectric_region", dict, optional=False, sub_fields=dielectric(), doc=doc_dielectric) + ] + def grid(): doc_xrange="" doc_yrange="" From 67da37b040c886daa74eba8619064d4aa27a90d7 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 11 Mar 2024 11:07:40 +0800 Subject: [PATCH 052/209] add abs to formula powerlaw --- dptb/nnsktb/formula.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dptb/nnsktb/formula.py b/dptb/nnsktb/formula.py index f49f6fb0..dccd8165 100644 --- a/dptb/nnsktb/formula.py +++ b/dptb/nnsktb/formula.py @@ -120,8 +120,8 @@ def powerlaw(self, rij, paraArray, iatomtype, jatomtype, rcut:th.float32 = th.te paraArray = paraArray.view(-1, self.num_paras) #alpha1, alpha2, alpha3, alpha4 = paraArray[:, 0], paraArray[:, 1]**2, paraArray[:, 2]**2, paraArray[:, 3]**2 - alpha1, alpha2 = paraArray[:, 0], paraArray[:, 1].abs() - + # alpha1, alpha2 = paraArray[:, 0], paraArray[:, 1].abs() + alpha1, alpha2 = paraArray[:, 0], paraArray[:, 1] # r0 = map(lambda x:(bond_length[iatomtype[x]]+bond_length[jatomtype[x]])/(2*1.8897259886), range(len(iatomtype))) # r0 = th.tensor(list(r0)) r0 = (bond_length[iatomtype]+bond_length[jatomtype])/(2*1.8897259886) From 921d1161c56c07d6814d155a29fd37a9f0f13d1f Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 11 Mar 2024 11:08:03 +0800 Subject: [PATCH 053/209] simplify real-space gird setting --- dptb/negf/poisson_init.py | 25 +++++++++++-------------- dptb/postprocess/NEGF.py | 31 +++++++++++++++++++------------ 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index e4e36cf0..75e8bb0b 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -98,25 +98,22 @@ def cal_vorlen(self,x): class Gate(object): - def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): + def __init__(self,xrange,yrange,zrange): + # Fermi_level of gate (in unit eV) self.Ef = 0.0 - # gate region - self.xmin = xmin; self.xmax = xmax - self.ymin = ymin; self.ymax = ymax - self.zmin = zmin; self.zmax = zmax + # Gate region + self.xmin,self.xmax = float(xrange[0]),float(xrange[1]) + self.ymin,self.ymax = float(yrange[0]),float(yrange[1]) + self.zmin,self.zmax = float(zrange[0]),float(zrange[1]) class Dielectric(object): - def __init__(self,xmin,xmax,ymin,ymax,zmin,zmax): + def __init__(self,xrange,yrange,zrange): + # dielectric permittivity self.eps = 1.0 # gate region - self.xmin = xmin; self.xmax = xmax - self.ymin = ymin; self.ymax = ymax - self.zmin = zmin; self.zmax = zmax - - - - - + self.xmin,self.xmax = float(xrange[0]),float(xrange[1]) + self.ymin,self.ymax = float(yrange[0]),float(yrange[1]) + self.zmin,self.zmax = float(zrange[0]),float(zrange[1]) class Interface3D(object): diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 12aba3ba..756ac05c 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -112,7 +112,7 @@ def __init__(self, apiHrk, run_opt, jdata): ## Poisson equation settings self.poisson_options = j_must_have(jdata, "poisson_options") - self.LDOS_integral = {} + # self.LDOS_integral = {} # for electron density integral self.free_charge_nanotcad = {} self.gate_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("gate")] self.dielectric_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("dielectric")] @@ -146,7 +146,7 @@ def generate_energy_grid(self): cal_int_grid = True if self.out_dos or self.out_tc or self.out_current_nscf or self.out_ldos: - # Energy relative to Fermi level + # Energy gird is set relative to Fermi level self.uni_grid = torch.linspace(start=self.jdata["emin"], end=self.jdata["emax"], steps=int((self.jdata["emax"]-self.jdata["emin"])/self.jdata["espacing"])) if cal_pole: @@ -162,7 +162,8 @@ def compute(self): if self.scf: if not self.out_density: - raise RuntimeError("Error! scf calculation requires density matrix. Please set out_density to True") + self.out_density = True + raise UserWarning("SCF is required, but out_density is set to False. Automatically Setting out_density to True.") self.poisson_negf_scf(err=self.poisson_options['err'],tolerance=self.poisson_options['tolerance'],\ max_iter=self.poisson_options['max_iter'],mix_rate=self.poisson_options['mix_rate']) else: @@ -171,26 +172,32 @@ def compute(self): def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): - # create grid + # create real-space grid grid = self.get_grid(self.poisson_options["grid"],self.deviceprop.structure) # create gate Gate_list = [] for gg in range(len(self.gate_region)): - xmin,xmax = self.gate_region[gg].get("x_range",None).split(':') - ymin,ymax = self.gate_region[gg].get("y_range",None).split(':') - zmin,zmax = self.gate_region[gg].get("z_range",None).split(':') - gate_init = Gate(float(xmin),float(xmax),float(ymin),float(ymax),float(zmin),float(zmax)) + # xmin,xmax = self.gate_region[gg].get("x_range",None).split(':') + # ymin,ymax = self.gate_region[gg].get("y_range",None).split(':') + # zmin,zmax = self.gate_region[gg].get("z_range",None).split(':') + # gate_init = Gate(float(xmin),float(xmax),float(ymin),float(ymax),float(zmin),float(zmax)) + gate_init = Gate(self.gate_region[gg].get("x_range",None).split(':'),\ + self.gate_region[gg].get("y_range",None).split(':'),\ + self.gate_region[gg].get("z_range",None).split(':')) gate_init.Ef = float(self.gate_region[gg].get("voltage",None)) # in unit of volt Gate_list.append(gate_init) # create dielectric Dielectric_list = [] for dd in range(len(self.dielectric_region)): - xmin,xmax = self.dielectric_region[dd].get("x_range",None).split(':') - ymin,ymax = self.dielectric_region[dd].get("y_range",None).split(':') - zmin,zmax = self.dielectric_region[dd].get("z_range",None).split(':') - dielectric_init = Dielectric(float(xmin),float(xmax),float(ymin),float(ymax),float(zmin),float(zmax)) + # xmin,xmax = self.dielectric_region[dd].get("x_range",None).split(':') + # ymin,ymax = self.dielectric_region[dd].get("y_range",None).split(':') + # zmin,zmax = self.dielectric_region[dd].get("z_range",None).split(':') + + dielectric_init = Gate(self.dielectric_region[dd].get("x_range",None).split(':'),\ + self.dielectric_region[dd].get("y_range",None).split(':'),\ + self.dielectric_region[dd].get("z_range",None).split(':')) dielectric_init.eps = float(self.dielectric_region[dd].get("relative permittivity",None)) Dielectric_list.append(dielectric_init) From a74f3a21c07a72091c19e2f9e23ab5a7f339ae81 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 11 Mar 2024 11:26:16 +0800 Subject: [PATCH 054/209] simplify boundary_points_get in poisson_init.py --- dptb/negf/poisson_init.py | 76 +++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 75e8bb0b..546c1fb8 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -96,24 +96,32 @@ def cal_vorlen(self,x): return xd +class region(object): + def __init__(self,x_range,y_range,z_range): + self.xmin,self.xmax = float(x_range[0]),float(x_range[1]) + self.ymin,self.ymax = float(y_range[0]),float(y_range[1]) + self.zmin,self.zmax = float(z_range[0]),float(z_range[1]) -class Gate(object): - def __init__(self,xrange,yrange,zrange): - # Fermi_level of gate (in unit eV) - self.Ef = 0.0 +class Gate(region): + def __init__(self,x_range,y_range,z_range): # Gate region - self.xmin,self.xmax = float(xrange[0]),float(xrange[1]) - self.ymin,self.ymax = float(yrange[0]),float(yrange[1]) - self.zmin,self.zmax = float(zrange[0]),float(zrange[1]) - -class Dielectric(object): - def __init__(self,xrange,yrange,zrange): + super().__init__(x_range,y_range,z_range) + # Fermi_level of gate (in unit eV) + self.Ef = 0.0 + # self.xmin,self.xmax = float(xrange[0]),float(xrange[1]) + # self.ymin,self.ymax = float(yrange[0]),float(yrange[1]) + # self.zmin,self.zmax = float(zrange[0]),float(zrange[1]) + +class Dielectric(region): + def __init__(self,x_range,y_range,z_range): + # dielectric region + super().__init__(x_range,y_range,z_range) # dielectric permittivity self.eps = 1.0 - # gate region - self.xmin,self.xmax = float(xrange[0]),float(xrange[1]) - self.ymin,self.ymax = float(yrange[0]),float(yrange[1]) - self.zmin,self.zmax = float(zrange[0]),float(zrange[1]) + # # gate region + # self.xmin,self.xmax = float(xrange[0]),float(xrange[1]) + # self.ymin,self.ymax = float(yrange[0]),float(yrange[1]) + # self.zmin,self.zmax = float(zrange[0]),float(zrange[1]) class Interface3D(object): @@ -130,42 +138,40 @@ def __init__(self,grid,gate_list,dielectric_list): self.grid = grid self.eps = np.ones(grid.Np) # dielectric permittivity - self.phi = np.zeros(grid.Np) # potential - self.phi_old = np.zeros(grid.Np) # potential in the previous iteration - self.free_charge = np.zeros(grid.Np) # free charge density - self.fixed_charge = np.zeros(grid.Np) # fixed charge density + self.phi,self.phi_old = np.zeros(grid.Np),np.zeros(grid.Np) # potential + self.free_charge,self.fixed_charge = np.zeros(grid.Np),np.zeros(grid.Np) # free charge density and fixed charge density self.Temperature = 300.0 # temperature in unit of Kelvin self.kBT = Boltzmann*self.Temperature/eV2J # thermal energy in unit of eV # store the boundary information: xmin,xmax,ymin,ymax,zmin,zmax,gate self.boudnary_points = {i:"in" for i in range(self.grid.Np)} # initially set all points as internal - self.boudnary_points_get() + self.boundary_points_get() self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially, all grid points are set to zero self.potential_eps_get(gate_list) self.potential_eps_get(dielectric_list) - def boudnary_points_get(self): + def boundary_points_get(self): # set the boundary points - for i in range(self.grid.Np): - if self.grid.grid_coord[i,0] == np.min(self.grid.xall): - self.boudnary_points[i] = "xmin" - elif self.grid.grid_coord[i,0] == np.max(self.grid.xall): - self.boudnary_points[i] = "xmax" - elif self.grid.grid_coord[i,1] == np.min(self.grid.yall): - self.boudnary_points[i] = "ymin" - elif self.grid.grid_coord[i,1] == np.max(self.grid.yall): - self.boudnary_points[i] = "ymax" - elif self.grid.grid_coord[i,2] == np.min(self.grid.zall): - self.boudnary_points[i] = "zmin" - elif self.grid.grid_coord[i,2] == np.max(self.grid.zall): - self.boudnary_points[i] = "zmax" + xmin,xmax = np.min(self.grid.xall),np.max(self.grid.xall) + ymin,ymax = np.min(self.grid.yall),np.max(self.grid.yall) + zmin,zmax = np.min(self.grid.zall),np.max(self.grid.zall) internal_NP = 0 for i in range(self.grid.Np): - if self.boudnary_points[i] == "in": - internal_NP += 1 + if self.grid.grid_coord[i,0] == xmin: self.boudnary_points[i] = "xmin" + elif self.grid.grid_coord[i,0] == xmax: self.boudnary_points[i] = "xmax" + elif self.grid.grid_coord[i,1] == ymin: self.boudnary_points[i] = "ymin" + elif self.grid.grid_coord[i,1] == ymax: self.boudnary_points[i] = "ymax" + elif self.grid.grid_coord[i,2] == zmin: self.boudnary_points[i] = "zmin" + elif self.grid.grid_coord[i,2] == zmax: self.boudnary_points[i] = "zmax" + else: internal_NP +=1 + + # internal_NP = 0 + # for i in range(self.grid.Np): + # if self.boudnary_points[i] == "in": + # internal_NP += 1 self.internal_NP = internal_NP def potential_eps_get(self,region_list): From a85e9d010292b24069ec4144afb4080f472df891 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 11 Mar 2024 11:31:32 +0800 Subject: [PATCH 055/209] update interface class initialization --- dptb/negf/poisson_init.py | 8 +++++--- dptb/postprocess/NEGF.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 546c1fb8..dbb487f4 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -149,8 +149,8 @@ def __init__(self,grid,gate_list,dielectric_list): self.boundary_points_get() self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially, all grid points are set to zero - self.potential_eps_get(gate_list) - self.potential_eps_get(dielectric_list) + self.potential_eps_get(gate_list+dielectric_list) + def boundary_points_get(self): @@ -186,11 +186,13 @@ def potential_eps_get(self,region_list): (region_list[i].ymax>=self.grid.grid_coord[:,1])& (region_list[i].zmin<=self.grid.grid_coord[:,2])& (region_list[i].zmax>=self.grid.grid_coord[:,2]))[0] - if region_list[i].__class__.__name__ == 'Gate': #attribute gate potential to the corresponding grid points + if region_list[i].__class__.__name__ == 'Gate': + #attribute gate potential to the corresponding grid points self.boudnary_points.update({index[i]: "Gate" for i in range(len(index))}) self.lead_gate_potential[index] = region_list[i].Ef gate_point += len(index) elif region_list[i].__class__.__name__ == 'Dielectric': + # attribute dielectric permittivity to the corresponding grid points self.eps[index] = region_list[i].eps else: raise ValueError('Unknown region type: ',region_list[i].__class__.__name__) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 756ac05c..2e10955d 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -207,9 +207,9 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): #initial guess for electrostatic potential log.info(msg="-----Initial guess for electrostatic potential----") interface_poisson.solve_poisson(method=self.poisson_options['solver'],tolerance=tolerance) - np.save(self.results_path+"/initial_guess_phi.npy",interface_poisson.phi) + # np.save(self.results_path+"/initial_guess_phi.npy",interface_poisson.phi) atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) - np.save(self.results_path+"/initial_guess_phi_at_atom.npy",interface_poisson.phi[atom_gridpoint_index]) + # np.save(self.results_path+"/initial_guess_phi_at_atom.npy",interface_poisson.phi[atom_gridpoint_index]) log.info(msg="-------------------------------------------\n") max_diff = 1e30; max_diff_list = [] From d22f36e5becf64147baa5a59de25c3e8e691233e Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 11 Mar 2024 13:25:07 +0800 Subject: [PATCH 056/209] change gate to dielectric --- dptb/postprocess/NEGF.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 2e10955d..6fe8a267 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -195,7 +195,7 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # ymin,ymax = self.dielectric_region[dd].get("y_range",None).split(':') # zmin,zmax = self.dielectric_region[dd].get("z_range",None).split(':') - dielectric_init = Gate(self.dielectric_region[dd].get("x_range",None).split(':'),\ + dielectric_init = Dielectric(self.dielectric_region[dd].get("x_range",None).split(':'),\ self.dielectric_region[dd].get("y_range",None).split(':'),\ self.dielectric_region[dd].get("z_range",None).split(':')) dielectric_init.eps = float(self.dielectric_region[dd].get("relative permittivity",None)) From eeb552a54cc371e644e002896c222c3205f3b31e Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 11 Mar 2024 13:35:00 +0800 Subject: [PATCH 057/209] add some notes --- dptb/postprocess/NEGF.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 6fe8a267..928690cc 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -178,10 +178,6 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # create gate Gate_list = [] for gg in range(len(self.gate_region)): - # xmin,xmax = self.gate_region[gg].get("x_range",None).split(':') - # ymin,ymax = self.gate_region[gg].get("y_range",None).split(':') - # zmin,zmax = self.gate_region[gg].get("z_range",None).split(':') - # gate_init = Gate(float(xmin),float(xmax),float(ymin),float(ymax),float(zmin),float(zmax)) gate_init = Gate(self.gate_region[gg].get("x_range",None).split(':'),\ self.gate_region[gg].get("y_range",None).split(':'),\ self.gate_region[gg].get("z_range",None).split(':')) @@ -191,10 +187,6 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # create dielectric Dielectric_list = [] for dd in range(len(self.dielectric_region)): - # xmin,xmax = self.dielectric_region[dd].get("x_range",None).split(':') - # ymin,ymax = self.dielectric_region[dd].get("y_range",None).split(':') - # zmin,zmax = self.dielectric_region[dd].get("z_range",None).split(':') - dielectric_init = Dielectric(self.dielectric_region[dd].get("x_range",None).split(':'),\ self.dielectric_region[dd].get("y_range",None).split(':'),\ self.dielectric_region[dd].get("z_range",None).split(':')) @@ -207,8 +199,8 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): #initial guess for electrostatic potential log.info(msg="-----Initial guess for electrostatic potential----") interface_poisson.solve_poisson(method=self.poisson_options['solver'],tolerance=tolerance) - # np.save(self.results_path+"/initial_guess_phi.npy",interface_poisson.phi) atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) + # np.save(self.results_path+"/initial_guess_phi.npy",interface_poisson.phi) # np.save(self.results_path+"/initial_guess_phi_at_atom.npy",interface_poisson.phi[atom_gridpoint_index]) log.info(msg="-------------------------------------------\n") From f852edddcabb655addc011227c6a53607b2ec345 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 11 Mar 2024 13:53:17 +0800 Subject: [PATCH 058/209] seperate scipy and pyamg clearer --- dptb/negf/poisson_init.py | 99 ++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index dbb487f4..c24730b3 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -198,22 +198,21 @@ def potential_eps_get(self,region_list): raise ValueError('Unknown region type: ',region_list[i].__class__.__name__) print('Number of gate points: ',gate_point) - def to_pyamg(self,dtype=None): + def to_pyamg(self,dtype=np.float64): # convert to amg format A,b matrix - if dtype == None: - dtype = np.float64 + # if dtype == None: + # dtype = np.float64 # A = poisson(self.grid.shape,format='csr',dtype=dtype) - A = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) - b = np.zeros(A.shape[0],dtype=A.dtype) - self.construct_poisson(A,b) + Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) + B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) + self.NR_construct_Jacobian(Jacobian) + self.NR_construct_B(B) - return A,b + return Jacobian,B - def to_scipy(self,dtype=None): + def to_scipy(self,dtype=np.float64): # convert to amg format A,b matrix - if dtype == None: - dtype = np.float64 # A = poisson(self.grid.shape,format='csr',dtype=dtype) Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) @@ -300,12 +299,12 @@ def construct_poisson(self,A,b): #TODO: add lead potential. For dptb-negf, we only need to change zmin and zmax as lead - def solve_poisson_pyamg(self,A,b,tolerance=1e-7,accel=None): + def solve_pyamg(self,A,b,tolerance=1e-7,accel=None): # solve the Poisson equation print('Solve Poisson equation by pyamg') pyamg_solver = pyamg.aggregation.smoothed_aggregation_solver(A, max_levels=1000) del A - print('Poisson equation solver: ',pyamg_solver) + # print('Poisson equation solver: ',pyamg_solver) residuals = [] def callback(x): @@ -320,57 +319,61 @@ def callback(x): x = pyamg_solver.solve( b, tol=tolerance, - callback=callback, + # callback=callback, residuals=residuals, accel=accel, cycle="W", - maxiter=1e7, + maxiter=1e3, ) - print("Done solving the Poisson equation!") return x def solve_poisson(self,method='pyamg',tolerance=1e-7): # solve poisson equation: if method == 'pyamg': - A,b = self.to_pyamg() - self.phi = self.solve_poisson_pyamg(A,b,tolerance) - max_diff = np.max(abs(self.phi-self.phi_old)) - return max_diff + print('Solve Poisson equation by pyamg') + # A,b = self.to_pyamg() + # self.phi = self.solve_poisson_pyamg(A,b,tolerance) + # max_diff = np.max(abs(self.phi-self.phi_old)) + # return max_diff elif method == 'scipy': - print('Solve Poisson equation by scipy') - # NR iteration - self.phi_initial = self.phi.copy() # tilde_phi in paper - norm_avp = 1.0; NR_circle_count = 0 - while norm_avp > 1e-3 and NR_circle_count < 100: - Jacobian,B = self.to_scipy() - norm_B = np.linalg.norm(B) - + else: + raise ValueError('Unknown Poisson solver: ',method) + # NR iteration + self.phi_initial = self.phi.copy() # tilde_phi in paper + norm_avp = 1.0; NR_circle_count = 0 + while norm_avp > 1e-3 and NR_circle_count < 100: + Jacobian,B = self.to_scipy() + norm_B = np.linalg.norm(B) + if method == 'scipy': delta_phi = spsolve(Jacobian,B) - self.phi_oldstep = self.phi.copy() - - max_diff_NR = np.max(abs(delta_phi)) - print('max_diff_NR: ',max_diff_NR) - norm_avp = np.linalg.norm(delta_phi) - self.phi += delta_phi - if norm_avp > 1e-3: + elif method == 'pyamg': + delta_phi = self.solve_pyamg(Jacobian,B,tolerance=1e-5) + else: + print('Unknown Poisson solver: ',method) + self.phi_oldstep = self.phi.copy() + + max_diff_NR = np.max(abs(delta_phi)) + print('max_diff_NR: ',max_diff_NR) + norm_avp = np.linalg.norm(delta_phi) + self.phi += delta_phi + if norm_avp > 1e-3: + _,B = self.to_scipy() + norm_B_new = np.linalg.norm(B) + print('norm_B_new: ',norm_B_new) + control_count = 1 + while norm_B_new > norm_B and control_count < 2: + self.phi -= delta_phi/np.power(2,control_count) _,B = self.to_scipy() norm_B_new = np.linalg.norm(B) - print('norm_B_new: ',norm_B_new) - control_count = 1 - while norm_B_new > norm_B and control_count < 2: - self.phi -= delta_phi/np.power(2,control_count) - _,B = self.to_scipy() - norm_B_new = np.linalg.norm(B) - control_count += 1 - print('control_count: ',control_count,' norm_B_new: ',norm_B_new) - NR_circle_count += 1 - print('NR circle: ',NR_circle_count,' norm_avp: ', norm_avp) - max_diff = np.max(abs(self.phi-self.phi_old)) - return max_diff - else: - raise ValueError('Unknown Poisson solver: ',method) + control_count += 1 + print('control_count: ',control_count,' norm_B_new: ',norm_B_new) + NR_circle_count += 1 + print('NR circle: ',NR_circle_count,' norm_avp: ', norm_avp) + max_diff = np.max(abs(self.phi-self.phi_old)) + return max_diff + From f1363a874b480d26639d168e0e3bf7e90755fa0c Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 11 Mar 2024 14:57:50 +0800 Subject: [PATCH 059/209] transform csr to lil when attribtue values --- dptb/negf/poisson_init.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index c24730b3..84b731f3 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -205,7 +205,9 @@ def to_pyamg(self,dtype=np.float64): # A = poisson(self.grid.shape,format='csr',dtype=dtype) Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) - self.NR_construct_Jacobian(Jacobian) + Jacobian_lil = Jacobian.tolil() + self.NR_construct_Jacobian(Jacobian_lil) + Jacobian = Jacobian_lil.tocsr() self.NR_construct_B(B) return Jacobian,B @@ -216,7 +218,11 @@ def to_scipy(self,dtype=np.float64): # A = poisson(self.grid.shape,format='csr',dtype=dtype) Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) - self.NR_construct_Jacobian(Jacobian) + + Jacobian_lil = Jacobian.tolil() + self.NR_construct_Jacobian(Jacobian_lil) + Jacobian = Jacobian_lil.tocsr() + self.NR_construct_B(B) # self.construct_poisson(A,b) return Jacobian,B @@ -332,10 +338,6 @@ def solve_poisson(self,method='pyamg',tolerance=1e-7): # solve poisson equation: if method == 'pyamg': print('Solve Poisson equation by pyamg') - # A,b = self.to_pyamg() - # self.phi = self.solve_poisson_pyamg(A,b,tolerance) - # max_diff = np.max(abs(self.phi-self.phi_old)) - # return max_diff elif method == 'scipy': print('Solve Poisson equation by scipy') else: @@ -379,7 +381,8 @@ def solve_poisson(self,method='pyamg',tolerance=1e-7): def NR_construct_Jacobian(self,J): # construct the Jacobian matrix for the Poisson equation - + + Nx = self.grid.shape[0];Ny = self.grid.shape[1];Nz = self.grid.shape[2] for gp_index in range(self.grid.Np): if self.boudnary_points[gp_index] == "in": @@ -426,6 +429,9 @@ def NR_construct_Jacobian(self,J): elif self.boudnary_points[gp_index] == "Gate": J[gp_index,gp_index] = elementary_charge + + + def NR_construct_B(self,B): # construct the -B matrix in NR iteration # Note that the sign of B has been changed for convenience in later NR iteration From 1508c5847d147e3a1e0e58b3d29671666b7cd775 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 11 Mar 2024 15:59:38 +0800 Subject: [PATCH 060/209] combine construct_Jac and construct_B to a func --- dptb/negf/poisson_init.py | 99 +++++++++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 9 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 84b731f3..519e23c3 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -205,11 +205,14 @@ def to_pyamg(self,dtype=np.float64): # A = poisson(self.grid.shape,format='csr',dtype=dtype) Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) + # Jacobian_lil = Jacobian.tolil() + # self.NR_construct_Jacobian(Jacobian_lil) + # Jacobian = Jacobian_lil.tocsr() + # self.NR_construct_B(B) + Jacobian_lil = Jacobian.tolil() - self.NR_construct_Jacobian(Jacobian_lil) - Jacobian = Jacobian_lil.tocsr() - self.NR_construct_B(B) - + self.NR_construct_Jac_B(Jacobian_lil,B) + Jacobian = Jacobian_lil.tocsr() return Jacobian,B @@ -219,11 +222,14 @@ def to_scipy(self,dtype=np.float64): Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) - Jacobian_lil = Jacobian.tolil() - self.NR_construct_Jacobian(Jacobian_lil) - Jacobian = Jacobian_lil.tocsr() + # Jacobian_lil = Jacobian.tolil() + # self.NR_construct_Jacobian(Jacobian_lil) + # Jacobian = Jacobian_lil.tocsr() + # self.NR_construct_B(B) - self.NR_construct_B(B) + Jacobian_lil = Jacobian.tolil() + self.NR_construct_Jac_B(Jacobian_lil,B) + Jacobian = Jacobian_lil.tocsr() # self.construct_poisson(A,b) return Jacobian,B @@ -403,7 +409,8 @@ def NR_construct_Jacobian(self,J): # add flux term to matrix A J[gp_index,gp_index] = -(flux_xm+flux_xp+flux_ym+flux_yp+flux_zm+flux_zp)\ - +elementary_charge*self.free_charge[gp_index]*(-np.sign(self.free_charge[gp_index]))/self.kBT*np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT) + +elementary_charge*self.free_charge[gp_index]*(-np.sign(self.free_charge[gp_index]))/self.kBT*\ + np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT) J[gp_index,gp_index-1] = flux_xm J[gp_index,gp_index+1] = flux_xp J[gp_index,gp_index-Nx] = flux_ym @@ -484,6 +491,80 @@ def NR_construct_B(self,B): + def NR_construct_Jac_B(self,J,B): + # construct the Jacobian and B for the Poisson equation + + + Nx = self.grid.shape[0];Ny = self.grid.shape[1];Nz = self.grid.shape[2] + for gp_index in range(self.grid.Np): + if self.boudnary_points[gp_index] == "in": + flux_xm_J = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index-1]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index,0]-self.grid.grid_coord[gp_index-1,0]) + flux_xm_B = flux_xm_J*(self.phi[gp_index-1]-self.phi[gp_index]) + + flux_xp_J = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index+1]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index+1,0]-self.grid.grid_coord[gp_index,0]) + flux_xp_B = flux_xp_J*(self.phi[gp_index+1]-self.phi[gp_index]) + + flux_ym_J = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index-Nx]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index-Nx,1]-self.grid.grid_coord[gp_index,1]) + flux_ym_B = flux_ym_J*(self.phi[gp_index-Nx]-self.phi[gp_index]) + flux_yp_J = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index+Nx]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index+Nx,1]-self.grid.grid_coord[gp_index,1]) + flux_yp_B = flux_yp_J*(self.phi[gp_index+Nx]-self.phi[gp_index]) + + flux_zm_J = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index-Nx*Ny]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index-Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) + flux_zm_B = flux_zm_J*(self.phi[gp_index-Nx*Ny]-self.phi[gp_index]) + + flux_zp_J = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index+Nx*Ny]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index+Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) + flux_zp_B = flux_zp_J*(self.phi[gp_index+Nx*Ny]-self.phi[gp_index]) + + # add flux term to matrix J + J[gp_index,gp_index] = -(flux_xm_J+flux_xp_J+flux_ym_J+flux_yp_J+flux_zm_J+flux_zp_J)\ + +elementary_charge*self.free_charge[gp_index]*(-np.sign(self.free_charge[gp_index]))/self.kBT*\ + np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT) + J[gp_index,gp_index-1] = flux_xm_J + J[gp_index,gp_index+1] = flux_xp_J + J[gp_index,gp_index-Nx] = flux_ym_J + J[gp_index,gp_index+Nx] = flux_yp_J + J[gp_index,gp_index-Nx*Ny] = flux_zm_J + J[gp_index,gp_index+Nx*Ny] = flux_zp_J + + + # add flux term to matrix B + B[gp_index] = (flux_xm_B+flux_xp_B+flux_ym_B+flux_yp_B+flux_zm_B+flux_zp_B) + B[gp_index] += elementary_charge*self.free_charge[gp_index]*np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT)\ + +elementary_charge*self.fixed_charge[gp_index] + + else:# boundary points + J[gp_index,gp_index] = elementary_charge + + if self.boudnary_points[gp_index] == "xmin": + J[gp_index,gp_index+1] = -1.0*elementary_charge + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+1])*elementary_charge + elif self.boudnary_points[gp_index] == "xmax": + J[gp_index,gp_index-1] = -1.0*elementary_charge + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-1])*elementary_charge + elif self.boudnary_points[gp_index] == "ymin": + J[gp_index,gp_index+Nx] = -1.0*elementary_charge + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+Nx])*elementary_charge + elif self.boudnary_points[gp_index] == "ymax": + J[gp_index,gp_index-Nx] = -1.0*elementary_charge + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx])*elementary_charge + elif self.boudnary_points[gp_index] == "zmin": + J[gp_index,gp_index+Nx*Ny] = -1.0*elementary_charge + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+Nx*Ny])*elementary_charge + elif self.boudnary_points[gp_index] == "zmax": + J[gp_index,gp_index-Nx*Ny] = -1.0*elementary_charge + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx*Ny])*elementary_charge + elif self.boudnary_points[gp_index] == "Gate": + J[gp_index,gp_index] = elementary_charge + B[gp_index] = (self.phi[gp_index]+self.lead_gate_potential[gp_index])*elementary_charge + + if B[gp_index]!=0: # for convenience change the sign of B in later NR iteration + B[gp_index] = -B[gp_index] \ No newline at end of file From ce734a04b5b943594f1f9401f449c1a4fedb2cde Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 11 Mar 2024 17:35:18 +0800 Subject: [PATCH 061/209] simplify the code for boundary conditions --- dptb/negf/poisson_init.py | 50 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 519e23c3..66c399fc 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -198,7 +198,7 @@ def potential_eps_get(self,region_list): raise ValueError('Unknown region type: ',region_list[i].__class__.__name__) print('Number of gate points: ',gate_point) - def to_pyamg(self,dtype=np.float64): + def to_pyamg_Jac_B(self,dtype=np.float64): # convert to amg format A,b matrix # if dtype == None: # dtype = np.float64 @@ -216,7 +216,7 @@ def to_pyamg(self,dtype=np.float64): return Jacobian,B - def to_scipy(self,dtype=np.float64): + def to_scipy_Jac_B(self,dtype=np.float64): # convert to amg format A,b matrix # A = poisson(self.grid.shape,format='csr',dtype=dtype) Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) @@ -311,7 +311,7 @@ def construct_poisson(self,A,b): #TODO: add lead potential. For dptb-negf, we only need to change zmin and zmax as lead - def solve_pyamg(self,A,b,tolerance=1e-7,accel=None): + def solver_pyamg(self,A,b,tolerance=1e-7,accel=None): # solve the Poisson equation print('Solve Poisson equation by pyamg') pyamg_solver = pyamg.aggregation.smoothed_aggregation_solver(A, max_levels=1000) @@ -352,12 +352,12 @@ def solve_poisson(self,method='pyamg',tolerance=1e-7): self.phi_initial = self.phi.copy() # tilde_phi in paper norm_avp = 1.0; NR_circle_count = 0 while norm_avp > 1e-3 and NR_circle_count < 100: - Jacobian,B = self.to_scipy() + Jacobian,B = self.to_scipy_Jac_B() norm_B = np.linalg.norm(B) if method == 'scipy': delta_phi = spsolve(Jacobian,B) elif method == 'pyamg': - delta_phi = self.solve_pyamg(Jacobian,B,tolerance=1e-5) + delta_phi = self.solver_pyamg(Jacobian,B,tolerance=1e-5) else: print('Unknown Poisson solver: ',method) self.phi_oldstep = self.phi.copy() @@ -367,13 +367,13 @@ def solve_poisson(self,method='pyamg',tolerance=1e-7): norm_avp = np.linalg.norm(delta_phi) self.phi += delta_phi if norm_avp > 1e-3: - _,B = self.to_scipy() + _,B = self.to_scipy_Jac_B() norm_B_new = np.linalg.norm(B) print('norm_B_new: ',norm_B_new) control_count = 1 while norm_B_new > norm_B and control_count < 2: self.phi -= delta_phi/np.power(2,control_count) - _,B = self.to_scipy() + _,B = self.to_scipy_Jac_B() norm_B_new = np.linalg.norm(B) control_count += 1 print('control_count: ',control_count,' norm_B_new: ',norm_B_new) @@ -493,8 +493,7 @@ def NR_construct_B(self,B): def NR_construct_Jac_B(self,J,B): # construct the Jacobian and B for the Poisson equation - - + Nx = self.grid.shape[0];Ny = self.grid.shape[1];Nz = self.grid.shape[2] for gp_index in range(self.grid.Np): if self.boudnary_points[gp_index] == "in": @@ -536,33 +535,32 @@ def NR_construct_Jac_B(self,J,B): # add flux term to matrix B B[gp_index] = (flux_xm_B+flux_xp_B+flux_ym_B+flux_yp_B+flux_zm_B+flux_zp_B) - B[gp_index] += elementary_charge*self.free_charge[gp_index]*np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT)\ - +elementary_charge*self.fixed_charge[gp_index] + B[gp_index] += elementary_charge*self.free_charge[gp_index]*np.exp(-np.sign(self.free_charge[gp_index])\ + *(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT)+elementary_charge*self.fixed_charge[gp_index] else:# boundary points - J[gp_index,gp_index] = elementary_charge + J[gp_index,gp_index] = 1.0 # correct for both Dirichlet and Neumann boundary condition if self.boudnary_points[gp_index] == "xmin": - J[gp_index,gp_index+1] = -1.0*elementary_charge - B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+1])*elementary_charge + J[gp_index,gp_index+1] = -1.0 + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+1]) elif self.boudnary_points[gp_index] == "xmax": - J[gp_index,gp_index-1] = -1.0*elementary_charge - B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-1])*elementary_charge + J[gp_index,gp_index-1] = -1.0 + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-1]) elif self.boudnary_points[gp_index] == "ymin": - J[gp_index,gp_index+Nx] = -1.0*elementary_charge - B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+Nx])*elementary_charge + J[gp_index,gp_index+Nx] = -1.0 + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+Nx]) elif self.boudnary_points[gp_index] == "ymax": - J[gp_index,gp_index-Nx] = -1.0*elementary_charge - B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx])*elementary_charge + J[gp_index,gp_index-Nx] = -1.0 + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx]) elif self.boudnary_points[gp_index] == "zmin": - J[gp_index,gp_index+Nx*Ny] = -1.0*elementary_charge - B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+Nx*Ny])*elementary_charge + J[gp_index,gp_index+Nx*Ny] = -1.0 + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+Nx*Ny]) elif self.boudnary_points[gp_index] == "zmax": - J[gp_index,gp_index-Nx*Ny] = -1.0*elementary_charge - B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx*Ny])*elementary_charge + J[gp_index,gp_index-Nx*Ny] = -1.0 + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx*Ny]) elif self.boudnary_points[gp_index] == "Gate": - J[gp_index,gp_index] = elementary_charge - B[gp_index] = (self.phi[gp_index]+self.lead_gate_potential[gp_index])*elementary_charge + B[gp_index] = (self.phi[gp_index]+self.lead_gate_potential[gp_index]) if B[gp_index]!=0: # for convenience change the sign of B in later NR iteration B[gp_index] = -B[gp_index] From c1699d129f4d1bd01c145a99ba6e1595ea3ef2b8 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 12 Mar 2024 11:00:03 +0800 Subject: [PATCH 062/209] change sign from hLL+v*sLL to hLL-v*sLL --- dptb/negf/negf_hamiltonian_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 699db8b1..08330bc4 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -249,7 +249,7 @@ def get_hs_lead(self, kpoint, tab, v): f["SL"][ix], f["SLL"][ix], f["SDL"][ix] - return hL-v*sL, hLL+v*sLL, hDL, sL, sLL, sDL # TODO: check hLL+v*sLL is correct or not + return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL # TODO: check hLL+v*sLL is correct or not def attach_potential(): pass From cb77077a512a8b735af7047076f6d7d76c41fc53 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 12 Mar 2024 11:03:35 +0800 Subject: [PATCH 063/209] remove unnecessary codes in poisson_init.py --- dptb/negf/poisson_init.py | 211 +------------------------------------- 1 file changed, 3 insertions(+), 208 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 66c399fc..7b64bc9f 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -15,7 +15,6 @@ class Grid(object): # define the grid in 3D space def __init__(self,xg,yg,zg,xa,ya,za): # xg,yg,zg are the coordinates of the basic grid points - # self.xg = np.around(xg,decimals=5);self.yg = np.around(yg,decimals=5);self.zg = np.around(zg,decimals=5) self.xg = xg self.yg = yg self.zg = zg @@ -108,9 +107,7 @@ def __init__(self,x_range,y_range,z_range): super().__init__(x_range,y_range,z_range) # Fermi_level of gate (in unit eV) self.Ef = 0.0 - # self.xmin,self.xmax = float(xrange[0]),float(xrange[1]) - # self.ymin,self.ymax = float(yrange[0]),float(yrange[1]) - # self.zmin,self.zmax = float(zrange[0]),float(zrange[1]) + class Dielectric(region): def __init__(self,x_range,y_range,z_range): @@ -118,10 +115,8 @@ def __init__(self,x_range,y_range,z_range): super().__init__(x_range,y_range,z_range) # dielectric permittivity self.eps = 1.0 - # # gate region - # self.xmin,self.xmax = float(xrange[0]),float(xrange[1]) - # self.ymin,self.ymax = float(yrange[0]),float(yrange[1]) - # self.zmin,self.zmax = float(zrange[0]),float(zrange[1]) + + class Interface3D(object): @@ -168,10 +163,6 @@ def boundary_points_get(self): elif self.grid.grid_coord[i,2] == zmax: self.boudnary_points[i] = "zmax" else: internal_NP +=1 - # internal_NP = 0 - # for i in range(self.grid.Np): - # if self.boudnary_points[i] == "in": - # internal_NP += 1 self.internal_NP = internal_NP def potential_eps_get(self,region_list): @@ -200,15 +191,9 @@ def potential_eps_get(self,region_list): def to_pyamg_Jac_B(self,dtype=np.float64): # convert to amg format A,b matrix - # if dtype == None: - # dtype = np.float64 # A = poisson(self.grid.shape,format='csr',dtype=dtype) Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) - # Jacobian_lil = Jacobian.tolil() - # self.NR_construct_Jacobian(Jacobian_lil) - # Jacobian = Jacobian_lil.tocsr() - # self.NR_construct_B(B) Jacobian_lil = Jacobian.tolil() self.NR_construct_Jac_B(Jacobian_lil,B) @@ -222,94 +207,12 @@ def to_scipy_Jac_B(self,dtype=np.float64): Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) - # Jacobian_lil = Jacobian.tolil() - # self.NR_construct_Jacobian(Jacobian_lil) - # Jacobian = Jacobian_lil.tocsr() - # self.NR_construct_B(B) - Jacobian_lil = Jacobian.tolil() self.NR_construct_Jac_B(Jacobian_lil,B) Jacobian = Jacobian_lil.tocsr() # self.construct_poisson(A,b) return Jacobian,B - def construct_poisson(self,A,b): - # construct the Poisson equation by adding boundary conditions and free charge to the matrix A and vector b - Nx = self.grid.shape[0];Ny = self.grid.shape[1];Nz = self.grid.shape[2] - for gp_index in range(self.grid.Np): - if self.boudnary_points[gp_index] == "in": - # flux_xm = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index-1]+self.eps[gp_index])*0.5\ - # *(self.phi[gp_index-1]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index,0]-self.grid.grid_coord[gp_index-1,0]) - # flux_xp = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index+1]+self.eps[gp_index])*0.5\ - # *(self.phi[gp_index+1]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+1,0]-self.grid.grid_coord[gp_index,0]) - - # flux_ym = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index-Nx]+self.eps[gp_index])*0.5\ - # *(self.phi[gp_index-Nx]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index-Nx,1]-self.grid.grid_coord[gp_index,1]) - # flux_yp = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index+Nx]+self.eps[gp_index])*0.5\ - # *(self.phi[gp_index+Nx]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+Nx,1]-self.grid.grid_coord[gp_index,1]) - - # flux_zm = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index-Nx*Ny]+self.eps[gp_index])*0.5\ - # *(self.phi[gp_index-Nx*Ny]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index-Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) - # flux_zp = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index+Nx*Ny]+self.eps[gp_index])*0.5\ - # *(self.phi[gp_index+Nx*Ny]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) - flux_xm = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index-1]+self.eps[gp_index])*0.5\ - /abs(self.grid.grid_coord[gp_index,0]-self.grid.grid_coord[gp_index-1,0]) - flux_xp = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index+1]+self.eps[gp_index])*0.5\ - /abs(self.grid.grid_coord[gp_index+1,0]-self.grid.grid_coord[gp_index,0]) - - flux_ym = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index-Nx]+self.eps[gp_index])*0.5\ - /abs(self.grid.grid_coord[gp_index-Nx,1]-self.grid.grid_coord[gp_index,1]) - flux_yp = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index+Nx]+self.eps[gp_index])*0.5\ - /abs(self.grid.grid_coord[gp_index+Nx,1]-self.grid.grid_coord[gp_index,1]) - - flux_zm = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index-Nx*Ny]+self.eps[gp_index])*0.5\ - /abs(self.grid.grid_coord[gp_index-Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) - flux_zp = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index+Nx*Ny]+self.eps[gp_index])*0.5\ - /abs(self.grid.grid_coord[gp_index+Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) - - # add flux term to matrix A - A[gp_index,gp_index] = -(flux_xm+flux_xp+flux_ym+flux_yp+flux_zm+flux_zp) - A[gp_index,gp_index-1] = flux_xm - A[gp_index,gp_index+1] = flux_xp - A[gp_index,gp_index-Nx] = flux_ym - A[gp_index,gp_index+Nx] = flux_yp - A[gp_index,gp_index-Nx*Ny] = flux_zm - A[gp_index,gp_index+Nx*Ny] = flux_zp - - # b[gp_index] = -elementary_charge*self.free_charge[gp_index]\ - # *np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT)\ - # -elementary_charge*self.fixed_charge[gp_index] - - # TODO: remove damping factor temporarily - # b[gp_index] = -elementary_charge*self.free_charge[gp_index]\ - # *np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_initial[gp_index])/self.kBT)\ - # -elementary_charge*self.fixed_charge[gp_index] - b[gp_index] = -elementary_charge*self.free_charge[gp_index] -elementary_charge*self.fixed_charge[gp_index] - - # free charge and fixed charge are number of electrons in real-space grid points - # the above free_charge form accelerate the convergence of the Poisson equation - # only internal points have non-zero free_charge and fixed_charge - - else:# boundary points - A[gp_index,gp_index] = 1.0 - assert b[gp_index] == 0.0 - if self.boudnary_points[gp_index] == "xmin": - A[gp_index,gp_index+1] = -1.0 - elif self.boudnary_points[gp_index] == "xmax": - A[gp_index,gp_index-1] = -1.0 - elif self.boudnary_points[gp_index] == "ymin": - A[gp_index,gp_index+Nx] = -1.0 - elif self.boudnary_points[gp_index] == "ymax": - A[gp_index,gp_index-Nx] = -1.0 - elif self.boudnary_points[gp_index] == "zmin": - A[gp_index,gp_index+Nx*Ny] = -1.0 - elif self.boudnary_points[gp_index] == "zmax": - A[gp_index,gp_index-Nx*Ny] = -1.0 - elif self.boudnary_points[gp_index] == "Gate": - b[gp_index] = -1*self.lead_gate_potential[gp_index] - - #TODO: add lead potential. For dptb-negf, we only need to change zmin and zmax as lead - def solver_pyamg(self,A,b,tolerance=1e-7,accel=None): # solve the Poisson equation @@ -382,114 +285,6 @@ def solve_poisson(self,method='pyamg',tolerance=1e-7): max_diff = np.max(abs(self.phi-self.phi_old)) return max_diff - - - - def NR_construct_Jacobian(self,J): - # construct the Jacobian matrix for the Poisson equation - - - Nx = self.grid.shape[0];Ny = self.grid.shape[1];Nz = self.grid.shape[2] - for gp_index in range(self.grid.Np): - if self.boudnary_points[gp_index] == "in": - flux_xm = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index-1]+self.eps[gp_index])*0.5\ - /abs(self.grid.grid_coord[gp_index,0]-self.grid.grid_coord[gp_index-1,0]) - flux_xp = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index+1]+self.eps[gp_index])*0.5\ - /abs(self.grid.grid_coord[gp_index+1,0]-self.grid.grid_coord[gp_index,0]) - - flux_ym = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index-Nx]+self.eps[gp_index])*0.5\ - /abs(self.grid.grid_coord[gp_index-Nx,1]-self.grid.grid_coord[gp_index,1]) - flux_yp = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index+Nx]+self.eps[gp_index])*0.5\ - /abs(self.grid.grid_coord[gp_index+Nx,1]-self.grid.grid_coord[gp_index,1]) - - flux_zm = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index-Nx*Ny]+self.eps[gp_index])*0.5\ - /abs(self.grid.grid_coord[gp_index-Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) - flux_zp = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index+Nx*Ny]+self.eps[gp_index])*0.5\ - /abs(self.grid.grid_coord[gp_index+Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) - - # add flux term to matrix A - J[gp_index,gp_index] = -(flux_xm+flux_xp+flux_ym+flux_yp+flux_zm+flux_zp)\ - +elementary_charge*self.free_charge[gp_index]*(-np.sign(self.free_charge[gp_index]))/self.kBT*\ - np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT) - J[gp_index,gp_index-1] = flux_xm - J[gp_index,gp_index+1] = flux_xp - J[gp_index,gp_index-Nx] = flux_ym - J[gp_index,gp_index+Nx] = flux_yp - J[gp_index,gp_index-Nx*Ny] = flux_zm - J[gp_index,gp_index+Nx*Ny] = flux_zp - - else:# boundary points - J[gp_index,gp_index] = elementary_charge - - if self.boudnary_points[gp_index] == "xmin": - J[gp_index,gp_index+1] = -1.0*elementary_charge - elif self.boudnary_points[gp_index] == "xmax": - J[gp_index,gp_index-1] = -1.0*elementary_charge - elif self.boudnary_points[gp_index] == "ymin": - J[gp_index,gp_index+Nx] = -1.0*elementary_charge - elif self.boudnary_points[gp_index] == "ymax": - J[gp_index,gp_index-Nx] = -1.0*elementary_charge - elif self.boudnary_points[gp_index] == "zmin": - J[gp_index,gp_index+Nx*Ny] = -1.0*elementary_charge - elif self.boudnary_points[gp_index] == "zmax": - J[gp_index,gp_index-Nx*Ny] = -1.0*elementary_charge - elif self.boudnary_points[gp_index] == "Gate": - J[gp_index,gp_index] = elementary_charge - - - - - def NR_construct_B(self,B): - # construct the -B matrix in NR iteration - # Note that the sign of B has been changed for convenience in later NR iteration - # return -B - Nx = self.grid.shape[0];Ny = self.grid.shape[1];Nz = self.grid.shape[2] - for gp_index in range(self.grid.Np): - if self.boudnary_points[gp_index] == "in": - flux_xm = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index-1]+self.eps[gp_index])*0.5\ - *(self.phi[gp_index-1]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index,0]-self.grid.grid_coord[gp_index-1,0]) - flux_xp = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index+1]+self.eps[gp_index])*0.5\ - *(self.phi[gp_index+1]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+1,0]-self.grid.grid_coord[gp_index,0]) - - flux_ym = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index-Nx]+self.eps[gp_index])*0.5\ - *(self.phi[gp_index-Nx]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index-Nx,1]-self.grid.grid_coord[gp_index,1]) - flux_yp = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index+Nx]+self.eps[gp_index])*0.5\ - *(self.phi[gp_index+Nx]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+Nx,1]-self.grid.grid_coord[gp_index,1]) - - flux_zm = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index-Nx*Ny]+self.eps[gp_index])*0.5\ - *(self.phi[gp_index-Nx*Ny]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index-Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) - flux_zp = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index+Nx*Ny]+self.eps[gp_index])*0.5\ - *(self.phi[gp_index+Nx*Ny]-self.phi[gp_index])/abs(self.grid.grid_coord[gp_index+Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) - - # add flux term to matrix B - B[gp_index] = (flux_xm+flux_xp+flux_ym+flux_yp+flux_zm+flux_zp) - B[gp_index] += elementary_charge*self.free_charge[gp_index]*np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT)\ - +elementary_charge*self.fixed_charge[gp_index] - - - else:# boundary points - - if self.boudnary_points[gp_index] == "xmin": - B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+1])*elementary_charge - elif self.boudnary_points[gp_index] == "xmax": - B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-1])*elementary_charge - elif self.boudnary_points[gp_index] == "ymin": - B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+Nx])*elementary_charge - elif self.boudnary_points[gp_index] == "ymax": - B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx])*elementary_charge - elif self.boudnary_points[gp_index] == "zmin": - B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+Nx*Ny])*elementary_charge - elif self.boudnary_points[gp_index] == "zmax": - B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx*Ny])*elementary_charge - elif self.boudnary_points[gp_index] == "Gate": - B[gp_index] = (self.phi[gp_index]+self.lead_gate_potential[gp_index])*elementary_charge - #TODO: add lead potential. For dptb-negf, we only need to change zmin and zmax as lead - - if B[gp_index]!=0: # for convenience change the sign of B in later NR iteration - B[gp_index] = -B[gp_index] - - - def NR_construct_Jac_B(self,J,B): # construct the Jacobian and B for the Poisson equation From 7a5c0e42ba1600f325242e447ac03ce277c1cb94 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 12 Mar 2024 14:09:32 +0800 Subject: [PATCH 064/209] simplify solve_poisson_NRcycle in poisson_init.py --- dptb/negf/poisson_init.py | 52 +++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 7b64bc9f..9c80187c 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -202,8 +202,8 @@ def to_pyamg_Jac_B(self,dtype=np.float64): def to_scipy_Jac_B(self,dtype=np.float64): - # convert to amg format A,b matrix - # A = poisson(self.grid.shape,format='csr',dtype=dtype) + # create the Jacobian and B for the Poisson equation in scipy sparse format + Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) @@ -243,45 +243,49 @@ def callback(x): return x - def solve_poisson(self,method='pyamg',tolerance=1e-7): - # solve poisson equation: - if method == 'pyamg': - print('Solve Poisson equation by pyamg') - elif method == 'scipy': - print('Solve Poisson equation by scipy') - else: - raise ValueError('Unknown Poisson solver: ',method) - # NR iteration - self.phi_initial = self.phi.copy() # tilde_phi in paper - norm_avp = 1.0; NR_circle_count = 0 - while norm_avp > 1e-3 and NR_circle_count < 100: + def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7): + # solve the Poisson equation with Newton-Raphson method + + + norm_delta_phi = 1.0 # Euclidean norm of delta_phi in each step + NR_cycle = 0 + + while norm_delta_phi > 1e-3 and NR_cycle < 100: + # obtain the Jacobian and B for the Poisson equation Jacobian,B = self.to_scipy_Jac_B() norm_B = np.linalg.norm(B) + if method == 'scipy': + if NR_cycle == 0: + print('Solve Poisson equation by scipy') delta_phi = spsolve(Jacobian,B) elif method == 'pyamg': + if NR_cycle == 0: + print('Solve Poisson equation by pyamg') delta_phi = self.solver_pyamg(Jacobian,B,tolerance=1e-5) else: print('Unknown Poisson solver: ',method) - self.phi_oldstep = self.phi.copy() - - max_diff_NR = np.max(abs(delta_phi)) - print('max_diff_NR: ',max_diff_NR) - norm_avp = np.linalg.norm(delta_phi) + + max_delta_phi = np.max(abs(delta_phi)) + norm_delta_phi = np.linalg.norm(delta_phi) self.phi += delta_phi - if norm_avp > 1e-3: + + if norm_delta_phi > 1e-3: _,B = self.to_scipy_Jac_B() norm_B_new = np.linalg.norm(B) - print('norm_B_new: ',norm_B_new) control_count = 1 + # control the norm of B to avoid larger norm_B after one NR cycle while norm_B_new > norm_B and control_count < 2: + if control_count==1: + print('norm_B increase after this NR cycle, contorler starts!') self.phi -= delta_phi/np.power(2,control_count) _,B = self.to_scipy_Jac_B() norm_B_new = np.linalg.norm(B) control_count += 1 - print('control_count: ',control_count,' norm_B_new: ',norm_B_new) - NR_circle_count += 1 - print('NR circle: ',NR_circle_count,' norm_avp: ', norm_avp) + print(' control_count: ',control_count,' norm_B_new: ',norm_B_new) + NR_cycle += 1 + print(' NR cycle: ',NR_cycle,' norm_delta_phi in NR: ', norm_delta_phi,' max_delta_phi in NR: ',max_delta_phi) + max_diff = np.max(abs(self.phi-self.phi_old)) return max_diff From 0c2f7abaafe5733578473695b491f6794e95c363 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 12 Mar 2024 14:28:18 +0800 Subject: [PATCH 065/209] add proffiler in NEGF.py --- dptb/negf/poisson_init.py | 3 ++- dptb/postprocess/NEGF.py | 21 ++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 9c80187c..e1a4de05 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -244,7 +244,8 @@ def callback(x): def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7): - # solve the Poisson equation with Newton-Raphson method + # solve the Poisson equation with Newton-Raphson method + # delta_phi: the correction on the potential norm_delta_phi = 1.0 # Euclidean norm of delta_phi in each step diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 928690cc..adfdef73 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -21,6 +21,8 @@ import logging from dptb.negf.poisson_init import Grid,Interface3D,Gate,Dielectric +from pyinstrument import Profiler + log = logging.getLogger(__name__) # TODO : add common class to set all the dtype and precision. @@ -171,7 +173,9 @@ def compute(self): self.negf_compute(scf_require=False,Vbias=potential_add) def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): - + + profiler = Profiler() + profiler.start() # create real-space grid grid = self.get_grid(self.poisson_options["grid"],self.deviceprop.structure) @@ -198,7 +202,7 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): #initial guess for electrostatic potential log.info(msg="-----Initial guess for electrostatic potential----") - interface_poisson.solve_poisson(method=self.poisson_options['solver'],tolerance=tolerance) + interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) # np.save(self.results_path+"/initial_guess_phi.npy",interface_poisson.phi) # np.save(self.results_path+"/initial_guess_phi_at_atom.npy",interface_poisson.phi[atom_gridpoint_index]) @@ -244,18 +248,22 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): interface_poisson.phi_old = interface_poisson.phi.copy() - max_diff = interface_poisson.solve_poisson(method=self.poisson_options['solver'],tolerance=tolerance) + max_diff = interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi_old-interface_poisson.phi) - iter_count += 1 - print('Poisson iteration: ',iter_count,' max_diff: ',max_diff) + iter_count += 1 # Gummel type iteration + print('Poisson iteration: ',iter_count,' Max Phi Diff: ',max_diff,'\n') max_diff_list.append(max_diff) + if iter_count > max_iter: log.info(msg="Warning! Poisson iteration exceeds max_iter {}".format(int(max_iter))) + profiler.stop() + with open('profile_report.html', 'w') as report_file: + report_file.write(profiler.output_html()) break self.poisson_out = {} @@ -270,6 +278,9 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # calculate transport properties with converged potential self.negf_compute(scf_require=False,Vbias=self.potential_tensor) + profiler.stop() + with open('profile_report.html', 'w') as report_file: + report_file.write(profiler.output_html()) def negf_compute(self,scf_require=False,Vbias=None): # check if scf is required From 5a9178827b13563afc2a4e0d9e32b5ce21012887 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 12 Mar 2024 21:01:51 +0800 Subject: [PATCH 066/209] replace print by log.info --- dptb/negf/poisson_init.py | 43 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index e1a4de05..ea4816d0 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -6,10 +6,12 @@ from scipy.constants import epsilon_0 as eps0 #TODO:later add to untils.constants.py from scipy.sparse import csr_matrix from scipy.sparse.linalg import spsolve +import logging #eps0 = 8.854187817e-12 # in the unit of F/m # As length in deeptb is in the unit of Angstrom, the unit of eps0 is F/Angstrom eps0 = eps0*1e-10 # in the unit of F/Angstrom +log = logging.getLogger(__name__) class Grid(object): # define the grid in 3D space @@ -32,7 +34,7 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.zall = np.unique(np.concatenate((uza,self.zg),0)) self.shape = (len(self.xall),len(self.yall),len(self.zall)) - print('unique len of zall:',len(np.unique(self.zall))) + # create meshgrid xmesh,ymesh,zmesh = np.meshgrid(self.xall,self.yall,self.zall) @@ -46,8 +48,9 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.Np = int(len(self.xall)*len(self.yall)*len(self.zall)) assert self.Np == len(xmesh) assert self.grid_coord.shape[0] == self.Np - - print('Number of grid points: ',self.Np,' grid shape: ',self.grid_coord.shape,' Number of atoms: ',self.Na) + + log.info(msg="Number of grid points: {:.1f} Number of atoms: {:.1f}".format(float(self.Np),self.Na)) + # print('Number of grid points: ',self.Np,' grid shape: ',self.grid_coord.shape,' Number of atoms: ',self.Na) # find the index of the atoms in the grid self.atom_index_dict = self.find_atom_index(xa,ya,za) @@ -72,8 +75,6 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.surface_grid = surface_grid # grid points order are the same as that of self.grid_coord - - def find_atom_index(self,xa,ya,za): # find the index of the atoms in the grid swap = {} @@ -187,7 +188,9 @@ def potential_eps_get(self,region_list): self.eps[index] = region_list[i].eps else: raise ValueError('Unknown region type: ',region_list[i].__class__.__name__) - print('Number of gate points: ',gate_point) + + log.info(msg="Number of gate points: {:.1f}".format(float(gate_point))) + def to_pyamg_Jac_B(self,dtype=np.float64): # convert to amg format A,b matrix @@ -216,7 +219,8 @@ def to_scipy_Jac_B(self,dtype=np.float64): def solver_pyamg(self,A,b,tolerance=1e-7,accel=None): # solve the Poisson equation - print('Solve Poisson equation by pyamg') + # log.info(msg="Solve Poisson equation by pyamg") + pyamg_solver = pyamg.aggregation.smoothed_aggregation_solver(A, max_levels=1000) del A # print('Poisson equation solver: ',pyamg_solver) @@ -249,23 +253,23 @@ def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7): norm_delta_phi = 1.0 # Euclidean norm of delta_phi in each step - NR_cycle = 0 + NR_cycle_step = 0 - while norm_delta_phi > 1e-3 and NR_cycle < 100: + while norm_delta_phi > 1e-3 and NR_cycle_step < 100: # obtain the Jacobian and B for the Poisson equation Jacobian,B = self.to_scipy_Jac_B() norm_B = np.linalg.norm(B) if method == 'scipy': - if NR_cycle == 0: - print('Solve Poisson equation by scipy') + if NR_cycle_step == 0: + log.info(msg="Solve Poisson equation by scipy") delta_phi = spsolve(Jacobian,B) elif method == 'pyamg': - if NR_cycle == 0: - print('Solve Poisson equation by pyamg') + if NR_cycle_step == 0: + log.info(msg="Solve Poisson equation by pyamg") delta_phi = self.solver_pyamg(Jacobian,B,tolerance=1e-5) else: - print('Unknown Poisson solver: ',method) + raise ValueError('Unknown Poisson solver: ',method) max_delta_phi = np.max(abs(delta_phi)) norm_delta_phi = np.linalg.norm(delta_phi) @@ -278,14 +282,15 @@ def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7): # control the norm of B to avoid larger norm_B after one NR cycle while norm_B_new > norm_B and control_count < 2: if control_count==1: - print('norm_B increase after this NR cycle, contorler starts!') + log.warning(msg="norm_B increase after this NR cycle, contorler starts!") self.phi -= delta_phi/np.power(2,control_count) _,B = self.to_scipy_Jac_B() norm_B_new = np.linalg.norm(B) - control_count += 1 - print(' control_count: ',control_count,' norm_B_new: ',norm_B_new) - NR_cycle += 1 - print(' NR cycle: ',NR_cycle,' norm_delta_phi in NR: ', norm_delta_phi,' max_delta_phi in NR: ',max_delta_phi) + control_count += 1 + log.info(msg=" control_count: {:.1f} norm_B_new: {:.5f}".format(float(control_count),norm_B_new)) + + NR_cycle_step += 1 + log.info(msg=" NR cycle step: {:.1f} norm_delta_phi: {:.10f} max_delta_phi: {:.10f}".format(float(NR_cycle_step),norm_delta_phi,max_delta_phi)) max_diff = np.max(abs(self.phi-self.phi_old)) return max_diff From f3e81f9d9667823ef151b2c3a6515da1783189d4 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 12 Mar 2024 22:03:45 +0800 Subject: [PATCH 067/209] get_hs_lead only when voltage changes --- dptb/negf/lead_property.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 877b3fd2..a2f38fdc 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -96,7 +96,13 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S # if not hasattr(self, "HL"): #TODO: check here whether it is necessary to calculate the self energy every time - self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) + if not hasattr(self, "HL"): + self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) + self.voltage_old = self.voltage + elif abs(self.voltage_old-self.voltage)>1e-6: + self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) + self.voltage_old = self.voltage + self.se, _ = selfEnergy( ee=energy, From 469a94b59c4aa4fc1974a04f587217df0c046a11 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 12 Mar 2024 23:05:56 +0800 Subject: [PATCH 068/209] move print to log --- dptb/negf/poisson_init.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index ea4816d0..33ac6250 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -287,10 +287,10 @@ def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7): _,B = self.to_scipy_Jac_B() norm_B_new = np.linalg.norm(B) control_count += 1 - log.info(msg=" control_count: {:.1f} norm_B_new: {:.5f}".format(float(control_count),norm_B_new)) + log.info(msg=" control_count: {:d} norm_B_new: {:.5f}".format(float(control_count),norm_B_new)) NR_cycle_step += 1 - log.info(msg=" NR cycle step: {:.1f} norm_delta_phi: {:.10f} max_delta_phi: {:.10f}".format(float(NR_cycle_step),norm_delta_phi,max_delta_phi)) + log.info(msg=" NR cycle step: {:d} norm_delta_phi: {:.10f} max_delta_phi: {:.10f}".format(float(NR_cycle_step),norm_delta_phi,max_delta_phi)) max_diff = np.max(abs(self.phi-self.phi_old)) return max_diff From c1dd88a765120c5d683fa871cc9a1e44629a2096 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 12 Mar 2024 23:06:14 +0800 Subject: [PATCH 069/209] log info update --- dptb/negf/poisson_init.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 33ac6250..d229cb02 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -287,10 +287,10 @@ def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7): _,B = self.to_scipy_Jac_B() norm_B_new = np.linalg.norm(B) control_count += 1 - log.info(msg=" control_count: {:d} norm_B_new: {:.5f}".format(float(control_count),norm_B_new)) + log.info(msg=" control_count: {:.1f} norm_B_new: {:.5f}".format(float(control_count),norm_B_new)) NR_cycle_step += 1 - log.info(msg=" NR cycle step: {:d} norm_delta_phi: {:.10f} max_delta_phi: {:.10f}".format(float(NR_cycle_step),norm_delta_phi,max_delta_phi)) + log.info(msg=" NR cycle step: {:d} norm_delta_phi: {:.8f} max_delta_phi: {:.8f}".format(int(NR_cycle_step),norm_delta_phi,max_delta_phi)) max_diff = np.max(abs(self.phi-self.phi_old)) return max_diff From a669274181b7eab6ccf20f3bf96f6a089618eb6b Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 13 Mar 2024 09:46:33 +0800 Subject: [PATCH 070/209] simplify NEGF.py --- dptb/postprocess/NEGF.py | 110 +++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index adfdef73..195bbf35 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -115,7 +115,7 @@ def __init__(self, apiHrk, run_opt, jdata): ## Poisson equation settings self.poisson_options = j_must_have(jdata, "poisson_options") # self.LDOS_integral = {} # for electron density integral - self.free_charge_nanotcad = {} + self.free_charge = {} # net charge: hole - electron self.gate_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("gate")] self.dielectric_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("dielectric")] @@ -173,7 +173,7 @@ def compute(self): self.negf_compute(scf_require=False,Vbias=potential_add) def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): - + profiler = Profiler() profiler.start() # create real-space grid @@ -204,27 +204,21 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): log.info(msg="-----Initial guess for electrostatic potential----") interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) - # np.save(self.results_path+"/initial_guess_phi.npy",interface_poisson.phi) - # np.save(self.results_path+"/initial_guess_phi_at_atom.npy",interface_poisson.phi[atom_gridpoint_index]) log.info(msg="-------------------------------------------\n") - max_diff = 1e30; max_diff_list = [] + max_diff_phi = 1e30; max_diff_list = [] iter_count=0 - while max_diff > err: - + # Gummel type iteration + while max_diff_phi > err: # update Hamiltonian by modifying onsite energy with potential atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) np.save(self.results_path+"/atom_gridpoint_index.npy",atom_gridpoint_index) - self.potential_at_atom = interface_poisson.phi[atom_gridpoint_index] # a vector with length of number of atoms - - potential_list = [] - for i in range(len(self.device_atom_norbs)): - potential_list.append(self.potential_at_atom[i]*torch.ones(self.device_atom_norbs[i])) - self.potential_tensor = torch.cat(potential_list) - torch.save(self.potential_tensor, self.results_path+"/potential_tensor.pth") - - #TODO: check the sign of potential_tensor: -1 is right or not. - self.negf_compute(scf_require=True,Vbias=self.potential_tensor) + self.potential_at_atom = interface_poisson.phi[atom_gridpoint_index] + self.potential_at_orb = torch.cat([torch.full((norb,), p) for p, norb in zip(self.potential_at_atom, self.device_atom_norbs)]) + torch.save(self.potential_at_orb, self.results_path+"/potential_at_orb.pth") + + + self.negf_compute(scf_require=True,Vbias=self.potential_at_orb) # Vbias makes sense for orthogonal basis as in NanoTCAD # TODO: check if Vbias makes sense for non-orthogonal basis @@ -243,24 +237,20 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # TODO: check the spin degenracy # TODO: add k summation operation interface_poisson.free_charge[atom_gridpoint_index] =\ - np.real(self.free_charge_nanotcad[str(self.kpoints[0])].numpy()) + np.real(self.free_charge[str(self.kpoints[0])].numpy()) interface_poisson.phi_old = interface_poisson.phi.copy() - - max_diff = interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) - + max_diff_phi = interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi_old-interface_poisson.phi) iter_count += 1 # Gummel type iteration - print('Poisson iteration: ',iter_count,' Max Phi Diff: ',max_diff,'\n') - max_diff_list.append(max_diff) - - + log.info(msg="Poisson-NEGF iteration: {} Potential Diff Maximum: {}\n".format(iter_count,max_diff_phi)) + max_diff_list.append(max_diff_phi) if iter_count > max_iter: - log.info(msg="Warning! Poisson iteration exceeds max_iter {}".format(int(max_iter))) + log.info(msg="Warning! Poisson-NEGF iteration exceeds the upper limit of iterations {}".format(int(max_iter))) profiler.stop() with open('profile_report.html', 'w') as report_file: report_file.write(profiler.output_html()) @@ -272,15 +262,16 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): self.poisson_out['grid'] = torch.tensor(interface_poisson.grid.grid_coord) self.poisson_out['free_charge_at_atom'] = torch.tensor(interface_poisson.free_charge[atom_gridpoint_index]) self.poisson_out['max_diff_list'] = torch.tensor(max_diff_list) - torch.save(self.poisson_out, self.results_path+"/poisson.out.pth") # calculate transport properties with converged potential - self.negf_compute(scf_require=False,Vbias=self.potential_tensor) + self.negf_compute(scf_require=False,Vbias=self.potential_at_orb) - profiler.stop() - with open('profile_report.html', 'w') as report_file: - report_file.write(profiler.output_html()) + # output the profile report in html format + if iter_count <= max_iter: + profiler.stop() + with open('profile_report.html', 'w') as report_file: + report_file.write(profiler.output_html()) def negf_compute(self,scf_require=False,Vbias=None): # check if scf is required @@ -304,9 +295,9 @@ def negf_compute(self,scf_require=False,Vbias=None): if hasattr(self, "uni_grid"): self.out["k"] = k dE = abs(self.uni_grid[1] - self.uni_grid[0]) - self.free_charge_nanotcad.update({str(k):torch.zeros_like(torch.tensor(self.device_atom_norbs),dtype=torch.complex128)}) + self.free_charge.update({str(k):torch.zeros_like(torch.tensor(self.device_atom_norbs),dtype=torch.complex128)}) - output_freq = int(len(self.uni_grid)/10) + output_freq = int(len(self.uni_grid)/5) for ie, e in enumerate(self.uni_grid): if ie % output_freq == 0: @@ -314,7 +305,7 @@ def negf_compute(self,scf_require=False,Vbias=None): leads = self.stru_options.keys() for ll in leads: if ll.startswith("lead"): - # TODO: temporarily set the voltage to -1*potential_tensor[0] and -1*potential_tensor[-1] + # TODO: temporarily set the voltage to -1*potential_at_orb[0] and -1*potential_at_orb[-1] if Vbias is not None: if ll == 'lead_L' : getattr(self.deviceprop, ll).voltage = Vbias[0] @@ -352,12 +343,18 @@ def negf_compute(self,scf_require=False,Vbias=None): prop.append(self.compute_LDOS(k)) else: - self.get_density_nanotcad(e, k, dE) + self.compute_density_Fiori( + e, k, dE, + deviceprop=self.deviceprop, + device_atom_norbs=self.device_atom_norbs, + potential_at_atom = self.potential_at_atom) + + # whether scf_require is True or False, density are computed for Poisson-NEGF SCF if self.out_density or self.out_potential: - self.out["DM_eq"], self.out["DM_neq"] = self.compute_density(k,Vbias) + self.out["DM_eq"], self.out["DM_neq"] = self.compute_density_Ozaki(k,Vbias) if self.out_potential: pass @@ -446,9 +443,10 @@ def compute_LDOS(self, kpoint): def compute_current_nscf(self, kpoint, ee, tc): return self.deviceprop._cal_current_nscf_(ee, tc) - def compute_density(self, kpoint,Vbias): + def compute_density_Ozaki(self, kpoint,Vbias): DM_eq, DM_neq = self.density.integrate(deviceprop=self.deviceprop, kpoint=kpoint, Vbias=Vbias) return DM_eq, DM_neq + def compute_current(self, kpoint): self.deviceprop.cal_green_function(e=self.int_grid, kpoint=kpoint, block_tridiagonal=self.block_tridiagonal) @@ -462,37 +460,37 @@ def SCF(self): pass - def get_density_nanotcad(self,e,kpoint,dE,eta_lead=1e-5, eta_device=0.,Vbias=None): + def compute_density_Fiori(self,e,kpoint,dE,deviceprop,device_atom_norbs,potential_at_atom): - tx, ty = self.deviceprop.g_trans.shape - lx, ly = self.deviceprop.lead_L.se.shape - rx, ry = self.deviceprop.lead_R.se.shape + tx, ty = deviceprop.g_trans.shape + lx, ly = deviceprop.lead_L.se.shape + rx, ry = deviceprop.lead_R.se.shape x0 = min(lx, tx) x1 = min(rx, ty) gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) - gammaL[:x0, :x0] += self.deviceprop.lead_L.gamma[:x0, :x0] + gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) - gammaR[-x1:, -x1:] += self.deviceprop.lead_R.gamma[-x1:, -x1:] + gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] - A_L = torch.mm(torch.mm(self.deviceprop.g_trans,gammaL),self.deviceprop.g_trans.conj().T) - A_R = torch.mm(torch.mm(self.deviceprop.g_trans,gammaR),self.deviceprop.g_trans.conj().T) + A_L = torch.mm(torch.mm(deviceprop.g_trans,gammaL),deviceprop.g_trans.conj().T) + A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) - # Vbias = -1 * potential_tensor - for Ei_index, Ei_at_atom in enumerate(-1*self.potential_at_atom): - pre_orbs = sum(self.device_atom_norbs[:Ei_index]) + # Vbias = -1 * potential_at_orb + for Ei_index, Ei_at_atom in enumerate(-1*potential_at_atom): + pre_orbs = sum(device_atom_norbs[:Ei_index]) # electron density if e >= Ei_at_atom: - for j in range(self.device_atom_norbs[Ei_index]): - self.free_charge_nanotcad[str(kpoint)][Ei_index] +=\ - 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*self.deviceprop.lead_L.fermi_dirac(e+self.deviceprop.lead_L.efermi) \ - +A_R[pre_orbs+j,pre_orbs+j]*self.deviceprop.lead_R.fermi_dirac(e+self.deviceprop.lead_R.efermi))*dE + for j in range(device_atom_norbs[Ei_index]): + self.free_charge[str(kpoint)][Ei_index] +=\ + 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ + +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))*dE # 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*self.deviceprop.lead_L.fermi_dirac(e+self.deviceprop.lead_L.mu) \ # +A_R[pre_orbs+j,pre_orbs+j]*self.deviceprop.lead_R.fermi_dirac(e+self.deviceprop.lead_R.mu))*dE # hole density else: - for j in range(self.device_atom_norbs[Ei_index]): - self.free_charge_nanotcad[str(kpoint)][Ei_index] +=\ - 2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-self.deviceprop.lead_L.fermi_dirac(e+self.deviceprop.lead_L.efermi)) \ - +A_R[pre_orbs+j,pre_orbs+j]*(1-self.deviceprop.lead_R.fermi_dirac(e+self.deviceprop.lead_R.efermi)))*dE \ No newline at end of file + for j in range(device_atom_norbs[Ei_index]): + self.free_charge[str(kpoint)][Ei_index] +=\ + 2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ + +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)))*dE \ No newline at end of file From cee3dd66840603721a7138f622afd4c65c6ea6d5 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 13 Mar 2024 11:32:32 +0800 Subject: [PATCH 071/209] move Fiori cal to density.py and simplify negf_compute --- dptb/negf/density.py | 49 +++++++++++++++- dptb/postprocess/NEGF.py | 120 +++++++++++++++++++++------------------ dptb/utils/argcheck.py | 12 +++- 3 files changed, 124 insertions(+), 57 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index 2081ce51..10add8d7 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -227,4 +227,51 @@ def get_density_onsite(self, deviceprop, DM): onsite_density = torch.cat([torch.from_numpy(deviceprop.structure.positions), onsite_density.unsqueeze(-1)], dim=-1) - return onsite_density \ No newline at end of file + return onsite_density + + + +class Fiori(Density): + + def __init__(self, n_gauss): + super(Fiori, self).__init__() + self.n_gauss = n_gauss + self.xs = None + self.wlg = None + + def density_integrate_Fiori(self,e,kpoint,dE,deviceprop,device_atom_norbs,potential_at_atom,free_charge): + + # xs, wlg = gauss_xw(xl=torch.scalar_tensor(xl), xu=torch.scalar_tensor(xu), n=self.n_gauss) + + + tx, ty = deviceprop.g_trans.shape + lx, ly = deviceprop.lead_L.se.shape + rx, ry = deviceprop.lead_R.se.shape + x0 = min(lx, tx) + x1 = min(rx, ty) + + gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) + gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] + gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) + gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] + + A_L = torch.mm(torch.mm(deviceprop.g_trans,gammaL),deviceprop.g_trans.conj().T) + A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) + + # Vbias = -1 * potential_at_orb + for Ei_index, Ei_at_atom in enumerate(-1*potential_at_atom): + pre_orbs = sum(device_atom_norbs[:Ei_index]) + + # electron density + if e >= Ei_at_atom: + for j in range(device_atom_norbs[Ei_index]): + free_charge[str(kpoint)][Ei_index] +=\ + 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ + +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))*dE + + # hole density + else: + for j in range(device_atom_norbs[Ei_index]): + free_charge[str(kpoint)][Ei_index] +=\ + 2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ + +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)))*dE \ No newline at end of file diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 195bbf35..5a24a453 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -5,7 +5,7 @@ from dptb.negf.negf_utils import quad, gauss_xw,leggauss,update_kmap from dptb.negf.ozaki_res_cal import ozaki_residues from dptb.negf.negf_hamiltonian_init import NEGFHamiltonianInit -from dptb.negf.density import Ozaki +from dptb.negf.density import Ozaki,Fiori from dptb.negf.areshkin_pole_sum import pole_maker from dptb.negf.device_property import DeviceProperty from dptb.negf.lead_property import LeadProperty @@ -92,6 +92,8 @@ def __init__(self, apiHrk, run_opt, jdata): self.density_options = j_must_have(self.jdata, "density_options") if self.density_options["method"] == "Ozaki": self.density = Ozaki(R=self.density_options["R"], M_cut=self.density_options["M_cut"], n_gauss=self.density_options["n_gauss"]) + elif self.density_options["method"] == "Fiori": + self.density = Fiori(n_gauss=self.density_options["n_gauss"]) else: raise ValueError @@ -132,10 +134,12 @@ def generate_energy_grid(self): v_list = [self.stru_options[i].get("voltage", None) for i in self.stru_options if i.startswith("lead")] v_list_b = [i == v_list[0] for i in v_list] if not all(v_list_b): - cal_pole = True + if self.density_options["method"] == "Ozaki": + cal_pole = True cal_int_grid = True elif self.out_density or self.out_potential: - cal_pole = True + if self.density_options["method"] == "Ozaki": + cal_pole = True v_list = [self.stru_options[i].get("voltage", None) for i in self.stru_options if i.startswith("lead")] v_list_b = [i == v_list[0] for i in v_list] if not all(v_list_b): @@ -151,7 +155,7 @@ def generate_energy_grid(self): # Energy gird is set relative to Fermi level self.uni_grid = torch.linspace(start=self.jdata["emin"], end=self.jdata["emax"], steps=int((self.jdata["emax"]-self.jdata["emin"])/self.jdata["espacing"])) - if cal_pole: + if cal_pole and self.density_options["method"] == "Ozaki": self.poles, self.residues = ozaki_residues(M_cut=self.jdata["density_options"]["M_cut"]) self.poles = 1j* self.poles * self.kBT + self.deviceprop.lead_L.mu - self.deviceprop.mu @@ -163,9 +167,9 @@ def generate_energy_grid(self): def compute(self): if self.scf: - if not self.out_density: - self.out_density = True - raise UserWarning("SCF is required, but out_density is set to False. Automatically Setting out_density to True.") + # if not self.out_density: + # self.out_density = True + # raise UserWarning("SCF is required, but out_density is set to False. Automatically Setting out_density to True.") self.poisson_negf_scf(err=self.poisson_options['err'],tolerance=self.poisson_options['tolerance'],\ max_iter=self.poisson_options['max_iter'],mix_rate=self.poisson_options['mix_rate']) else: @@ -305,8 +309,8 @@ def negf_compute(self,scf_require=False,Vbias=None): leads = self.stru_options.keys() for ll in leads: if ll.startswith("lead"): - # TODO: temporarily set the voltage to -1*potential_at_orb[0] and -1*potential_at_orb[-1] - if Vbias is not None: + if Vbias is not None and self.density_options["method"] == "Fiori": + # set voltage as -1*potential_at_orb[0] and -1*potential_at_orb[-1] for self-energy same as in NanoTCAD if ll == 'lead_L' : getattr(self.deviceprop, ll).voltage = Vbias[0] else: @@ -342,24 +346,29 @@ def negf_compute(self,scf_require=False,Vbias=None): prop = self.out.setdefault("LDOS", []) prop.append(self.compute_LDOS(k)) else: - - self.compute_density_Fiori( - e, k, dE, - deviceprop=self.deviceprop, - device_atom_norbs=self.device_atom_norbs, - potential_at_atom = self.potential_at_atom) + if self.density_options["method"] == "Fiori": + self.compute_density_Fiori( + e=e, + kpoint=k, + dE = dE, + deviceprop=self.deviceprop, + device_atom_norbs=self.device_atom_norbs, + potential_at_atom = self.potential_at_atom, + free_charge = self.free_charge) + else: + raise ValueError("Ozaki method is not supported for Poisson-NEGF SCF in this version.") - - - - # whether scf_require is True or False, density are computed for Poisson-NEGF SCF - if self.out_density or self.out_potential: - self.out["DM_eq"], self.out["DM_neq"] = self.compute_density_Ozaki(k,Vbias) - if self.out_potential: - pass - if scf_require==False: + if self.out_density or self.out_potential: + if self.density_options["method"] == "Ozaki": + self.out["DM_eq"], self.out["DM_neq"] = self.compute_density_Ozaki(k,Vbias) + elif self.density_options["method"] == "Fiori": + log.warning("Fiori method is not supported for output density in this version.") + else: + raise ValueError("Unknown method for density calculation.") + if self.out_potential: + pass if self.out_dos: self.out["DOS"] = torch.stack(self.out["DOS"]) if self.out_tc or self.out_current_nscf: @@ -372,8 +381,9 @@ def negf_compute(self,scf_require=False,Vbias=None): if self.out_lcurrent: lcurrent = 0 + log.info(msg="computing local current at k = [{:.4f},{:.4f},{:.4f}]".format(float(k[0]),float(k[1]),float(k[2]))) for i, e in enumerate(self.int_grid): - log.info(msg="computing green's function at e = {:.3f}".format(float(e))) + log.info(msg=" computing green's function at e = {:.3f}".format(float(e))) leads = self.stru_options.keys() for ll in leads: if ll.startswith("lead"): @@ -447,6 +457,9 @@ def compute_density_Ozaki(self, kpoint,Vbias): DM_eq, DM_neq = self.density.integrate(deviceprop=self.deviceprop, kpoint=kpoint, Vbias=Vbias) return DM_eq, DM_neq + def compute_density_Fiori(self,e,kpoint,dE,deviceprop,device_atom_norbs,potential_at_atom,free_charge): + self.density.density_integrate_Fiori(e,kpoint,dE,deviceprop,device_atom_norbs,potential_at_atom,free_charge) + def compute_current(self, kpoint): self.deviceprop.cal_green_function(e=self.int_grid, kpoint=kpoint, block_tridiagonal=self.block_tridiagonal) @@ -460,37 +473,36 @@ def SCF(self): pass - def compute_density_Fiori(self,e,kpoint,dE,deviceprop,device_atom_norbs,potential_at_atom): + # def compute_density_Fiori(self,e,kpoint,dE,deviceprop,device_atom_norbs,potential_at_atom,free_charge): - tx, ty = deviceprop.g_trans.shape - lx, ly = deviceprop.lead_L.se.shape - rx, ry = deviceprop.lead_R.se.shape - x0 = min(lx, tx) - x1 = min(rx, ty) + # tx, ty = deviceprop.g_trans.shape + # lx, ly = deviceprop.lead_L.se.shape + # rx, ry = deviceprop.lead_R.se.shape + # x0 = min(lx, tx) + # x1 = min(rx, ty) - gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) - gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] - gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) - gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] + # gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) + # gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] + # gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) + # gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] - A_L = torch.mm(torch.mm(deviceprop.g_trans,gammaL),deviceprop.g_trans.conj().T) - A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) + # A_L = torch.mm(torch.mm(deviceprop.g_trans,gammaL),deviceprop.g_trans.conj().T) + # A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) - # Vbias = -1 * potential_at_orb - for Ei_index, Ei_at_atom in enumerate(-1*potential_at_atom): - pre_orbs = sum(device_atom_norbs[:Ei_index]) + # # Vbias = -1 * potential_at_orb + # for Ei_index, Ei_at_atom in enumerate(-1*potential_at_atom): + # pre_orbs = sum(device_atom_norbs[:Ei_index]) - # electron density - if e >= Ei_at_atom: - for j in range(device_atom_norbs[Ei_index]): - self.free_charge[str(kpoint)][Ei_index] +=\ - 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ - +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))*dE - # 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*self.deviceprop.lead_L.fermi_dirac(e+self.deviceprop.lead_L.mu) \ - # +A_R[pre_orbs+j,pre_orbs+j]*self.deviceprop.lead_R.fermi_dirac(e+self.deviceprop.lead_R.mu))*dE - # hole density - else: - for j in range(device_atom_norbs[Ei_index]): - self.free_charge[str(kpoint)][Ei_index] +=\ - 2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ - +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)))*dE \ No newline at end of file + # # electron density + # if e >= Ei_at_atom: + # for j in range(device_atom_norbs[Ei_index]): + # free_charge[str(kpoint)][Ei_index] +=\ + # 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ + # +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))*dE + + # # hole density + # else: + # for j in range(device_atom_norbs[Ei_index]): + # free_charge[str(kpoint)][Ei_index] +=\ + # 2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ + # +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)))*dE \ No newline at end of file diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 1362a74d..118cd5e3 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -612,9 +612,11 @@ def poisson_options(): def density_options(): doc_method = "" doc_Ozaki = "" + doc_Fiori = "" return Variant("method", [ - Argument("Ozaki", dict, Ozaki(), doc=doc_method) - ], optional=True, default_tag="Ozaki", doc=doc_Ozaki) + Argument("Ozaki", dict, Ozaki(), doc=doc_Ozaki), + Argument("Fiori", dict, Fiori(), doc=doc_Fiori) + ], optional=True, default_tag="Ozaki", doc=doc_method) def Ozaki(): doc_M_cut = "" @@ -626,6 +628,12 @@ def Ozaki(): Argument("n_gauss", int, optional=True, default=10, doc=doc_n_gauss), ] +def Fiori(): + doc_n_gauss = "" + return [ + Argument("n_gauss", int, optional=True, default=100, doc=doc_n_gauss) + ] + def fmm(): doc_err = "" From d6449f9bef00b41c5416beca34303657d4ab5ed6 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 13 Mar 2024 17:41:24 +0800 Subject: [PATCH 072/209] add gauss integrate to Fiori method --- dptb/negf/density.py | 123 ++++++++++++++++------ dptb/postprocess/NEGF.py | 221 +++++++++++++++++++++------------------ dptb/utils/argcheck.py | 2 + 3 files changed, 211 insertions(+), 135 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index 10add8d7..d0559e91 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -238,40 +238,97 @@ def __init__(self, n_gauss): self.n_gauss = n_gauss self.xs = None self.wlg = None + self.e_grid_Fiori = None - def density_integrate_Fiori(self,e,kpoint,dE,deviceprop,device_atom_norbs,potential_at_atom,free_charge): + def density_integrate_Fiori_direct(self,e_grid,kpoint,Vbias,deviceprop,device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): - # xs, wlg = gauss_xw(xl=torch.scalar_tensor(xl), xu=torch.scalar_tensor(xu), n=self.n_gauss) + dE = e_grid[1] - e_grid[0] + for eidx, e in enumerate(e_grid): + deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) + deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) + deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device,Vbias = Vbias) + + tx, ty = deviceprop.g_trans.shape + lx, ly = deviceprop.lead_L.se.shape + rx, ry = deviceprop.lead_R.se.shape + x0 = min(lx, tx) + x1 = min(rx, ty) + + gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) + gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] + gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) + gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] + + A_L = torch.mm(torch.mm(deviceprop.g_trans,gammaL),deviceprop.g_trans.conj().T) + A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) + + # Vbias = -1 * potential_at_orb + for Ei_index, Ei_at_atom in enumerate(-1*potential_at_atom): + pre_orbs = sum(device_atom_norbs[:Ei_index]) + + # electron density + if e >= Ei_at_atom: + for j in range(device_atom_norbs[Ei_index]): + free_charge[str(kpoint)][Ei_index] +=\ + 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ + +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))*dE + + # hole density + else: + for j in range(device_atom_norbs[Ei_index]): + free_charge[str(kpoint)][Ei_index] +=\ + 2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ + +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)))*dE + + + + + def density_integrate_Fiori_gauss(self,e_grid,kpoint,Vbias,deviceprop,device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): + + if self.xs is None: + self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) + # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() + self.e_grid_Fiori = e_grid + elif self.e_grid_Fiori[0] != e_grid[0] or self.e_grid_Fiori[-1] != e_grid[-1]: + self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) + # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() + self.e_grid_Fiori = e_grid + + for eidx, e in enumerate(self.xs): - tx, ty = deviceprop.g_trans.shape - lx, ly = deviceprop.lead_L.se.shape - rx, ry = deviceprop.lead_R.se.shape - x0 = min(lx, tx) - x1 = min(rx, ty) - - gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) - gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] - gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) - gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] - - A_L = torch.mm(torch.mm(deviceprop.g_trans,gammaL),deviceprop.g_trans.conj().T) - A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) - - # Vbias = -1 * potential_at_orb - for Ei_index, Ei_at_atom in enumerate(-1*potential_at_atom): - pre_orbs = sum(device_atom_norbs[:Ei_index]) - - # electron density - if e >= Ei_at_atom: - for j in range(device_atom_norbs[Ei_index]): - free_charge[str(kpoint)][Ei_index] +=\ - 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ - +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))*dE - - # hole density - else: - for j in range(device_atom_norbs[Ei_index]): - free_charge[str(kpoint)][Ei_index] +=\ - 2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ - +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)))*dE \ No newline at end of file + deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) + deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) + deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device,Vbias = Vbias) + + tx, ty = deviceprop.g_trans.shape + lx, ly = deviceprop.lead_L.se.shape + rx, ry = deviceprop.lead_R.se.shape + x0 = min(lx, tx) + x1 = min(rx, ty) + + gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) + gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] + gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) + gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] + + A_L = torch.mm(torch.mm(deviceprop.g_trans,gammaL),deviceprop.g_trans.conj().T) + A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) + + # Vbias = -1 * potential_at_orb + for Ei_index, Ei_at_atom in enumerate(-1*potential_at_atom): + pre_orbs = sum(device_atom_norbs[:Ei_index]) + + # electron density + if e >= Ei_at_atom: + for j in range(device_atom_norbs[Ei_index]): + free_charge[str(kpoint)][Ei_index] +=\ + self.wlg[eidx]*2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ + +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)) + + # hole density + else: + for j in range(device_atom_norbs[Ei_index]): + free_charge[str(kpoint)][Ei_index] +=\ + self.wlg[eidx]*2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ + +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))) \ No newline at end of file diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 5a24a453..1bc522f5 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -278,35 +278,20 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): report_file.write(profiler.output_html()) def negf_compute(self,scf_require=False,Vbias=None): - # check if scf is required - # if self.scf: - # # perform k-point sampling and scf calculation to get the converged density - # for k in self.kpoints: - # pass - # else: - # pass - # computing output properties + + assert scf_require is not None + for ik, k in enumerate(self.kpoints): - self.out = {} - self.out["lead_L_se"] = {} - self.out["lead_R_se"] = {} - self.out["gtrans"] = {} - self.out['uni_grid'] = self.uni_grid + self.out = {} + self.out["gtrans"] = {}; self.out['uni_grid'] = self.uni_grid; self.out["k"] = k + self.free_charge.update({str(k):torch.zeros_like(torch.tensor(self.device_atom_norbs),dtype=torch.complex128)}) log.info(msg="Properties computation at k = [{:.4f},{:.4f},{:.4f}]".format(float(k[0]),float(k[1]),float(k[2]))) - # computing properties that is functions of E - if hasattr(self, "uni_grid"): - self.out["k"] = k - dE = abs(self.uni_grid[1] - self.uni_grid[0]) - self.free_charge.update({str(k):torch.zeros_like(torch.tensor(self.device_atom_norbs),dtype=torch.complex128)}) - - output_freq = int(len(self.uni_grid)/5) - for ie, e in enumerate(self.uni_grid): - - if ie % output_freq == 0: - log.info(msg="computing green's function at e = {:.3f}".format(float(e))) + if scf_require: + if self.density_options["method"] == "Fiori": leads = self.stru_options.keys() + for ll in leads: if ll.startswith("lead"): if Vbias is not None and self.density_options["method"] == "Fiori": @@ -315,100 +300,121 @@ def negf_compute(self,scf_require=False,Vbias=None): getattr(self.deviceprop, ll).voltage = Vbias[0] else: getattr(self.deviceprop, ll).voltage = Vbias[-1] - - getattr(self.deviceprop, ll).self_energy( - energy=e, - kpoint=k, - eta_lead=self.jdata["eta_lead"], - method=self.jdata["sgf_solver"] - ) - self.out[str(ll)+"_se"][str(e.numpy())] = getattr(self.deviceprop, ll).se - - gtrans = self.deviceprop.cal_green_function( - energy=e, - kpoint=k, - eta_device=self.jdata["eta_device"], - block_tridiagonal=self.block_tridiagonal, - Vbias=Vbias - ) - - self.out["gtrans"][str(e.numpy())] = gtrans - - if scf_require==False: - if self.out_dos: - prop = self.out.setdefault("DOS", []) - prop.append(self.compute_DOS(k)) - if self.out_tc or self.out_current_nscf: - prop = self.out.setdefault("TC", []) - prop.append(self.compute_TC(k)) - if self.out_ldos: - prop = self.out.setdefault("LDOS", []) - prop.append(self.compute_LDOS(k)) - else: - if self.density_options["method"] == "Fiori": - self.compute_density_Fiori( - e=e, - kpoint=k, - dE = dE, - deviceprop=self.deviceprop, - device_atom_norbs=self.device_atom_norbs, - potential_at_atom = self.potential_at_atom, - free_charge = self.free_charge) - else: - raise ValueError("Ozaki method is not supported for Poisson-NEGF SCF in this version.") - - - if scf_require==False: - if self.out_density or self.out_potential: - if self.density_options["method"] == "Ozaki": - self.out["DM_eq"], self.out["DM_neq"] = self.compute_density_Ozaki(k,Vbias) - elif self.density_options["method"] == "Fiori": - log.warning("Fiori method is not supported for output density in this version.") - else: - raise ValueError("Unknown method for density calculation.") - if self.out_potential: - pass - if self.out_dos: - self.out["DOS"] = torch.stack(self.out["DOS"]) - if self.out_tc or self.out_current_nscf: - self.out["TC"] = torch.stack(self.out["TC"]) - if self.out_current_nscf: - self.out["BIAS_POTENTIAL_NSCF"], self.out["CURRENT_NSCF"] = self.compute_current_nscf(k, self.uni_grid, self.out["TC"]) - # computing properties that are not functions of E (improvement can be made here in properties related to integration of energy window of fermi functions) - if self.out_current: - pass - - if self.out_lcurrent: - lcurrent = 0 - log.info(msg="computing local current at k = [{:.4f},{:.4f},{:.4f}]".format(float(k[0]),float(k[1]),float(k[2]))) - for i, e in enumerate(self.int_grid): - log.info(msg=" computing green's function at e = {:.3f}".format(float(e))) + self.compute_density_Fiori( + e_grid = self.uni_grid, + kpoint=k, + Vbias=Vbias, + integrate_way = self.density_options["integrate_way"], + deviceprop=self.deviceprop, + device_atom_norbs=self.device_atom_norbs, + potential_at_atom = self.potential_at_atom, + free_charge = self.free_charge) + else: + # TODO: add Ozaki support for NanoTCAD-style SCF + raise ValueError("Ozaki method is not supported for Poisson-NEGF SCF in this version.") + + + # in non-scf case, computing properties in uni_gird + else: + if hasattr(self, "uni_grid"): + # dE = abs(self.uni_grid[1] - self.uni_grid[0]) + output_freq = int(len(self.uni_grid)/5) + + for ie, e in enumerate(self.uni_grid): + if ie % output_freq == 0: + log.info(msg="computing green's function at e = {:.3f}".format(float(e))) leads = self.stru_options.keys() for ll in leads: if ll.startswith("lead"): + if Vbias is not None and self.density_options["method"] == "Fiori": + # set voltage as -1*potential_at_orb[0] and -1*potential_at_orb[-1] for self-energy same as in NanoTCAD + if ll == 'lead_L' : + getattr(self.deviceprop, ll).voltage = Vbias[0] + else: + getattr(self.deviceprop, ll).voltage = Vbias[-1] + getattr(self.deviceprop, ll).self_energy( energy=e, kpoint=k, eta_lead=self.jdata["eta_lead"], method=self.jdata["sgf_solver"] ) + # self.out[str(ll)+"_se"][str(e.numpy())] = getattr(self.deviceprop, ll).se - self.deviceprop.cal_green_function( - energy=e, - kpoint=k, + gtrans = self.deviceprop.cal_green_function( + energy=e, kpoint=k, eta_device=self.jdata["eta_device"], - block_tridiagonal=self.block_tridiagonal + block_tridiagonal=self.block_tridiagonal, + Vbias=Vbias ) + self.out["gtrans"][str(e.numpy())] = gtrans + - lcurrent += self.int_weight[i] * self.compute_lcurrent(k) - self.out["LOCAL_CURRENT"] = lcurrent + if self.out_dos: + prop = self.out.setdefault("DOS", []) + prop.append(self.compute_DOS(k)) + if self.out_tc or self.out_current_nscf: + prop = self.out.setdefault("TC", []) + prop.append(self.compute_TC(k)) + if self.out_ldos: + prop = self.out.setdefault("LDOS", []) + prop.append(self.compute_LDOS(k)) + + + # over energy loop in uni_gird + # The following code is for output properties before NEGF ends + # TODO: check following code for multiple k points calculation + + if self.out_density or self.out_potential: + if self.density_options["method"] == "Ozaki": + self.out["DM_eq"], self.out["DM_neq"] = self.compute_density_Ozaki(k,Vbias) + elif self.density_options["method"] == "Fiori": + log.warning("Fiori method is not supported for output density in this version.") + else: + raise ValueError("Unknown method for density calculation.") + if self.out_potential: + pass + if self.out_dos: + self.out["DOS"] = torch.stack(self.out["DOS"]) + if self.out_tc or self.out_current_nscf: + self.out["TC"] = torch.stack(self.out["TC"]) + if self.out_current_nscf: + self.out["BIAS_POTENTIAL_NSCF"], self.out["CURRENT_NSCF"] = self.compute_current_nscf(k, self.uni_grid, self.out["TC"]) + # computing properties that are not functions of E (improvement can be made here in properties related to integration of energy window of fermi functions) + if self.out_current: + pass + + if self.out_lcurrent: + lcurrent = 0 + log.info(msg="computing local current at k = [{:.4f},{:.4f},{:.4f}]".format(float(k[0]),float(k[1]),float(k[2]))) + for i, e in enumerate(self.int_grid): + log.info(msg=" computing green's function at e = {:.3f}".format(float(e))) + leads = self.stru_options.keys() + for ll in leads: + if ll.startswith("lead"): + getattr(self.deviceprop, ll).self_energy( + energy=e, + kpoint=k, + eta_lead=self.jdata["eta_lead"], + method=self.jdata["sgf_solver"] + ) + + self.deviceprop.cal_green_function( + energy=e, + kpoint=k, + eta_device=self.jdata["eta_device"], + block_tridiagonal=self.block_tridiagonal + ) + + lcurrent += self.int_weight[i] * self.compute_lcurrent(k) + self.out["LOCAL_CURRENT"] = lcurrent - if scf_require == False: + torch.save(self.out, self.results_path+"/negf.k{}.out.pth".format(ik)) - # plotting + # plotting def get_grid(self,grid_info,structase): @@ -457,8 +463,19 @@ def compute_density_Ozaki(self, kpoint,Vbias): DM_eq, DM_neq = self.density.integrate(deviceprop=self.deviceprop, kpoint=kpoint, Vbias=Vbias) return DM_eq, DM_neq - def compute_density_Fiori(self,e,kpoint,dE,deviceprop,device_atom_norbs,potential_at_atom,free_charge): - self.density.density_integrate_Fiori(e,kpoint,dE,deviceprop,device_atom_norbs,potential_at_atom,free_charge) + def compute_density_Fiori(self,e_grid,kpoint,Vbias,integrate_way,deviceprop,device_atom_norbs,potential_at_atom,free_charge): + if integrate_way == "direct": + self.density.density_integrate_Fiori_direct(e_grid,kpoint,Vbias,deviceprop,device_atom_norbs, + potential_at_atom,free_charge, + eta_lead=self.jdata["eta_lead"], + eta_device=self.jdata["eta_device"],) + elif integrate_way == "gauss": + self.density.density_integrate_Fiori_gauss(e_grid,kpoint,Vbias,deviceprop,device_atom_norbs, + potential_at_atom,free_charge, + eta_lead=self.jdata["eta_lead"], + eta_device=self.jdata["eta_device"],) + else: + raise ValueError("Unknown integrate method for density calculation.") def compute_current(self, kpoint): diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 118cd5e3..224ef49c 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -630,7 +630,9 @@ def Ozaki(): def Fiori(): doc_n_gauss = "" + doc_integrate_way="" return [ + Argument("integrate_way", int, optional=True, default='direct', doc=doc_integrate_way), Argument("n_gauss", int, optional=True, default=100, doc=doc_n_gauss) ] From 7c765682a436a63614c45e3dc166ba04fccf1583 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 13 Mar 2024 20:52:43 +0800 Subject: [PATCH 073/209] merge Fiori_gauss and Fiori_direct together --- dptb/negf/density.py | 112 +++++++++++++++++------------ dptb/negf/negf_hamiltonian_init.py | 8 ++- dptb/postprocess/NEGF.py | 18 +---- 3 files changed, 72 insertions(+), 66 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index d0559e91..e864e172 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -240,11 +240,29 @@ def __init__(self, n_gauss): self.wlg = None self.e_grid_Fiori = None - def density_integrate_Fiori_direct(self,e_grid,kpoint,Vbias,deviceprop,device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): + def density_integrate_Fiori(self,e_grid,kpoint,Vbias,integrate_way,deviceprop,device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): + + if integrate_way == "gauss": + if self.xs is None: + self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) + # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() + self.e_grid_Fiori = e_grid + elif self.e_grid_Fiori[0] != e_grid[0] or self.e_grid_Fiori[-1] != e_grid[-1]: + self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) + # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() + self.e_grid_Fiori = e_grid + integrate_range = self.xs + pre_factor = self.wlg + elif integrate_way == "direct": + dE = e_grid[1] - e_grid[0] + integrate_range = e_grid + pre_factor = dE * torch.ones(len(e_grid)) + else: + raise ValueError("integrate_way only supports gauss and direct in this version") + - dE = e_grid[1] - e_grid[0] - for eidx, e in enumerate(e_grid): + for eidx, e in enumerate(integrate_range): deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device,Vbias = Vbias) @@ -271,64 +289,64 @@ def density_integrate_Fiori_direct(self,e_grid,kpoint,Vbias,deviceprop,device_at if e >= Ei_at_atom: for j in range(device_atom_norbs[Ei_index]): free_charge[str(kpoint)][Ei_index] +=\ - 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ - +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))*dE + pre_factor[eidx]*2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ + +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)) # hole density else: for j in range(device_atom_norbs[Ei_index]): free_charge[str(kpoint)][Ei_index] +=\ - 2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ - +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)))*dE + pre_factor[eidx]*2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ + +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))) - def density_integrate_Fiori_gauss(self,e_grid,kpoint,Vbias,deviceprop,device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): + # def density_integrate_Fiori_gauss(self,e_grid,kpoint,Vbias,deviceprop,device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): - if self.xs is None: - self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) - # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() - self.e_grid_Fiori = e_grid - elif self.e_grid_Fiori[0] != e_grid[0] or self.e_grid_Fiori[-1] != e_grid[-1]: - self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) - # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() - self.e_grid_Fiori = e_grid + # if self.xs is None: + # self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) + # # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() + # self.e_grid_Fiori = e_grid + # elif self.e_grid_Fiori[0] != e_grid[0] or self.e_grid_Fiori[-1] != e_grid[-1]: + # self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) + # # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() + # self.e_grid_Fiori = e_grid - for eidx, e in enumerate(self.xs): + # for eidx, e in enumerate(self.xs): - deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) - deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) - deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device,Vbias = Vbias) + # deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) + # deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) + # deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device,Vbias = Vbias) - tx, ty = deviceprop.g_trans.shape - lx, ly = deviceprop.lead_L.se.shape - rx, ry = deviceprop.lead_R.se.shape - x0 = min(lx, tx) - x1 = min(rx, ty) + # tx, ty = deviceprop.g_trans.shape + # lx, ly = deviceprop.lead_L.se.shape + # rx, ry = deviceprop.lead_R.se.shape + # x0 = min(lx, tx) + # x1 = min(rx, ty) - gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) - gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] - gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) - gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] + # gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) + # gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] + # gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) + # gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] - A_L = torch.mm(torch.mm(deviceprop.g_trans,gammaL),deviceprop.g_trans.conj().T) - A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) + # A_L = torch.mm(torch.mm(deviceprop.g_trans,gammaL),deviceprop.g_trans.conj().T) + # A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) - # Vbias = -1 * potential_at_orb - for Ei_index, Ei_at_atom in enumerate(-1*potential_at_atom): - pre_orbs = sum(device_atom_norbs[:Ei_index]) + # # Vbias = -1 * potential_at_orb + # for Ei_index, Ei_at_atom in enumerate(-1*potential_at_atom): + # pre_orbs = sum(device_atom_norbs[:Ei_index]) - # electron density - if e >= Ei_at_atom: - for j in range(device_atom_norbs[Ei_index]): - free_charge[str(kpoint)][Ei_index] +=\ - self.wlg[eidx]*2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ - +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)) - - # hole density - else: - for j in range(device_atom_norbs[Ei_index]): - free_charge[str(kpoint)][Ei_index] +=\ - self.wlg[eidx]*2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ - +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))) \ No newline at end of file + # # electron density + # if e >= Ei_at_atom: + # for j in range(device_atom_norbs[Ei_index]): + # free_charge[str(kpoint)][Ei_index] +=\ + # self.wlg[eidx]*2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ + # +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)) + + # # hole density + # else: + # for j in range(device_atom_norbs[Ei_index]): + # free_charge[str(kpoint)][Ei_index] +=\ + # self.wlg[eidx]*2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ + # +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))) \ No newline at end of file diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 08330bc4..fb66ca75 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -212,9 +212,11 @@ def get_hs_device(self, kpoint, V, block_tridiagonal=False): if block_tridiagonal: return hd, sd, hl, su, sl, hu else: - print('HD shape:', HD.shape) - print('SD shape:', SD.shape) - print('V shape:', V.shape) + # print('HD shape:', HD.shape) + # print('SD shape:', SD.shape) + # print('V shape:', V.shape) + log.info(msg='Device Hamiltonian shape: {0}x{0}'.format(HD.shape[0], HD.shape[1])) + return [HD - V*SD], [SD], [], [], [], [] def get_hs_lead(self, kpoint, tab, v): diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 1bc522f5..d48ac235 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -301,7 +301,7 @@ def negf_compute(self,scf_require=False,Vbias=None): else: getattr(self.deviceprop, ll).voltage = Vbias[-1] - self.compute_density_Fiori( + self.density.density_integrate_Fiori( e_grid = self.uni_grid, kpoint=k, Vbias=Vbias, @@ -462,21 +462,7 @@ def compute_current_nscf(self, kpoint, ee, tc): def compute_density_Ozaki(self, kpoint,Vbias): DM_eq, DM_neq = self.density.integrate(deviceprop=self.deviceprop, kpoint=kpoint, Vbias=Vbias) return DM_eq, DM_neq - - def compute_density_Fiori(self,e_grid,kpoint,Vbias,integrate_way,deviceprop,device_atom_norbs,potential_at_atom,free_charge): - if integrate_way == "direct": - self.density.density_integrate_Fiori_direct(e_grid,kpoint,Vbias,deviceprop,device_atom_norbs, - potential_at_atom,free_charge, - eta_lead=self.jdata["eta_lead"], - eta_device=self.jdata["eta_device"],) - elif integrate_way == "gauss": - self.density.density_integrate_Fiori_gauss(e_grid,kpoint,Vbias,deviceprop,device_atom_norbs, - potential_at_atom,free_charge, - eta_lead=self.jdata["eta_lead"], - eta_device=self.jdata["eta_device"],) - else: - raise ValueError("Unknown integrate method for density calculation.") - + def compute_current(self, kpoint): self.deviceprop.cal_green_function(e=self.int_grid, kpoint=kpoint, block_tridiagonal=self.block_tridiagonal) From 13c4cf08ca2702512dd39b02715d2a9752844462 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 14 Mar 2024 15:07:40 +0800 Subject: [PATCH 074/209] test --- dptb/negf/density.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index e864e172..f7a69876 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -241,7 +241,8 @@ def __init__(self, n_gauss): self.e_grid_Fiori = None def density_integrate_Fiori(self,e_grid,kpoint,Vbias,integrate_way,deviceprop,device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): - + if integrate_way == "gauss": + print('ok') if integrate_way == "gauss": if self.xs is None: self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) From febcfa8ac62dca22c46e76bd6624f0909a22b00e Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 15 Mar 2024 16:29:30 +0800 Subject: [PATCH 075/209] update density.py device_property.py lead_property.py for ksum --- dptb/negf/density.py | 2 -- dptb/negf/device_property.py | 11 ++++------- dptb/negf/lead_property.py | 18 +++++++++--------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index f7a69876..564a6e77 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -241,8 +241,6 @@ def __init__(self, n_gauss): self.e_grid_Fiori = None def density_integrate_Fiori(self,e_grid,kpoint,Vbias,integrate_way,deviceprop,device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): - if integrate_way == "gauss": - print('ok') if integrate_way == "gauss": if self.xs is None: self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index e722266a..7a9a91ee 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -136,11 +136,8 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr energy = torch.tensor(energy, dtype=torch.complex128) self.block_tridiagonal = block_tridiagonal - if self.kpoint is None: - self.kpoint = kpoint - self.newK_flag = True - elif abs(self.kpoint - kpoint).sum() > 1e-5: - self.kpoint = kpoint + if self.kpoint is None or abs(self.kpoint - torch.tensor(kpoint)).sum() > 1e-5: + self.kpoint = torch.tensor(kpoint) self.newK_flag = True else: self.newK_flag = False @@ -172,14 +169,14 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr else: self.newV_flag = False else: - self.newV_flag = False + self.newV_flag = True # for the first time to run cal_green_function in Poisson-NEGF SCF # if not hasattr(self, "hd") or not hasattr(self, "sd"): #maybe the reason why different kpoint has different green function if not hasattr(self, "hd") or not hasattr(self, "sd"): - self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) + self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(self.kpoint, self.V, block_tridiagonal) elif self.newK_flag or self.newV_flag: # check whether kpoints or Vbias change or not self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index a2f38fdc..a92e2741 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -73,6 +73,8 @@ def __init__(self, tab, hamiltonian, structure, results_path, voltage, e_T=300, self.e_T = e_T self.efermi = efermi self.mu = self.efermi - self.voltage + self.kpoint = None + self.voltage_old = None def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-Sancho"): '''calculate and loads the self energy and surface green function at the given kpoint and energy. @@ -84,7 +86,7 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S energy specific energy value. eta_lead : - the broadening parameter for calculating lead self-energy. + the broadening parameter for calculating lead surface green function. method : specify the method for calculating the self energy. At this stage it only supports "Lopez-Sancho". @@ -92,17 +94,15 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S assert len(np.array(kpoint).reshape(-1)) == 3 # according to given kpoint and e_mesh, calculating or loading the self energy and surface green function to self. if not isinstance(energy, torch.Tensor): - energy = torch.tensor(energy) # energy relative to Ef + energy = torch.tensor(energy) # Energy relative to Ef # if not hasattr(self, "HL"): #TODO: check here whether it is necessary to calculate the self energy every time - if not hasattr(self, "HL"): + if not hasattr(self, "HL") or abs(self.voltage_old-self.voltage)>1e-6 or max(abs(self.kpoint-torch.tensor(kpoint)))>1e-6: self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) self.voltage_old = self.voltage - elif abs(self.voltage_old-self.voltage)>1e-6: - self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) - self.voltage_old = self.voltage - + self.kpoint = torch.tensor(kpoint) + self.se, _ = selfEnergy( ee=energy, @@ -125,12 +125,12 @@ def sigmaLR2Gamma(self, se): Parameters ---------- se - The parameter "se" represents a complex number. + The parameter "se" represents self energy, a complex matrix. Returns ------- Gamma - The Gamma function. + The Gamma function, $\Gamma = 1j(se-se^\dagger)$. ''' return 1j * (se - se.conj().T) From 3d3386aa37ef2cd957fc22faef23d7874fb0059b Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 15 Mar 2024 16:54:24 +0800 Subject: [PATCH 076/209] update negf_hamiltonian_init.py for ksum --- dptb/negf/negf_hamiltonian_init.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index fb66ca75..ebb5a9b0 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -106,9 +106,11 @@ def initialize(self, kpoints, block_tridiagnal=False): projatoms = self.apiH.structure.projatoms self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] - allbonds,hamil_block,_ =self.apiH.get_HR() - torch.save(allbonds, os.path.join(self.results_path, "allbonds"+".pth")) - torch.save(hamil_block, os.path.join(self.results_path, "hamil_block"+".pth")) + self.apiH.get_HR() + # output the allbonds and hamil_block for check + # allbonds,hamil_block,_ =self.apiH.get_HR() + # torch.save(allbonds, os.path.join(self.results_path, "allbonds"+".pth")) + # torch.save(hamil_block, os.path.join(self.results_path, "hamil_block"+".pth")) H, S = self.apiH.get_HK(kpoints=kpoints) d_start = int(np.sum(self.atom_norbs[:proj_device_id[0]])) @@ -129,7 +131,7 @@ def initialize(self, kpoints, block_tridiagnal=False): if kk.startswith("lead"): HS_leads = {} stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] - write(os.path.join(self.results_path, "stru_"+kk+".vasp"), stru_lead) + # write(os.path.join(self.results_path, "stru_"+kk+".vasp"), stru_lead) self.apiH.update_struct(stru_lead, mode="lead", stru_options=self.stru_options.get(kk), pbc=self.stru_options["pbc"]) # update lead id n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]][projatoms[:self.lead_ids[kk][0]]].sum() @@ -151,9 +153,11 @@ def initialize(self, kpoints, block_tridiagnal=False): structure_leads[kk] = self.apiH.structure.struct - allbonds_lead,hamil_block_lead,_ = self.apiH.get_HR() - torch.save(allbonds_lead, os.path.join(self.results_path, "allbonds_"+kk+".pth")) - torch.save(hamil_block_lead, os.path.join(self.results_path, "hamil_block_"+kk+".pth")) + self.apiH.get_HR() + # output the allbonds and hamil_block for check + # allbonds_lead,hamil_block_lead,_ = self.apiH.get_HR() + # torch.save(allbonds_lead, os.path.join(self.results_path, "allbonds_"+kk+".pth")) + # torch.save(hamil_block_lead, os.path.join(self.results_path, "hamil_block_"+kk+".pth")) h, s = self.apiH.get_HK(kpoints=kpoints) nL = int(h.shape[1] / 2) @@ -251,7 +255,7 @@ def get_hs_lead(self, kpoint, tab, v): f["SL"][ix], f["SLL"][ix], f["SDL"][ix] - return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL # TODO: check hLL+v*sLL is correct or not + return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL def attach_potential(): pass From 72bfd7e0975235ec4f7ca64f0eac0d0c56c944b1 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 16 Mar 2024 10:51:38 +0800 Subject: [PATCH 077/209] update unit tests for ksum and poisson-dev --- .../test_negf_hamiltonian/run_input.json | 2 +- .../data/test_negf/test_negf_run/graphene.xyz | 50 ++++++ .../{input_negf.json => negf_chain.json} | 2 +- .../test_negf_run/negf_graphene.json | 80 +++++++++ dptb/tests/test_negf_kmesh_sampling.py | 157 ++++++++++++++++++ dptb/tests/test_negf_run.py | 70 +++++++- dptb/tests/test_tbtrans_init.py | 34 ++-- 7 files changed, 370 insertions(+), 25 deletions(-) create mode 100644 dptb/tests/data/test_negf/test_negf_run/graphene.xyz rename dptb/tests/data/test_negf/test_negf_run/{input_negf.json => negf_chain.json} (98%) create mode 100644 dptb/tests/data/test_negf/test_negf_run/negf_graphene.json create mode 100644 dptb/tests/test_negf_kmesh_sampling.py diff --git a/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json b/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json index 3782cd39..f4c232c8 100644 --- a/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json +++ b/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json @@ -1,7 +1,7 @@ { "task_options": { "task": "negf", - "scf": true, + "scf": false, "block_tridiagonal": false, "ele_T": 300, "unit": "Hartree", diff --git a/dptb/tests/data/test_negf/test_negf_run/graphene.xyz b/dptb/tests/data/test_negf/test_negf_run/graphene.xyz new file mode 100644 index 00000000..092b6a2a --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_run/graphene.xyz @@ -0,0 +1,50 @@ + 48 +Lattice="20.00000000 0.00000000 0.00000000 0.00000000 4.92000008 0.00000000 0.00000000 0.00000000 25.56479931 " nsc="1 1 1" pbc="T T T" boundary_condition="PERIODIC PERIODIC PERIODIC PERIODIC PERIODIC PERIODIC" +C 10.00000000 1.23000002 4.97094738 +C 10.00000000 3.69000006 4.97094738 +C 10.00000000 0.00000000 5.68105233 +C 10.00000000 2.46000004 5.68105233 +C 10.00000000 0.00000000 7.10134745 +C 10.00000000 2.46000004 7.10134745 +C 10.00000000 1.23000002 7.81145215 +C 10.00000000 3.69000006 7.81145215 +C 10.00000000 1.23000002 0.71014750 +C 10.00000000 3.69000006 0.71014750 +C 10.00000000 0.00000000 1.42025245 +C 10.00000000 2.46000004 1.42025245 +C 10.00000000 0.00000000 2.84054756 +C 10.00000000 2.46000004 2.84054756 +C 10.00000000 1.23000002 3.55065227 +C 10.00000000 3.69000006 3.55065227 +C 10.00000000 1.23000002 9.23174727 +C 10.00000000 3.69000006 9.23174727 +C 10.00000000 0.00000000 9.94185222 +C 10.00000000 2.46000004 9.94185222 +C 10.00000000 0.00000000 11.36214733 +C 10.00000000 2.46000004 11.36214733 +C 10.00000000 1.23000002 12.07225204 +C 10.00000000 3.69000006 12.07225204 +C 10.00000000 1.23000002 13.49254715 +C 10.00000000 3.69000006 13.49254715 +C 10.00000000 0.00000000 14.20265210 +C 10.00000000 2.46000004 14.20265210 +C 10.00000000 0.00000000 15.62294722 +C 10.00000000 2.46000004 15.62294722 +C 10.00000000 1.23000002 16.33305192 +C 10.00000000 3.69000006 16.33305192 +C 10.00000000 1.23000002 17.75334703 +C 10.00000000 3.69000006 17.75334703 +C 10.00000000 0.00000000 18.46345199 +C 10.00000000 2.46000004 18.46345199 +C 10.00000000 0.00000000 19.88374710 +C 10.00000000 2.46000004 19.88374710 +C 10.00000000 1.23000002 20.59385180 +C 10.00000000 3.69000006 20.59385180 +C 10.00000000 1.23000002 22.01414692 +C 10.00000000 3.69000006 22.01414692 +C 10.00000000 0.00000000 22.72425187 +C 10.00000000 2.46000004 22.72425187 +C 10.00000000 0.00000000 24.14454699 +C 10.00000000 2.46000004 24.14454699 +C 10.00000000 1.23000002 24.85465169 +C 10.00000000 3.69000006 24.85465169 \ No newline at end of file diff --git a/dptb/tests/data/test_negf/test_negf_run/input_negf.json b/dptb/tests/data/test_negf/test_negf_run/negf_chain.json similarity index 98% rename from dptb/tests/data/test_negf/test_negf_run/input_negf.json rename to dptb/tests/data/test_negf/test_negf_run/negf_chain.json index 480dc27d..ec34ee80 100644 --- a/dptb/tests/data/test_negf/test_negf_run/input_negf.json +++ b/dptb/tests/data/test_negf/test_negf_run/negf_chain.json @@ -1,7 +1,7 @@ { "task_options": { "task": "negf", - "scf": true, + "scf": false, "block_tridiagonal": false, "ele_T": 300, "unit": "Hartree", diff --git a/dptb/tests/data/test_negf/test_negf_run/negf_graphene.json b/dptb/tests/data/test_negf/test_negf_run/negf_graphene.json new file mode 100644 index 00000000..eba9ba43 --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_run/negf_graphene.json @@ -0,0 +1,80 @@ +{ + "task_options": + { + "task": "negf", + "scf": false, + "block_tridiagonal": false, + "ele_T": 500, + "unit": "Hartree", + "scf_options":{ + "mode": "PDIIS", + "mixing_period": 3, + "step_size": 0.05, + "n_history": 6, + "abs_err": 1e-6, + "rel_err": 1e-4, + "max_iter": 100 + }, + "stru_options":{ + "pbc":[false, true, false], + "kmesh":[1,3,1], + "gamma_center": true, + "time_reversal_symmetry": true, + "device":{ + "id":"16-32", + "sort": true + }, + "lead_L":{ + "id":"0-16", + "voltage":0.0 + }, + "lead_R":{ + "id":"32-48", + "voltage":0.0 + } + }, + "poisson_options": { + "solver": "fmm", + "err": 1e-5 + }, + "sgf_solver": "Sancho-Rubio", + "espacing": 0.2, + "emin": -5, + "emax": 5, + "e_fermi": -13.638589859008789, + "density_options":{ + "method": "Ozaki" + }, + "eta_lead":1e-4, + "eta_device":1e-5, + "out_dos": false, + "out_tc": true, + "out_ldos": false, + "out_current_nscf": false, + "out_density": false, + "out_lcurrent": false + }, + "common_options": { + "onsitemode": "none", + "onsite_cutoff": 2.0, + "bond_cutoff": 2.0, + "env_cutoff": 2.0, + "atomtype": ["C"], + "proj_atom_neles": {"C": 1}, + "proj_atom_anglr_m": { + "C": ["2s"] + } + }, + "model_options": { + "sknetwork": { + "sk_hop_nhidden": 1, + "sk_onsite_nhidden": 1 + }, + "skfunction": { + "sk_cutoff": 1.6, + "sk_decay_w": 0.3, + "skformula": "powerlaw" + } + + } +} \ No newline at end of file diff --git a/dptb/tests/test_negf_kmesh_sampling.py b/dptb/tests/test_negf_kmesh_sampling.py new file mode 100644 index 00000000..30a6d29f --- /dev/null +++ b/dptb/tests/test_negf_kmesh_sampling.py @@ -0,0 +1,157 @@ +from dptb.entrypoints import run +import pytest +import torch +import numpy as np +from dptb.utils.make_kpoints import kmesh_sampling_negf + + +@pytest.fixture(scope='session', autouse=True) +def root_directory(request): + """ + :return: + """ + return str(request.config.rootdir) + + +def test_negf_ksampling(root_directory): + + #-------- 1D ksampling----------- + + ## even meshgrid + meshgrid = [1,4,1] + ### Gamma center + is_gamma_center = True + is_time_reversal = True + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp - np.array([[0. , 0. , 0. ],[0. , 0.25, 0. ],[0. , 0.5 , 0. ]])).max()<1e-5 + assert abs(wk - np.array([0.25, 0.5 , 0.25])).max()<1e-5 + + is_time_reversal = False + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp - np.array([[0., 0., 0.],[0., 0.25, 0.],[0., 0.5, 0.],[0., 0.75, 0.]])).max()<1e-5 + assert abs(wk - np.array([0.25, 0.25 ,0.25, 0.25])).max()<1e-5 + + ### Monkhorst-Packing sampling + is_gamma_center = False + is_time_reversal = True + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + print(kp) + assert abs(kp-np.array([[0. , 0.125 , 0. ],[0. , 0.375, 0. ]])).max()<1e-5 + assert abs(wk-np.array([0.5, 0.5])).max()<1e-5 + + is_time_reversal = False + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp-np.array([[ 0., -0.375, 0.],[ 0., -0.125, 0.],[ 0.,0.125,0.],[ 0.,0.375, 0.]])).max()<1e-5 + assert abs(wk - np.array([0.25, 0.25, 0.25, 0.25])).max()<1e-5 + + ## odd meshgrid + meshgrid = [1,5,1] + ### Gamma center + is_gamma_center = True + is_time_reversal = True + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp - np.array([[0., 0., 0.],[0., 0.2, 0. ],[0., 0.4 , 0.]])).max()<1e-5 + assert abs(wk - np.array([0.2, 0.4, 0.4])).max()<1e-5 + + is_time_reversal = False + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp - np.array([[0., 0., 0.],[0., 0.2, 0. ],[0., 0.4 , 0.],[0., 0.6 , 0.],[0., 0.8 , 0.]])).max()<1e-5 + assert abs(wk - np.array([0.2, 0.2 ,0.2, 0.2, 0.2])).max()<1e-5 + + ### Monkhorst-Packing sampling + is_gamma_center = False + is_time_reversal = True + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp-np.array([[0. , 0.0 ,0 ],[0. , 0.2 ,0 ],[0., 0.4, 0 ]])).max()<1e-5 + assert abs(wk-np.array([0.2, 0.4, 0.4])).max()<1e-5 + + is_time_reversal = False + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp-np.array([[ 0., -0.4, 0.],[ 0., -0.2, 0.],[ 0.,0.0,0.],[ 0.,0.2, 0.],[ 0.,0.4,0.]])).max()<1e-5 + assert abs(wk - np.array([0.2, 0.2, 0.2, 0.2, 0.2])).max()<1e-5 + + #-------- 1D ksampling----------- + + + + + #-------- 2D ksampling----------- + ## even meshgrid + meshgrid = [2,2,1] + ### Gamma center + is_gamma_center = True + is_time_reversal = True + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp - np.array([[0. , 0. , 0. ],[0. , 0.5, 0. ],[0.5 , 0. , 0. ],[0.5 , 0.5 , 0. ]])).max()<1e-5 + assert abs(wk - np.array([0.25, 0.25 , 0.25, 0.25])).max()<1e-5 + + is_time_reversal = False + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp - np.array([[0. , 0. , 0. ],[0. , 0.5, 0. ],[0.5 , 0. , 0. ],[0.5 , 0.5 , 0. ]])).max()<1e-5 + assert abs(wk - np.array([0.25, 0.25 , 0.25, 0.25])).max()<1e-5 + + ### Monkhorst-Packing sampling + is_gamma_center = False + is_time_reversal = True + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + print(kp) + assert abs(kp - np.array([[0.25, -0.25, 0. ],[0.25, 0.25, 0. ]])).max()<1e-5 + assert abs(wk - np.array([0.5,0.5])).max()<1e-5 + + is_time_reversal = False + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp - np.array([[ -0.25, -0.25, 0. ],[ -0.25, 0.25, 0. ],[ 0.25, -0.25, 0. ],[ 0.25,0.25, 0. ]])).max()<1e-5 + assert abs(wk - np.array([0.25, 0.25, 0.25, 0.25])).max()<1e-5 + + ## odd meshgrid + meshgrid = [3,3,1] + ### Gamma center + is_gamma_center = True + is_time_reversal = True + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp - np.array([[0. , 0. , 0. ], + [0. , 0.33333333, 0. ], + [0.33333333, 0. , 0. ], + [0.33333333, 0.33333333, 0. ], + [0.33333333, 0.66666667, 0. ]])).max()<1e-5 + assert abs(wk - np.array([0.11111111, 0.22222222, 0.22222222, 0.22222222, 0.22222222])).max()<1e-5 + + is_time_reversal = False + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp - np.array([[0. , 0. , 0. ], + [0. , 0.33333333, 0. ], + [0. , 0.66666667, 0. ], + [0.33333333, 0. , 0. ], + [0.33333333, 0.33333333, 0. ], + [0.33333333, 0.66666667, 0. ], + [0.66666667, 0. , 0. ], + [0.66666667, 0.33333333, 0. ], + [0.66666667, 0.66666667, 0. ]])).max()<1e-5 + assert abs(wk - np.array([0.11111111, 0.11111111, 0.11111111, 0.11111111, 0.11111111, + 0.11111111, 0.11111111, 0.11111111, 0.11111111])).max()<1e-5 + + ### Monkhorst-Packing sampling + is_gamma_center = False + is_time_reversal = True + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + print(kp) + assert abs(kp - np.array([[ 0. , 0. , 0. ], + [ 0. , 0.33333333, 0. ], + [ 0.33333333, -0.33333333, 0. ], + [ 0.33333333, 0. , 0. ], + [ 0.33333333, 0.33333333, 0. ]])).max()<1e-5 + assert abs(wk - np.array([0.11111111, 0.22222222, 0.22222222, 0.22222222, 0.22222222])).max()<1e-5 + + is_time_reversal = False + kp , wk = kmesh_sampling_negf(meshgrid, is_gamma_center, is_time_reversal) + assert abs(kp - np.array([[-0.33333333, -0.33333333, 0. ], + [-0.33333333, 0. , 0. ], + [-0.33333333, 0.33333333, 0. ], + [ 0. , -0.33333333, 0. ], + [ 0. , 0. , 0. ], + [ 0. , 0.33333333, 0. ], + [ 0.33333333, -0.33333333, 0. ], + [ 0.33333333, 0. , 0. ], + [ 0.33333333, 0.33333333, 0. ]])).max()<1e-5 + assert abs(wk - np.array([0.11111111, 0.11111111, 0.11111111, 0.11111111, 0.11111111, + 0.11111111, 0.11111111, 0.11111111, 0.11111111])).max()<1e-5 \ No newline at end of file diff --git a/dptb/tests/test_negf_run.py b/dptb/tests/test_negf_run.py index a8840e4a..61187d1f 100644 --- a/dptb/tests/test_negf_run.py +++ b/dptb/tests/test_negf_run.py @@ -1,6 +1,7 @@ from dptb.entrypoints import run import pytest import torch +import numpy as np @pytest.fixture(scope='session', autouse=True) @@ -12,19 +13,78 @@ def root_directory(request): # NEGF calculaion in 1D carbon chain with zero-bias transmission 1 G0 -def test_negf_run(root_directory): - INPUT_file = root_directory +"/dptb/tests/data/test_negf/test_negf_run/input_negf.json" - output = root_directory +"/dptb/tests/data/test_negf/test_negf_run/out_negf" +def test_negf_run_chain(root_directory): + INPUT_file = root_directory +"/dptb/tests/data/test_negf/test_negf_run/negf_chain.json" + output = root_directory +"/dptb/tests/data/test_negf/test_negf_run/out_negf_chain" checkfile = root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json' structure = root_directory +"/dptb/tests/data/test_negf/test_negf_run/chain.vasp" run(INPUT=INPUT_file,init_model=checkfile,output=output,run_sk=True,structure=structure,\ log_level=5,log_path=output+"/test.log",use_correction=False) - negf_results = torch.load(output+"/results/negf.k0.out.pth") - trans = negf_results['TC'] + negf_results = torch.load(output+"/results/negf.out.pth") + trans = negf_results['T_avg'] assert(abs(trans[int(len(trans)/2)]-1)<1e-5) #compare with calculated transmission at efermi +# NEGF calculation in 2D graphene with zero-bias and multiple kpoints + +def test_negf_run(root_directory): + INPUT_file = root_directory +"/dptb/tests/data/test_negf/test_negf_run/negf_graphene.json" + output = root_directory +"/dptb/tests/data/test_negf/test_negf_run/out_negf_graphene" + checkfile = root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json' + structure = root_directory +"/dptb/tests/data/test_negf/test_negf_run/graphene.xyz" + + run(INPUT=INPUT_file,init_model=checkfile,output=output,run_sk=True,structure=structure,\ + log_level=5,log_path=output+"/test.log",use_correction=False) + + negf_results = torch.load(output+"/results/negf.out.pth") + + k_standard = np.array([[0. , 0. , 0.], [0. , 0.33333333, 0.]]) + k = negf_results['k'] + assert(abs(k-k_standard).max()<1e-5) #compare with calculated kpoints + + wk_standard = np.array([0.3333333333333333, 0.6666666666666666]) + wk = np.array(negf_results['wk']) + assert abs(wk-wk_standard).max()<1e-5 #compare with calculated weight + + + T_k0 = negf_results['T_k'][str(negf_results['k'][0])] + T_k0_standard = [2.2307e-18, 7.0694e-18, 2.4631e-17, 9.6304e-17, 4.3490e-16, 2.3676e-15, + 1.6641e-14, 1.7068e-13, 3.3234e-12, 2.8054e-10, 9.9964e-01, 9.9985e-01, + 9.9989e-01, 9.9991e-01, 9.9991e-01, 9.9992e-01, 9.9992e-01, 9.9992e-01, + 9.9991e-01, 9.9987e-01, 4.0658e-08, 5.7304e-10, 8.4808e-11, 2.9762e-11, + 1.8432e-11, 1.8431e-11, 2.9762e-11, 8.4805e-11, 5.7300e-10, 4.0650e-08, + 9.9987e-01, 9.9991e-01, 9.9992e-01, 9.9992e-01, 9.9992e-01, 9.9991e-01, + 9.9991e-01, 9.9989e-01, 9.9985e-01, 9.9964e-01, 2.8058e-10, 3.3236e-12, + 1.7069e-13, 1.6642e-14, 2.3677e-15, 4.3491e-16, 9.6308e-17, 2.4632e-17, + 7.0696e-18, 2.2308e-18] + T_k0_standard = torch.tensor(T_k0_standard) + assert abs(T_k0-T_k0_standard).max()<1e-4 + + T_k1 = negf_results['T_k'][str(negf_results['k'][1])] + T_k1_standard = [3.4867e-19, 1.0166e-18, 3.2013e-18, 1.1041e-17, 4.2506e-17, 1.8749e-16, + 9.8430e-16, 6.5273e-15, 6.0546e-14, 9.6364e-13, 4.5495e-11, 3.3900e-07, + 9.9983e-01, 9.9988e-01, 9.9990e-01, 1.9996e+00, 1.9998e+00, 1.9998e+00, + 1.9998e+00, 1.9998e+00, 1.9998e+00, 9.9992e-01, 9.9992e-01, 9.9992e-01, + 9.9992e-01, 9.9992e-01, 9.9992e-01, 9.9992e-01, 9.9992e-01, 1.9998e+00, + 1.9998e+00, 1.9998e+00, 1.9998e+00, 1.9998e+00, 1.9996e+00, 9.9990e-01, + 9.9988e-01, 9.9983e-01, 3.3921e-07, 4.5502e-11, 9.6372e-13, 6.0549e-14, + 6.5277e-15, 9.8436e-16, 1.8749e-16, 4.2507e-17, 1.1042e-17, 3.2014e-18, + 1.0167e-18, 3.4868e-19] + T_k1_standard = torch.tensor(T_k1_standard) + assert abs(T_k1-T_k1_standard).max()<1e-4 + T_avg = negf_results['T_avg'] + T_avg_standard = [9.7602e-19, 3.0342e-18, 1.0345e-17, 3.9462e-17, 1.7330e-16, 9.1420e-16, + 6.2031e-15, 6.1245e-14, 1.1482e-12, 9.4156e-11, 3.3321e-01, 3.3328e-01, + 9.9985e-01, 9.9989e-01, 9.9990e-01, 1.6664e+00, 1.6665e+00, 1.6665e+00, + 1.6665e+00, 1.6665e+00, 1.3332e+00, 6.6661e-01, 6.6661e-01, 6.6661e-01, + 6.6661e-01, 6.6661e-01, 6.6661e-01, 6.6661e-01, 6.6661e-01, 1.3332e+00, + 1.6665e+00, 1.6665e+00, 1.6665e+00, 1.6665e+00, 1.6664e+00, 9.9990e-01, + 9.9989e-01, 9.9985e-01, 3.3328e-01, 3.3321e-01, 9.4171e-11, 1.1482e-12, + 6.1249e-14, 6.2035e-15, 9.1425e-16, 1.7331e-16, 3.9464e-17, 1.0345e-17, + 3.0343e-18, 9.7605e-19] + T_avg_standard = torch.tensor(T_avg_standard) + assert abs(T_avg-T_avg_standard).max()<1e-4 #compare with calculated transmission at efermi diff --git a/dptb/tests/test_tbtrans_init.py b/dptb/tests/test_tbtrans_init.py index 7bb49009..390e16f9 100644 --- a/dptb/tests/test_tbtrans_init.py +++ b/dptb/tests/test_tbtrans_init.py @@ -80,27 +80,25 @@ def test_tbtrans_init(root_directory): G_eigs = G_eigs.apply.array.eigh() -Ef M_eigs = M_eigs.apply.array.eigh() -Ef - G_eigs_right = np.array([[-19.95362763, -17.10774579, -17.10774579, -16.9118761 , - -11.20609829, -10.01050689, -10.01050689, -8.11443067, - -8.11443067, -7.60442045, -6.6550603 , -3.79211664, - -3.79211663, -3.22863532, -3.22863532, -3.17535758, - 3.18703263, 3.24031037, 3.24031037, 6.45306332, - 7.70583557, 7.91107113, 10.91058699, 10.91058699, - 23.64785516, 23.64785516, 28.30755414, 28.30755428, - 28.65719263, 30.78452851, 33.25399887, 33.25399887]]) - - M_eigs_right = np.array([[-18.69653568, -18.69653568, -16.91187582, -16.91187582, - -11.20609828, -11.20609828, -7.44726991, -7.44726991, - -6.65506047, -6.65506047, -5.79252308, -5.79252308, - -5.2193769 , -5.2193769 , -3.17535758, -3.17535758, - 3.18703263, 3.18703263, 5.84906816, 5.84906816, - 7.74726616, 7.74726616, 7.91107121, 7.91107121, - 28.12912079, 28.12912079, 28.65719227, 28.65719227, - 29.54182975, 29.54182975, 30.78452877, 30.78452877]]) + G_eigs_right = np.array([[-19.95431228, -17.10836511, -17.10836511, -16.91249093, -11.20658215, + -10.01096331, -10.01096331, -8.11484357, -8.11484357, -7.60482165, + -6.65543971, -3.79243033, -3.79243032, -3.22893608, -3.22893608, + -3.17565711, 3.18687914, 3.2401581, 3.2401581, 6.4529848, + 7.70578579, 7.91102607 , 10.91061078 , 10.91061078 , 23.6481713, + 23.6481713, 28.30797724 , 28.30797738 , 28.65762375 , 30.78500847, + 33.2545355 , 33.2545355 ]]) + + M_eigs_right = np.array([[-18.69719147, -18.69719147, -16.91249065, -16.91249065, -11.20658214, + -11.20658214, -7.4476675, -7.4476675 , -6.65543987 ,-6.65543987, + -5.79288269, -5.79288269, -5.21972335 , -5.21972335 , -3.17565711, + -3.17565711, 3.18687914, 3.18687914 , 5.84897577 , 5.84897577, + 7.74721734, 7.74721734, 7.91102615 , 7.91102615 , 28.12953979, + 28.12953979, 28.65762339, 28.65762339, 29.54228118, 29.54228118, + 30.78500872, 30.78500872]]) + assert (G_eigs[0]-G_eigs_right[0]).max()<1e-5 assert (M_eigs[0]-M_eigs_right[0]).max()<1e-5 - From 9ae78d46dfe66b3c89a688cc6f77d1ac71b4d0fb Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 16 Mar 2024 11:05:29 +0800 Subject: [PATCH 078/209] update for ksum in poisson_dev --- dptb/negf/device_property.py | 2 +- dptb/negf/recursive_green_cal.py | 2 +- dptb/postprocess/NEGF.py | 146 +++++++++++++++++-------------- dptb/postprocess/tbtrans_init.py | 2 +- dptb/utils/argcheck.py | 6 +- dptb/utils/make_kpoints.py | 78 ++++++++++++++++- 6 files changed, 165 insertions(+), 71 deletions(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index 7a9a91ee..7261e263 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -350,7 +350,7 @@ def _cal_ldos_(self): ldos = torch.stack([ldos[accmap[i]:accmap[i+1]].sum() for i in range(len(accmap)-1)]) # return ldos*2 - return ldos #temporarily return the ldos without spin degeneracy + return ldos*2 def _cal_local_current_(self): '''calculate the local current between different atoms diff --git a/dptb/negf/recursive_green_cal.py b/dptb/negf/recursive_green_cal.py index 39a3d45c..6fe2b8bc 100644 --- a/dptb/negf/recursive_green_cal.py +++ b/dptb/negf/recursive_green_cal.py @@ -249,7 +249,7 @@ def recursive_gf(energy, hl, hd, hu, sd, su, sl, left_se, right_se, seP=None, ch """ shift_energy = energy + chemiPot - shift_energy = torch.scalar_tensor(shift_energy, dtype=torch.complex128) + # shift_energy = torch.scalar_tensor(shift_energy, dtype=torch.complex128) temp_mat_d_list = [hd[i] * 1. for i in range(len(hd))] temp_mat_l_list = [hl[i] * 1. for i in range(len(hl))] diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index d48ac235..836a2c61 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -17,7 +17,7 @@ import os from dptb.utils.tools import j_must_have import numpy as np -from dptb.utils.make_kpoints import kmesh_sampling +from dptb.utils.make_kpoints import kmesh_sampling_negf import logging from dptb.negf.poisson_init import Grid,Interface3D,Gate,Dielectric @@ -50,10 +50,24 @@ def __init__(self, apiHrk, run_opt, jdata): self.e_fermi = jdata["e_fermi"] self.stru_options = j_must_have(jdata, "stru_options") self.pbc = self.stru_options["pbc"] + + # check the consistency of the kmesh and pbc + assert len(self.pbc) == 3, "pbc should be a list of length 3" + for i in range(3): + if self.pbc[i] == False and self.jdata["stru_options"]["kmesh"][i] > 1: + raise ValueError("kmesh should be 1 for non-periodic direction") + elif self.pbc[i] == False and self.jdata["stru_options"]["kmesh"][i] == 0: + self.jdata["stru_options"]["kmesh"][i] = 1 + log.warning(msg="kmesh should be set to 1 for non-periodic direction! Automatically Setting kmesh to 1 in direction {}.".format(i)) + elif self.pbc[i] == True and self.jdata["stru_options"]["kmesh"][i] == 0: + raise ValueError("kmesh should be > 0 for periodic direction") + if not any(self.pbc): - self.kpoints = np.array([[0,0,0]]) + self.kpoints,self.wk = np.array([[0,0,0]]),np.array([1.]) else: - self.kpoints = kmesh_sampling(self.jdata["stru_options"]["kmesh"]) + self.kpoints,self.wk = kmesh_sampling_negf(self.jdata["stru_options"]["kmesh"], + self.jdata["stru_options"]["gamma_center"], + self.jdata["stru_options"]["time_reversal_symmetry"]) self.unit = jdata["unit"] self.scf = jdata["scf"] @@ -99,7 +113,7 @@ def __init__(self, apiHrk, run_opt, jdata): # number of orbitals on atoms in device region self.device_atom_norbs = self.negf_hamiltonian.atom_norbs[self.negf_hamiltonian.proj_device_id[0]:self.negf_hamiltonian.proj_device_id[1]] - np.save(self.results_path+"/device_atom_norbs.npy",self.device_atom_norbs) + # np.save(self.results_path+"/device_atom_norbs.npy",self.device_atom_norbs) # geting the output settings self.out_tc = jdata["out_tc"] @@ -216,10 +230,10 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): while max_diff_phi > err: # update Hamiltonian by modifying onsite energy with potential atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) - np.save(self.results_path+"/atom_gridpoint_index.npy",atom_gridpoint_index) + # np.save(self.results_path+"/atom_gridpoint_index.npy",atom_gridpoint_index) self.potential_at_atom = interface_poisson.phi[atom_gridpoint_index] self.potential_at_orb = torch.cat([torch.full((norb,), p) for p, norb in zip(self.potential_at_atom, self.device_atom_norbs)]) - torch.save(self.potential_at_orb, self.results_path+"/potential_at_orb.pth") + # torch.save(self.potential_at_orb, self.results_path+"/potential_at_orb.pth") self.negf_compute(scf_require=True,Vbias=self.potential_at_orb) @@ -240,10 +254,11 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # TODO: check the sign of free_charge # TODO: check the spin degenracy # TODO: add k summation operation - interface_poisson.free_charge[atom_gridpoint_index] =\ - np.real(self.free_charge[str(self.kpoints[0])].numpy()) + free_charge_allk = torch.zeros_like(torch.tensor(self.device_atom_norbs),dtype=torch.complex128) + for ik,k in enumerate(self.kpoints): + free_charge_allk += np.real(self.free_charge[str(k)].numpy()) * self.wk[ik] + interface_poisson.free_charge[atom_gridpoint_index] = free_charge_allk - interface_poisson.phi_old = interface_poisson.phi.copy() max_diff_phi = interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi_old-interface_poisson.phi) @@ -281,10 +296,24 @@ def negf_compute(self,scf_require=False,Vbias=None): assert scf_require is not None + + self.out['k']=[];self.out['wk']=[] + if hasattr(self, "uni_grid"): self.out["uni_grid"] = self.uni_grid for ik, k in enumerate(self.kpoints): - self.out = {} - self.out["gtrans"] = {}; self.out['uni_grid'] = self.uni_grid; self.out["k"] = k + + + # output kpoints information + if ik == 0: + log.info(msg="------ k-point for NEGF -----\n") + log.info(msg="Gamma Center: {0}".format(self.jdata["stru_options"]["gamma_center"])) + log.info(msg="Time Reversal: {0}".format(self.jdata["stru_options"]["time_reversal_symmetry"])) + log.info(msg="k-points Num: {0}".format(len(self.kpoints))) + log.info(msg="k-points weights: {0}".format(self.wk)) + log.info(msg="--------------------------------\n") + + self.out['k'].append(k) + self.out['wk'].append(self.wk[ik]) self.free_charge.update({str(k):torch.zeros_like(torch.tensor(self.device_atom_norbs),dtype=torch.complex128)}) log.info(msg="Properties computation at k = [{:.4f},{:.4f},{:.4f}]".format(float(k[0]),float(k[1]),float(k[2]))) @@ -312,14 +341,14 @@ def negf_compute(self,scf_require=False,Vbias=None): free_charge = self.free_charge) else: # TODO: add Ozaki support for NanoTCAD-style SCF - raise ValueError("Ozaki method is not supported for Poisson-NEGF SCF in this version.") + raise ValueError("Ozaki method does not support Poisson-NEGF SCF in this version.") # in non-scf case, computing properties in uni_gird else: if hasattr(self, "uni_grid"): # dE = abs(self.uni_grid[1] - self.uni_grid[0]) - output_freq = int(len(self.uni_grid)/5) + output_freq = int(len(self.uni_grid)/10) for ie, e in enumerate(self.uni_grid): if ie % output_freq == 0: @@ -342,24 +371,33 @@ def negf_compute(self,scf_require=False,Vbias=None): ) # self.out[str(ll)+"_se"][str(e.numpy())] = getattr(self.deviceprop, ll).se - gtrans = self.deviceprop.cal_green_function( + self.deviceprop.cal_green_function( energy=e, kpoint=k, eta_device=self.jdata["eta_device"], block_tridiagonal=self.block_tridiagonal, Vbias=Vbias ) - self.out["gtrans"][str(e.numpy())] = gtrans + # self.out["gtrans"][str(e.numpy())] = gtrans if self.out_dos: - prop = self.out.setdefault("DOS", []) - prop.append(self.compute_DOS(k)) + # prop = self.out.setdefault("DOS", []) + # prop.append(self.compute_DOS(k)) + prop = self.out.setdefault('DOS', {}) + propk = prop.setdefault(str(k), []) + propk.append(self.compute_DOS(k)) if self.out_tc or self.out_current_nscf: - prop = self.out.setdefault("TC", []) - prop.append(self.compute_TC(k)) + # prop = self.out.setdefault("TC", []) + # prop.append(self.compute_TC(k)) + prop = self.out.setdefault('T_k', {}) + propk = prop.setdefault(str(k), []) + propk.append(self.compute_TC(k)) if self.out_ldos: - prop = self.out.setdefault("LDOS", []) - prop.append(self.compute_LDOS(k)) + # prop = self.out['LDOS'].setdefault(str(k), []) + # prop.append(self.compute_LDOS(k)) + prop = self.out.setdefault('LDOS', {}) + propk = prop.setdefault(str(k), []) + propk.append(self.compute_LDOS(k)) # over energy loop in uni_gird @@ -368,23 +406,26 @@ def negf_compute(self,scf_require=False,Vbias=None): if self.out_density or self.out_potential: if self.density_options["method"] == "Ozaki": - self.out["DM_eq"], self.out["DM_neq"] = self.compute_density_Ozaki(k,Vbias) + prop_DM_eq = self.out.setdefault('DM_eq', {}) + prop_DM_neq = self.out.setdefault('DM_neq', {}) + prop_DM_eq[str(k)], prop_DM_neq[str(k)] = self.compute_density_Ozaki(k,Vbias) elif self.density_options["method"] == "Fiori": - log.warning("Fiori method is not supported for output density in this version.") + log.warning("Fiori method does not support output density in this version.") else: raise ValueError("Unknown method for density calculation.") if self.out_potential: pass if self.out_dos: - self.out["DOS"] = torch.stack(self.out["DOS"]) + self.out["DOS"][str(k)] = torch.stack(self.out["DOS"][str(k)]) if self.out_tc or self.out_current_nscf: - self.out["TC"] = torch.stack(self.out["TC"]) - if self.out_current_nscf: - self.out["BIAS_POTENTIAL_NSCF"], self.out["CURRENT_NSCF"] = self.compute_current_nscf(k, self.uni_grid, self.out["TC"]) + self.out["T_k"][str(k)] = torch.stack(self.out["T_k"][str(k)]) + # if self.out_current_nscf: + # self.out["BIAS_POTENTIAL_NSCF"], self.out["CURRENT_NSCF"] = self.compute_current_nscf(k, self.uni_grid, self.out["TC"]) # computing properties that are not functions of E (improvement can be made here in properties related to integration of energy window of fermi functions) if self.out_current: pass + # TODO: check the following code for multiple k points calculation if self.out_lcurrent: lcurrent = 0 log.info(msg="computing local current at k = [{:.4f},{:.4f},{:.4f}]".format(float(k[0]),float(k[1]),float(k[2]))) @@ -408,13 +449,20 @@ def negf_compute(self,scf_require=False,Vbias=None): ) lcurrent += self.int_weight[i] * self.compute_lcurrent(k) - self.out["LOCAL_CURRENT"] = lcurrent + prop_local_current = self.out.setdefault('LOCAL_CURRENT', {}) + prop_local_current[str(k)] = lcurrent - - torch.save(self.out, self.results_path+"/negf.k{}.out.pth".format(ik)) - # plotting + if scf_require==False: + self.out["k"] = np.array(self.out["k"]) + self.out['T_avg'] = torch.tensor(self.out['wk']) @ torch.stack(list(self.out["T_k"].values())) + # TODO:check the following code for multiple k points calculation + if self.out_current_nscf: + self.out["BIAS_POTENTIAL_NSCF"], self.out["CURRENT_NSCF"] = self.compute_current_nscf(self.uni_grid, self.out["T_avg"]) + torch.save(self.out, self.results_path+"/negf.out.pth") + + def get_grid(self,grid_info,structase): @@ -456,7 +504,7 @@ def compute_TC(self, kpoint): def compute_LDOS(self, kpoint): return self.deviceprop.ldos - def compute_current_nscf(self, kpoint, ee, tc): + def compute_current_nscf(self, ee, tc): return self.deviceprop._cal_current_nscf_(ee, tc) def compute_density_Ozaki(self, kpoint,Vbias): @@ -475,37 +523,3 @@ def compute_lcurrent(self, kpoint): def SCF(self): pass - - # def compute_density_Fiori(self,e,kpoint,dE,deviceprop,device_atom_norbs,potential_at_atom,free_charge): - - # tx, ty = deviceprop.g_trans.shape - # lx, ly = deviceprop.lead_L.se.shape - # rx, ry = deviceprop.lead_R.se.shape - # x0 = min(lx, tx) - # x1 = min(rx, ty) - - # gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) - # gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] - # gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) - # gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] - - # A_L = torch.mm(torch.mm(deviceprop.g_trans,gammaL),deviceprop.g_trans.conj().T) - # A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) - - # # Vbias = -1 * potential_at_orb - # for Ei_index, Ei_at_atom in enumerate(-1*potential_at_atom): - # pre_orbs = sum(device_atom_norbs[:Ei_index]) - - # # electron density - # if e >= Ei_at_atom: - # for j in range(device_atom_norbs[Ei_index]): - # free_charge[str(kpoint)][Ei_index] +=\ - # 2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ - # +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))*dE - - # # hole density - # else: - # for j in range(device_atom_norbs[Ei_index]): - # free_charge[str(kpoint)][Ei_index] +=\ - # 2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ - # +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)))*dE \ No newline at end of file diff --git a/dptb/postprocess/tbtrans_init.py b/dptb/postprocess/tbtrans_init.py index 60d8f4e3..1dc8af17 100644 --- a/dptb/postprocess/tbtrans_init.py +++ b/dptb/postprocess/tbtrans_init.py @@ -602,7 +602,7 @@ def hamiltonian_get(self,allbonds:torch.tensor,hamil_block:torch.tensor,overlap_ ''' if energy_unit_option=='Hartree': - unit_constant = 1 + unit_constant = 1.0000 elif energy_unit_option=='eV': unit_constant = 13.605662285137 * 2 diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 224ef49c..d8ad8402 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -547,12 +547,16 @@ def stru_options(): doc_device = "" doc_lead_L = "" doc_lead_R = "" + doc_gamma_center="" + doc_time_reversal_symmetry="" return [ Argument("device", dict, optional=False, sub_fields=device(), doc=doc_device), Argument("lead_L", dict, optional=False, sub_fields=lead(), doc=doc_lead_L), Argument("lead_R", dict, optional=False, sub_fields=lead(), doc=doc_lead_R), Argument("kmesh", list, optional=True, default=[1,1,1], doc=doc_kmesh), - Argument("pbc", list, optional=True, default=[False, False, False], doc=doc_pbc) + Argument("pbc", list, optional=True, default=[False, False, False], doc=doc_pbc), + Argument("gamma_center", list, optional=True, default=True, doc=doc_gamma_center), + Argument("time_reversal_symmetry", list, optional=True, default=True, doc=doc_time_reversal_symmetry) ] def device(): diff --git a/dptb/utils/make_kpoints.py b/dptb/utils/make_kpoints.py index 68019170..e969f93f 100644 --- a/dptb/utils/make_kpoints.py +++ b/dptb/utils/make_kpoints.py @@ -125,6 +125,8 @@ def gamma_center(meshgrid=[1,1,1]): return kpoints + + def kmesh_sampling(meshgrid=[1,1,1], is_gamma_center=True): """ Generate k-points using Monkhorst-Pack method based on given meshgrid. The k-points are centered at Gamma point by default. @@ -139,6 +141,80 @@ def kmesh_sampling(meshgrid=[1,1,1], is_gamma_center=True): return kpoints +def kmesh_sampling_negf(meshgrid=[1,1,1], is_gamma_center=True, is_time_reversal=True): + """ Generate k-points for NEGF based on given meshgrid. Through time symmetry reduction, the number of k-points is reduced. + + """ + + if is_time_reversal: + kpoints,wk = time_symmetry_reduce(meshgrid, is_gamma_center=is_gamma_center) + + else: + kpoints = kmesh_sampling(meshgrid, is_gamma_center=is_gamma_center) + wk = np.ones(len(kpoints))/len(kpoints) + + return kpoints,wk + + +def time_symmetry_reduce(meshgrid=[1,1,1], is_gamma_center=True): + '''Reduce the number of k-points in a meshgrid by applying symmetry operations. + + For gamma centered meshgrid, k-points range from 0 to 1 in each dimension initially. + For non-gamma centered meshgrid, k-points range from -0.5 to 0.5 in each dimension initially. + + With time symmetry reduction, the number of k-points is reduced and limited to [0,0.5] in x-direction. + + Parameters + ---------- + meshgrid + The `meshgrid` parameter specifies the number of k-points in each direction. + is_gamma_center + The parameter "is_gamma_center" is a boolean value that determines whether the k-point mesh must be + centered around the gamma point (0, 0, 0) or not. + + Returns + ------- + the reduced k-points and their corresponding weights. + + ''' + + k_points = kmesh_sampling(meshgrid, is_gamma_center=is_gamma_center) + k_points_with_tr = [] + kweight = [] + + + if is_gamma_center: + k_points[k_points>0.5] = k_points[k_points>0.5] - 1 + + k_points = np.round(k_points, decimals=5) + + for kp in k_points: + if (-kp).tolist() not in k_points_with_tr: + k_points_with_tr.append(kp.tolist()) + kweight.append(1) + else: + kweight[k_points_with_tr.index((-kp).tolist())] += 1 + + k_points_with_tr = np.array(k_points_with_tr) + + # make the reduced kpoints in [0,0.5] in x-direction + if is_gamma_center: + k_points_with_tr[k_points_with_tr < 0] += 1 + else: # MP sampling + k_points_with_tr = -1 * k_points_with_tr # due to time revesal symmetry + + # sort the k-points + k_sort_indx = np.lexsort((k_points_with_tr[:, 2], k_points_with_tr[:, 1], k_points_with_tr[:, 0])) + k_points_with_tr = k_points_with_tr[k_sort_indx] + kweight = np.array(kweight)/len(k_points) # normalize the weight to one + kweight = kweight[k_sort_indx] + assert abs(kweight.sum() - 1.0) < 1e-5, "The sum of weight is not 1.0" + + return k_points_with_tr, kweight + + + + def kgrid_spacing(structase,kspacing:float,sampling='MP'): """Generate k-points based on the given k-spacing and sampling method. @@ -315,4 +391,4 @@ def vasp_kpath(structase, pathstr:str, high_sym_kpoints_dict:dict, number_in_lin xlist = np.concatenate([xlist, xlist[-1] + np.linspace(0,kdist[i],number_in_line)]) xlist_label.append(xlist[-1]) - return klist, xlist, xlist_label, klabels + return klist, xlist, xlist_label, klabels \ No newline at end of file From 8595628502c13594b501805d1c4b73cde0c49ef8 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 20 Mar 2024 20:19:20 +0800 Subject: [PATCH 079/209] add atom_cor and gird_cor consistency check --- dptb/negf/poisson_init.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index d229cb02..13205e6a 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -27,11 +27,11 @@ def __init__(self,xg,yg,zg,xa,ya,za): assert np.min(za) >= np.min(zg) and np.max(za) <= np.max(zg) self.Na = len(xa) # number of atoms - uxa = np.unique(xa);uya = np.unique(ya);uza = np.unique(za) + uxa = np.unique(xa).round(decimals=6);uya = np.unique(ya).round(decimals=6);uza = np.unique(za).round(decimals=6) # x,y,z are the coordinates of the grid points - self.xall = np.unique(np.concatenate((uxa,self.xg),0)) # unique results are sorted - self.yall = np.unique(np.concatenate((uya,self.yg),0)) - self.zall = np.unique(np.concatenate((uza,self.zg),0)) + self.xall = np.unique(np.concatenate((uxa,self.xg),0).round(decimals=3)) # unique results are sorted + self.yall = np.unique(np.concatenate((uya,self.yg),0).round(decimals=3)) + self.zall = np.unique(np.concatenate((uza,self.zg),0).round(decimals=3)) self.shape = (len(self.xall),len(self.yall),len(self.zall)) @@ -80,9 +80,9 @@ def find_atom_index(self,xa,ya,za): swap = {} for atom_index in range(self.Na): for gp_index in range(self.Np): - if xa[atom_index]==self.grid_coord[gp_index][0] and \ - ya[atom_index]==self.grid_coord[gp_index][1] and \ - za[atom_index]==self.grid_coord[gp_index][2]: + if abs(xa[atom_index]-self.grid_coord[gp_index][0])<1e-3 and \ + abs(ya[atom_index]-self.grid_coord[gp_index][1])<1e-3 and \ + abs(za[atom_index]-self.grid_coord[gp_index][2])<1e-3: swap.update({atom_index:gp_index}) return swap From 892e34568984748727e9a5d86e50d7f2e9f2eb65 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 1 Apr 2024 13:43:08 +0800 Subject: [PATCH 080/209] add loginfo for k-point in NEGF.py --- dptb/postprocess/NEGF.py | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 836a2c61..2e4a6c1b 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -68,6 +68,14 @@ def __init__(self, apiHrk, run_opt, jdata): self.kpoints,self.wk = kmesh_sampling_negf(self.jdata["stru_options"]["kmesh"], self.jdata["stru_options"]["gamma_center"], self.jdata["stru_options"]["time_reversal_symmetry"]) + log.info(msg="------ k-point for NEGF -----") + log.info(msg="Gamma Center: {0}".format(self.jdata["stru_options"]["gamma_center"])) + log.info(msg="Time Reversal: {0}".format(self.jdata["stru_options"]["time_reversal_symmetry"])) + log.info(msg="k-points Num: {0}".format(len(self.kpoints))) + if len(self.wk)<10: + log.info(msg="k-points: {0}".format(self.kpoints)) + log.info(msg="k-points weights: {0}".format(self.wk)) + log.info(msg="--------------------------------") self.unit = jdata["unit"] self.scf = jdata["scf"] @@ -230,8 +238,10 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): while max_diff_phi > err: # update Hamiltonian by modifying onsite energy with potential atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) + # print("atom_gridpoint_index",atom_gridpoint_index) # np.save(self.results_path+"/atom_gridpoint_index.npy",atom_gridpoint_index) self.potential_at_atom = interface_poisson.phi[atom_gridpoint_index] + # print([torch.full((norb,), p) for p, norb in zip(self.potential_at_atom, self.device_atom_norbs)]) self.potential_at_orb = torch.cat([torch.full((norb,), p) for p, norb in zip(self.potential_at_atom, self.device_atom_norbs)]) # torch.save(self.potential_at_orb, self.results_path+"/potential_at_orb.pth") @@ -268,6 +278,10 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): log.info(msg="Poisson-NEGF iteration: {} Potential Diff Maximum: {}\n".format(iter_count,max_diff_phi)) max_diff_list.append(max_diff_phi) + if max_diff_phi <= err: + log.info(msg="Poisson-NEGF SCF Converges Successfully!") + + if iter_count > max_iter: log.info(msg="Warning! Poisson-NEGF iteration exceeds the upper limit of iterations {}".format(int(max_iter))) profiler.stop() @@ -277,6 +291,7 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): self.poisson_out = {} self.poisson_out['potential'] = torch.tensor(interface_poisson.phi) + self.poisson_out['potential_at_atom'] = self.potential_at_atom self.poisson_out['grid_point_number'] = interface_poisson.grid.Np self.poisson_out['grid'] = torch.tensor(interface_poisson.grid.grid_coord) self.poisson_out['free_charge_at_atom'] = torch.tensor(interface_poisson.free_charge[atom_gridpoint_index]) @@ -304,13 +319,15 @@ def negf_compute(self,scf_require=False,Vbias=None): # output kpoints information - if ik == 0: - log.info(msg="------ k-point for NEGF -----\n") - log.info(msg="Gamma Center: {0}".format(self.jdata["stru_options"]["gamma_center"])) - log.info(msg="Time Reversal: {0}".format(self.jdata["stru_options"]["time_reversal_symmetry"])) - log.info(msg="k-points Num: {0}".format(len(self.kpoints))) - log.info(msg="k-points weights: {0}".format(self.wk)) - log.info(msg="--------------------------------\n") + # if ik == 0: + # log.info(msg="------ k-point for NEGF -----") + # log.info(msg="Gamma Center: {0}".format(self.jdata["stru_options"]["gamma_center"])) + # log.info(msg="Time Reversal: {0}".format(self.jdata["stru_options"]["time_reversal_symmetry"])) + # log.info(msg="k-points Num: {0}".format(len(self.kpoints))) + # if len(self.wk)<10: + # log.info(msg="k-points: {0}".format(self.kpoints)) + # log.info(msg="k-points weights: {0}".format(self.wk)) + # log.info(msg="--------------------------------") self.out['k'].append(k) self.out['wk'].append(self.wk[ik]) @@ -471,6 +488,7 @@ def get_grid(self,grid_info,structase): y_start,y_end,y_num = grid_info.get("y_range",None).split(':') yg = np.linspace(float(y_start),float(y_end),int(y_num)) + # yg = np.array([(float(y_start)+float(y_end))/2]) # TODO: temporary fix for 2D case z_start,z_end,z_num = grid_info.get("z_range",None).split(':') zg = np.linspace(float(z_start),float(z_end),int(z_num)) From c3a5fdcdc51f244a661d4d6e3809e3954421f3f1 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 1 Apr 2024 14:14:10 +0800 Subject: [PATCH 081/209] add structure for tbtrans --- .../data/test_negf/structure_tbtrans.vasp | 28 +++++++++++++++++++ .../data/test_tbtrans/structure_tbtrans.vasp | 28 +++++++++++++++++++ .../tbtrans_hBN/data/struct_unitcell.vasp | 10 +++++++ 3 files changed, 66 insertions(+) create mode 100644 dptb/tests/data/test_negf/structure_tbtrans.vasp create mode 100644 dptb/tests/data/test_tbtrans/structure_tbtrans.vasp create mode 100644 examples/tbtrans_hBN/data/struct_unitcell.vasp diff --git a/dptb/tests/data/test_negf/structure_tbtrans.vasp b/dptb/tests/data/test_negf/structure_tbtrans.vasp new file mode 100644 index 00000000..8dae0214 --- /dev/null +++ b/dptb/tests/data/test_negf/structure_tbtrans.vasp @@ -0,0 +1,28 @@ + B N + 1.0000000000000000 + 30.0000000000000000 0.0000000000000000 0.0000000000000000 + 0.0000000000000000 5.0079998999999997 0.0000000000000000 + 0.0000000000000000 -6.2599998699999997 10.8426378299999993 + B N + 10 10 +Cartesian + 15.0000000000000000 1.2519998999999999 0.7228424800000000 + 15.0000000000000000 3.7559998499999998 0.7228424800000000 + 15.0000000000000000 -0.0000000700000000 2.8913700400000000 + 15.0000000000000000 2.5039998699999999 2.8913700400000000 + 15.0000000000000000 -1.2520000499999999 5.0598976100000002 + 15.0000000000000000 1.2519998999999999 5.0598976100000002 + 15.0000000000000000 -2.5040000199999999 7.2284251800000003 + 15.0000000000000000 -0.0000000700000000 7.2284251800000003 + 15.0000000000000000 -3.7559999999999998 9.3969527399999997 + 15.0000000000000000 -1.2520000499999999 9.3969527399999997 + 15.0000000000000000 -0.0000000000000000 1.4456850900000000 + 15.0000000000000000 2.5039999499999999 1.4456850900000000 + 15.0000000000000000 -1.2519999799999999 3.6142126499999998 + 15.0000000000000000 1.2519999700000000 3.6142126499999998 + 15.0000000000000000 -2.5039999499999999 5.7827402200000000 + 15.0000000000000000 -0.0000000000000000 5.7827402200000000 + 15.0000000000000000 -3.7559999199999998 7.9512677900000002 + 15.0000000000000000 -1.2519999799999999 7.9512677900000002 + 15.0000000000000000 -5.0079998999999997 10.1197953500000004 + 15.0000000000000000 -2.5039999499999999 10.1197953500000004 diff --git a/dptb/tests/data/test_tbtrans/structure_tbtrans.vasp b/dptb/tests/data/test_tbtrans/structure_tbtrans.vasp new file mode 100644 index 00000000..45345348 --- /dev/null +++ b/dptb/tests/data/test_tbtrans/structure_tbtrans.vasp @@ -0,0 +1,28 @@ + B N + 1.0000000000000000 + 30.0000000000000000 0.0000000000000000 0.0000000000000000 + 0.0000000000000000 4.3370551300000004 0.0000000000000000 + 0.0000000000000000 0.0000000000000000 12.5199997399999994 + B N + 10 10 +Cartesian + 15.0000000000000000 2.5299488299999999 0.6259999900000000 + 15.0000000000000000 0.3614212600000000 1.8779999599999999 + 15.0000000000000000 2.5299488299999999 3.1299999399999998 + 15.0000000000000000 0.3614212600000000 4.3819999100000002 + 15.0000000000000000 2.5299488299999999 5.6339998800000002 + 15.0000000000000000 0.3614212600000000 6.8859998600000001 + 15.0000000000000000 2.5299488299999999 8.1379998300000000 + 15.0000000000000000 0.3614212600000000 9.3899998100000008 + 15.0000000000000000 2.5299488299999999 10.6419997800000008 + 15.0000000000000000 0.3614212600000000 11.8939997599999998 + 15.0000000000000000 3.9756338699999998 0.6259999900000000 + 15.0000000000000000 1.8071063100000000 1.8779999599999999 + 15.0000000000000000 3.9756338699999998 3.1299999399999998 + 15.0000000000000000 1.8071063100000000 4.3819999100000002 + 15.0000000000000000 3.9756338699999998 5.6339998800000002 + 15.0000000000000000 1.8071063100000000 6.8859998600000001 + 15.0000000000000000 3.9756338699999998 8.1379998300000000 + 15.0000000000000000 1.8071063100000000 9.3899998100000008 + 15.0000000000000000 3.9756338699999998 10.6419997800000008 + 15.0000000000000000 1.8071063100000000 11.8939997599999998 diff --git a/examples/tbtrans_hBN/data/struct_unitcell.vasp b/examples/tbtrans_hBN/data/struct_unitcell.vasp new file mode 100644 index 00000000..25c135b7 --- /dev/null +++ b/examples/tbtrans_hBN/data/struct_unitcell.vasp @@ -0,0 +1,10 @@ +h-BN +1.0 + 2.5039999485 0.0000000000 0.0000000000 + -1.2519999743 2.1685275665 0.0000000000 + 0.0000000000 0.0000000000 30.000000000 + N B + 1 1 +Direct + 0.333333343 0.666666687 0.50000000 + 0.666666627 0.333333313 0.50000000 From e57453e924ebee773078e984713dc8385c1fdcf7 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 1 Apr 2024 14:42:23 +0800 Subject: [PATCH 082/209] copy some test files from v1 --- dptb/tests/test_negf_density_Ozaki.py | 104 ++++++++++ dptb/tests/test_negf_device_property.py | 188 ++++++++++++++++++ dptb/tests/test_negf_negf_hamiltonian_init.py | 129 ++++++++++++ dptb/tests/test_negf_ozaki_res_cal.py | 13 ++ 4 files changed, 434 insertions(+) create mode 100644 dptb/tests/test_negf_density_Ozaki.py create mode 100644 dptb/tests/test_negf_device_property.py create mode 100644 dptb/tests/test_negf_negf_hamiltonian_init.py create mode 100644 dptb/tests/test_negf_ozaki_res_cal.py diff --git a/dptb/tests/test_negf_density_Ozaki.py b/dptb/tests/test_negf_density_Ozaki.py new file mode 100644 index 00000000..b0eb8de5 --- /dev/null +++ b/dptb/tests/test_negf_density_Ozaki.py @@ -0,0 +1,104 @@ +# test_negf_density_Ozaki +from dptb.negf.density import Ozaki +from dptb.negf.device_property import DeviceProperty +from dptb.v1.init_nnsk import InitSKModel +from dptb.nnops.v1.NN2HRK import NN2HRK +from dptb.nnops.v1.apihost import NNSKHost +from dptb.utils.tools import j_must_have +from dptb.utils.tools import j_loader +import numpy as np +import torch +from dptb.negf.negf_hamiltonian_init import NEGFHamiltonianInit +from ase.io import read +from dptb.utils.make_kpoints import kmesh_sampling +from dptb.negf.lead_property import LeadProperty +from dptb.utils.constants import Boltzmann, eV2J +import pytest + + + +@pytest.fixture(scope='session', autouse=True) +def root_directory(request): + """ + :return: + """ + return str(request.config.rootdir) + +def test_negf_density_Ozaki(root_directory): + + model_ckpt=root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json' + jdata = root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json" + structure=root_directory +"/dptb/tests/data/test_negf/test_negf_run/chain.vasp" + log_path=root_directory +"/dptb/tests/data/test_negf/test_negf_Device/test.log" + + apihost = NNSKHost(checkpoint=model_ckpt, config=jdata) + apihost.register_plugin(InitSKModel()) + apihost.build() + apiHrk = NN2HRK(apihost=apihost, mode='nnsk') + jdata = j_loader(jdata) + task_options = j_must_have(jdata, "task_options") + + + run_opt = { + "run_sk": True, + "init_model":model_ckpt, + "results_path":root_directory +"/dptb/tests/data/test_negf", + "structure":structure, + "log_path": log_path, + "log_level": 5, + "use_correction":False + } + + structase=read(run_opt['structure']) + results_path=run_opt.get('results_path') + kpoints= kmesh_sampling(task_options["stru_options"]["kmesh"]) + ele_T = task_options["ele_T"] + kBT = Boltzmann * ele_T / eV2J + e_fermi = task_options["e_fermi"] + + hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) + with torch.no_grad(): + struct_device, struct_leads = hamiltonian.initialize(kpoints=kpoints) + device = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=e_fermi) + device.set_leadLR( + lead_L=LeadProperty( + hamiltonian=hamiltonian, + tab="lead_L", + structure=struct_leads["lead_L"], + results_path=results_path, + e_T=ele_T, + efermi=e_fermi, + voltage=task_options["stru_options"]["lead_L"]["voltage"] + ), + lead_R=LeadProperty( + hamiltonian=hamiltonian, + tab="lead_R", + structure=struct_leads["lead_R"], + results_path=results_path, + e_T=ele_T, + efermi=e_fermi, + voltage=task_options["stru_options"]["lead_R"]["voltage"] + ) + ) + + + # check Ozaki + kpoints= kmesh_sampling(task_options["stru_options"]["kmesh"]) + density_options = j_must_have(task_options, "density_options") + + density = Ozaki(R=density_options["R"], M_cut=density_options["M_cut"], n_gauss=density_options["n_gauss"]) + + #compute_density + DM_eq, DM_neq = density.integrate(deviceprop=device, kpoint=kpoints[0]) + DM_eq_standard = torch.tensor([[ 1.0000e+00, -6.3615e-01, 3.4565e-07, 2.1080e-01], + [-6.3615e-01, 1.0000e+00, -6.3615e-01, 3.4565e-07], + [ 3.4565e-07, -6.3615e-01, 1.0000e+00, -6.3615e-01], + [ 2.1080e-01, 3.4565e-07, -6.3615e-01, 1.0000e+00]],dtype=torch.float64) + + assert np.array(abs(DM_eq_standard-DM_eq)<1e-5).all() + assert DM_neq==0.0 + + onsite_density=density.get_density_onsite(deviceprop=device,DM=DM_eq) + onsite_density_standard = torch.tensor([[ 0.0000, 0.0000, 6.4000, 1.0000],[ 0.0000, 0.0000, 8.0000, 1.0000], + [ 0.0000, 0.0000, 9.6000, 1.0000],[ 0.0000, 0.0000, 11.2000, 1.0000]], dtype=torch.float64) + assert np.array(abs(onsite_density_standard-onsite_density)<1e-5).all() \ No newline at end of file diff --git a/dptb/tests/test_negf_device_property.py b/dptb/tests/test_negf_device_property.py new file mode 100644 index 00000000..cf24e0e4 --- /dev/null +++ b/dptb/tests/test_negf_device_property.py @@ -0,0 +1,188 @@ +#test_negf_Device_set_leadLR +from dptb.negf.device_property import DeviceProperty +from dptb.v1.init_nnsk import InitSKModel +from dptb.nnops.v1.NN2HRK import NN2HRK +from dptb.nnops.v1.apihost import NNSKHost +from dptb.utils.tools import j_must_have +from dptb.utils.tools import j_loader +import numpy as np +import torch +from dptb.negf.negf_hamiltonian_init import NEGFHamiltonianInit +from ase.io import read +from dptb.negf.lead_property import LeadProperty +from dptb.utils.constants import Boltzmann, eV2J +import pytest + + +@pytest.fixture(scope='session', autouse=True) +def root_directory(request): + """ + :return: + """ + return str(request.config.rootdir) + +def test_negf_Device(root_directory): + model_ckpt=root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json' + jdata = root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json" + structure=root_directory +"/dptb/tests/data/test_negf/test_negf_run/chain.vasp" + log_path=root_directory +"/dptb/tests/data/test_negf/test_negf_Device/test.log" + + + # read input files and generate Hamiltonian + apihost = NNSKHost(checkpoint=model_ckpt, config=jdata) + apihost.register_plugin(InitSKModel()) + apihost.build() + apiHrk = NN2HRK(apihost=apihost, mode='nnsk') + jdata = j_loader(jdata) + task_options = j_must_have(jdata, "task_options") + + run_opt = { + "run_sk": True, + "init_model":model_ckpt, + "results_path":root_directory +"/dptb/tests/data/test_negf/", + "structure":structure, + "log_path": log_path, + "log_level": 5, + "use_correction":False + } + + + structase=read(run_opt['structure']) + results_path=run_opt.get('results_path') + kpoints=np.array([[0,0,0]]) + ele_T = task_options["ele_T"] + kBT = Boltzmann * ele_T / eV2J + e_fermi = task_options["e_fermi"] + + hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) + with torch.no_grad(): + struct_device, struct_leads = hamiltonian.initialize(kpoints=kpoints) + + + device = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=e_fermi) + device.set_leadLR( + lead_L=LeadProperty( + hamiltonian=hamiltonian, + tab="lead_L", + structure=struct_leads["lead_L"], + results_path=results_path, + e_T=ele_T, + efermi=e_fermi, + voltage=task_options["stru_options"]["lead_L"]["voltage"] + ), + lead_R=LeadProperty( + hamiltonian=hamiltonian, + tab="lead_R", + structure=struct_leads["lead_R"], + results_path=results_path, + e_T=ele_T, + efermi=e_fermi, + voltage=task_options["stru_options"]["lead_R"]["voltage"] + ) + ) + + # check device.Lead_L.structure + assert all(device.lead_L.structure.symbols=='C4') + assert device.lead_L.structure.pbc[0]==False + assert device.lead_L.structure.pbc[1]==False + assert device.lead_L.structure.pbc[2]==True + assert np.diag(np.array((device.lead_L.structure.cell-[10.0, 10.0, 6.4])<1e-4)).all() + assert device.lead_L.tab=="lead_L" + assert abs(device.mu+13.638587951660156)<1e-5 + # check device.Lead_R.structure + assert all(device.lead_R.structure.symbols=='C4') + assert device.lead_R.structure.pbc[0]==False + assert device.lead_R.structure.pbc[1]==False + assert device.lead_R.structure.pbc[2]==True + assert np.diag(np.array((device.lead_R.structure.cell-[10.0, 10.0, 6.4])<1e-4)).all() + assert device.lead_R.tab=="lead_R" + + + # calculate Self energy and Green function + stru_options = j_must_have(task_options, "stru_options") + leads = stru_options.keys() + for ll in leads: + if ll.startswith("lead"): #calculate surface green function at E=0 + getattr(device, ll).self_energy( + energy=torch.tensor([0]), + kpoint=kpoints[0], + eta_lead=task_options["eta_lead"], + method=task_options["sgf_solver"] + ) + + # check left and right leads' self-energy + lead_L_se_standard=torch.tensor([[-3.3171e-07-0.6096j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], + [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], + [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], + [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j]], dtype=torch.complex128) + lead_R_se_standard=torch.tensor([[ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], + [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], + [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], + [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,-3.3171e-07-0.6096j]], dtype=torch.complex128) + + assert abs(device.lead_L.se-lead_L_se_standard).max()<1e-5 + assert abs(device.lead_R.se-lead_R_se_standard).max()<1e-5 + + device.cal_green_function( energy=torch.tensor([0]), #calculate device green function at E=0 + kpoint=kpoints[0], + eta_device=task_options["eta_device"], + block_tridiagonal=task_options["block_tridiagonal"] + ) + + #check green functions' results + assert list(device.greenfuncs.keys())==['g_trans', 'grd', 'grl', 'gru', 'gr_left', 'gnd', 'gnl',\ + 'gnu', 'gin_left', 'gpd', 'gpl', 'gpu', 'gip_left'] + g_trans= torch.tensor([[ 1.0983e-11-8.2022e-01j, -8.2022e-01+4.4634e-07j,8.9264e-07+8.2022e-01j, 8.2022e-01-1.3390e-06j], + [-8.2022e-01+4.4634e-07j, -3.6607e-12-8.2022e-01j,-8.2021e-01+4.4631e-07j, 8.9264e-07+8.2022e-01j], + [ 8.9264e-07+8.2022e-01j, -8.2021e-01+4.4631e-07j,-3.6607e-12-8.2022e-01j, -8.2022e-01+4.4634e-07j], + [ 8.2022e-01-1.3390e-06j, 8.9264e-07+8.2022e-01j,-8.2022e-01+4.4634e-07j, 1.0983e-11-8.2022e-01j]],dtype=torch.complex128) + grd= [torch.tensor([[ 1.0983e-11-8.2022e-01j, -8.2022e-01+4.4634e-07j,8.9264e-07+8.2022e-01j, 8.2022e-01-1.3390e-06j], + [-8.2022e-01+4.4634e-07j, -3.6607e-12-8.2022e-01j,-8.2021e-01+4.4631e-07j, 8.9264e-07+8.2022e-01j], + [ 8.9264e-07+8.2022e-01j, -8.2021e-01+4.4631e-07j,-3.6607e-12-8.2022e-01j, -8.2022e-01+4.4634e-07j], + [ 8.2022e-01-1.3390e-06j, 8.9264e-07+8.2022e-01j,-8.2022e-01+4.4634e-07j, 1.0983e-11-8.2022e-01j]],dtype=torch.complex128)] + + assert abs(g_trans-device.greenfuncs['g_trans']).max()<1e-5 + assert abs(grd[0]-device.greenfuncs['grd'][0]).max()<1e-5 + assert device.greenfuncs['grl'] == [] + assert device.greenfuncs['gru'] == [] + + gr_left= [torch.tensor([[ 1.0983e-11-8.2022e-01j, -8.2022e-01+4.4634e-07j,8.9264e-07+8.2022e-01j, 8.2022e-01-1.3390e-06j], + [-8.2022e-01+4.4634e-07j, -3.6607e-12-8.2022e-01j,-8.2021e-01+4.4631e-07j, 8.9264e-07+8.2022e-01j], + [ 8.9264e-07+8.2022e-01j, -8.2021e-01+4.4631e-07j,-3.6607e-12-8.2022e-01j, -8.2022e-01+4.4634e-07j], + [ 8.2022e-01-1.3390e-06j, 8.9264e-07+8.2022e-01j,-8.2022e-01+4.4634e-07j, 1.0983e-11-8.2022e-01j]],dtype=torch.complex128)] + + gnd = [torch.tensor([[ 8.2022e-01+0.0000e+00j, -4.4634e-07+2.2204e-16j,-8.2022e-01-3.1764e-22j, 1.3390e-06-5.5511e-17j], + [-4.4634e-07-2.7756e-16j, 8.2022e-01+2.6470e-23j, -4.4631e-07+2.7756e-16j, -8.2022e-01-2.3823e-22j], + [-8.2022e-01+2.9117e-22j, -4.4631e-07-2.2204e-16j, 8.2022e-01+7.9409e-23j, -4.4634e-07+1.1102e-16j], + [ 1.3390e-06+5.5511e-17j, -8.2022e-01+2.1176e-22j, -4.4634e-07-1.1102e-16j, 8.2022e-01+0.0000e+00j]],dtype=torch.complex128)] + + assert abs(gr_left[0]-device.greenfuncs['gr_left'][0]).max()<1e-5 + assert abs(gnd[0]-device.greenfuncs['gnd'][0]).max()<1e-5 + assert device.greenfuncs['gnl'] == [] + assert device.greenfuncs['gnu'] == [] + + gin_left=[torch.tensor([[ 8.2022e-01+0.0000e+00j, -4.4634e-07+2.2204e-16j, -8.2022e-01-3.1764e-22j, 1.3390e-06-5.5511e-17j], + [-4.4634e-07-2.7756e-16j, 8.2022e-01+2.6470e-23j, -4.4631e-07+2.7756e-16j, -8.2022e-01-2.3823e-22j], + [-8.2022e-01+2.9117e-22j, -4.4631e-07-2.2204e-16j, 8.2022e-01+7.9409e-23j, -4.4634e-07+1.1102e-16j], + [ 1.3390e-06+5.5511e-17j, -8.2022e-01+2.1176e-22j,-4.4634e-07-1.1102e-16j, 8.2022e-01+0.0000e+00j]],dtype=torch.complex128)] + assert abs(gin_left[0]-device.greenfuncs['gin_left'][0]).max()<1e-5 + + assert device.greenfuncs['gpd']== None + assert device.greenfuncs['gpl']== None + assert device.greenfuncs['gpu']== None + assert device.greenfuncs['gip_left']== None + + Tc=device._cal_tc_() #transmission + assert abs(Tc-1)<1e-5 + + dos = device._cal_dos_() + dos_standard = torch.tensor(2.0887, dtype=torch.float64) + assert abs(dos-dos_standard)<1e-4 + + ldos = device._cal_ldos_() + ldos_standard = torch.tensor([0.2611, 0.2611, 0.2611, 0.2611], dtype=torch.float64)*2 + assert abs(ldos_standard-ldos).max()<1e-4 + + + + diff --git a/dptb/tests/test_negf_negf_hamiltonian_init.py b/dptb/tests/test_negf_negf_hamiltonian_init.py new file mode 100644 index 00000000..13da5abe --- /dev/null +++ b/dptb/tests/test_negf_negf_hamiltonian_init.py @@ -0,0 +1,129 @@ +# Hamiltonian +from dptb.v1.init_nnsk import InitSKModel +from dptb.nnops.v1.NN2HRK import NN2HRK +from dptb.nnops.v1.apihost import NNSKHost +from dptb.utils.tools import j_must_have +from dptb.utils.tools import j_loader +import numpy as np +import torch +import pytest +from dptb.negf.negf_hamiltonian_init import NEGFHamiltonianInit +from ase.io import read + + +@pytest.fixture(scope='session', autouse=True) +def root_directory(request): + """ + :return: + """ + return str(request.config.rootdir) + + +def test_negf_Hamiltonian(root_directory): + + model_ckpt=root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json' + jdata = root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json" + structure=root_directory +"/dptb/tests/data/test_negf/test_negf_run/chain.vasp" + log_path=root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/test.log" + + apihost = NNSKHost(checkpoint=model_ckpt, config=jdata) + apihost.register_plugin(InitSKModel()) + apihost.build() + apiHrk = NN2HRK(apihost=apihost, mode='nnsk') + jdata = j_loader(jdata) + task_options = j_must_have(jdata, "task_options") + + run_opt = { + "run_sk": True, + "init_model":model_ckpt, + "results_path":root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/", + "structure":structure, + "log_path": log_path, + "log_level": 5, + "use_correction":False + } + + + structase=read(run_opt['structure']) + results_path=run_opt.get('results_path') + kpoints=np.array([[0,0,0]]) + + hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) + with torch.no_grad(): + struct_device, struct_leads = hamiltonian.initialize(kpoints=kpoints) + + #check device's Hamiltonian + assert all(struct_device.symbols=="C4") + assert all(struct_device.pbc)==False + assert np.diag(np.array(struct_device.cell==[10.0, 10.0, 19.2])).all() + + #check lead_L's Hamiltonian + + assert all(struct_leads["lead_L"].symbols=="C4") + assert struct_leads["lead_L"].pbc[0]==False + assert struct_leads["lead_L"].pbc[1]==False + assert struct_leads["lead_L"].pbc[2]==True + assert np.diag(np.array(struct_leads["lead_L"].cell==[10.0, 10.0, -6.4])).all() + + #check lead_R's Hamiltonian + + assert all(struct_leads["lead_R"].symbols=="C4") + assert struct_leads["lead_R"].pbc[0]==False + assert struct_leads["lead_R"].pbc[1]==False + assert struct_leads["lead_R"].pbc[2]==True + assert np.diag(np.array(struct_leads["lead_R"].cell==[10.0, 10.0, 6.4])).all() + + + #check hs_device + h_device = hamiltonian.get_hs_device(kpoint=np.array([0,0,0]),V=0,block_tridiagonal=False)[0][0] + print(hamiltonian.get_hs_device(kpoint=np.array([0,0,0]),V=0,block_tridiagonal=False)[0]) + h_device_standard = torch.tensor([[-13.6386+0.j, 0.6096+0.j, 0.0000+0.j, 0.0000+0.j], + [ 0.6096+0.j, -13.6386+0.j, 0.6096+0.j, 0.0000+0.j], + [ 0.0000+0.j, 0.6096+0.j, -13.6386+0.j, 0.6096+0.j], + [ 0.0000+0.j, 0.0000+0.j, 0.6096+0.j, -13.6386+0.j]],dtype=torch.complex128) + assert abs(h_device-h_device_standard).max()<1e-4 + + s_device = hamiltonian.get_hs_device(kpoint=np.array([0,0,0]),V=0,block_tridiagonal=False)[1][0] + print(hamiltonian.get_hs_device(kpoint=np.array([0,0,0]),V=0,block_tridiagonal=False)[1][0]) + s_standard = torch.eye(4) + assert abs(s_device-s_standard).max()<1e-5 + + + + #check hs_lead + hl_lead = hamiltonian.get_hs_lead(kpoint=np.array([0,0,0]),tab="lead_L",v=0)[0][0] + hl_lead_standard = torch.tensor([-13.6386+0.j, 0.6096+0.j], dtype=torch.complex128) + assert abs(hl_lead-hl_lead_standard).max()<1e-4 + + hll_lead = hamiltonian.get_hs_lead(kpoint=np.array([0,0,0]),tab="lead_L",v=0)[1][0] + hll_lead_standard = torch.tensor([0.0000+0.j, 0.6096+0.j], dtype=torch.complex128) + print(hll_lead) + assert abs(hll_lead-hll_lead_standard).max()<1e-4 + + hDL_lead = hamiltonian.get_hs_lead(kpoint=np.array([0,0,0]),tab="lead_L",v=0)[2] + hDL_lead_standard = torch.tensor([[0.0000+0.j, 0.6096+0.j], + [0.0000+0.j, 0.0000+0.j], + [0.0000+0.j, 0.0000+0.j], + [0.0000+0.j, 0.0000+0.j]], dtype=torch.complex128) + assert abs(hDL_lead-hDL_lead_standard).max()<1e-5 + + sl_lead = hamiltonian.get_hs_lead(kpoint=np.array([0,0,0]),tab="lead_L",v=0)[3] + sl_lead_standard = torch.eye(2) + assert abs(sl_lead-sl_lead_standard).max()<1e-5 + + sll_lead = hamiltonian.get_hs_lead(kpoint=np.array([0,0,0]),tab="lead_L",v=0)[4] + sll_lead_standard = torch.zeros(2) + assert abs(sll_lead-sll_lead_standard).max()<1e-5 + + sDL_lead = hamiltonian.get_hs_lead(kpoint=np.array([0,0,0]),tab="lead_L",v=0)[5] + sDL_lead_standard = torch.zeros([4,2]) + assert abs(sDL_lead-sDL_lead_standard).max()<1e-5 + + + # check device norbs + na = len(hamiltonian.device_norbs) + device_norbs_standard=[1,1,1,1] + assert na == 4 + assert hamiltonian.device_norbs==device_norbs_standard + + diff --git a/dptb/tests/test_negf_ozaki_res_cal.py b/dptb/tests/test_negf_ozaki_res_cal.py new file mode 100644 index 00000000..e13fb404 --- /dev/null +++ b/dptb/tests/test_negf_ozaki_res_cal.py @@ -0,0 +1,13 @@ +import pytest +import torch +from dptb.negf.ozaki_res_cal import ozaki_residues + +def test_ozaki(): + + p, r = ozaki_residues(M_cut=1) + assert abs(p-torch.tensor([3.464101615], dtype=torch.float64))<1e-8 + assert abs(r-torch.tensor([1.499999999], dtype=torch.float64))<1e-8 + p1, r1 = ozaki_residues(M_cut=2) + for i in range(2): + assert abs(p1[i]-torch.tensor([3.142466786, 13.043193723], dtype=torch.float64)[i])<1e-8 + assert abs(r1[i]-torch.tensor([1.002338271, 3.997661728], dtype=torch.float64)[i])<1e-8 From a599a67802105d8dde78cab7e16b47ab0bc0062b Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 1 Apr 2024 17:17:01 +0800 Subject: [PATCH 083/209] update NEGF.py init --- dptb/postprocess/NEGF.py | 102 ++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 40 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 2e4a6c1b..b7e93954 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -20,54 +20,75 @@ from dptb.utils.make_kpoints import kmesh_sampling_negf import logging from dptb.negf.poisson_init import Grid,Interface3D,Gate,Dielectric - +from typing import Optional, Union from pyinstrument import Profiler +from dptb.data import AtomicData, AtomicDataDict log = logging.getLogger(__name__) # TODO : add common class to set all the dtype and precision. class NEGF(object): - def __init__(self, apiHrk, run_opt, jdata): - self.apiH = apiHrk - if isinstance(run_opt['structure'],str): - self.structase = read(run_opt['structure']) - elif isinstance(run_opt['structure'],ase.Atoms): - self.structase = run_opt['structure'] - else: - raise ValueError('structure must be ase.Atoms or str') + def __init__(self, + model: torch.nn.Module, + AtomicData_options: dict, + structure: Union[AtomicData, ase.Atoms, str], + ele_T: float,e_fermi: float, + density_options: dict, + unit: str, + scf: bool, poisson_options: dict, + stru_options: dict, + block_tridiagonal: bool, + results_path: Optional[str]=None,device: Union[str, torch.device]=torch.device('cpu'), + out_tc: bool=False,out_dos: bool=False,out_density: bool=False,out_potential: bool=False, + out_current: bool=False,out_current_nscf: bool=False,out_ldos: bool=False,out_lcurrent: bool=False,): - self.results_path = run_opt.get('results_path') - self.jdata = jdata - self.cdtype = torch.complex128 - self._device = "cpu" + # self.apiH = apiHrk + # load model and structure + # AtomicData is + self.model = model + if isinstance(structure,str): + structase = read(structure) + data = AtomicData.from_ase(structase, **AtomicData_options) + elif isinstance(structure,ase.Atoms): + structase = structure + data = AtomicData.from_ase(structase, **AtomicData_options) + elif isinstance(structure,AtomicData): + structase = structure.to_ase() + data = data + else: + raise ValueError('structure must be AtomicData, ase.Atoms or str') + self.results_path = results_path + # self.jdata = jdata + self.cdtype = torch.complex128 + self.device = device # get the parameters - self.ele_T = jdata["ele_T"] + self.ele_T = ele_T self.kBT = Boltzmann * self.ele_T / eV2J # change to eV - self.e_fermi = jdata["e_fermi"] - self.stru_options = j_must_have(jdata, "stru_options") + self.e_fermi = e_fermi + self.stru_options = stru_options self.pbc = self.stru_options["pbc"] # check the consistency of the kmesh and pbc assert len(self.pbc) == 3, "pbc should be a list of length 3" for i in range(3): - if self.pbc[i] == False and self.jdata["stru_options"]["kmesh"][i] > 1: + if self.pbc[i] == False and self.stru_options["kmesh"][i] > 1: raise ValueError("kmesh should be 1 for non-periodic direction") - elif self.pbc[i] == False and self.jdata["stru_options"]["kmesh"][i] == 0: - self.jdata["stru_options"]["kmesh"][i] = 1 + elif self.pbc[i] == False and self.stru_options["kmesh"][i] == 0: + self.stru_options["kmesh"][i] = 1 log.warning(msg="kmesh should be set to 1 for non-periodic direction! Automatically Setting kmesh to 1 in direction {}.".format(i)) - elif self.pbc[i] == True and self.jdata["stru_options"]["kmesh"][i] == 0: + elif self.pbc[i] == True and self.stru_options["kmesh"][i] == 0: raise ValueError("kmesh should be > 0 for periodic direction") if not any(self.pbc): self.kpoints,self.wk = np.array([[0,0,0]]),np.array([1.]) else: - self.kpoints,self.wk = kmesh_sampling_negf(self.jdata["stru_options"]["kmesh"], - self.jdata["stru_options"]["gamma_center"], - self.jdata["stru_options"]["time_reversal_symmetry"]) + self.kpoints,self.wk = kmesh_sampling_negf(self.stru_options["kmesh"], + self.stru_options["gamma_center"], + self.stru_options["time_reversal_symmetry"]) log.info(msg="------ k-point for NEGF -----") log.info(msg="Gamma Center: {0}".format(self.jdata["stru_options"]["gamma_center"])) log.info(msg="Time Reversal: {0}".format(self.jdata["stru_options"]["time_reversal_symmetry"])) @@ -77,13 +98,13 @@ def __init__(self, apiHrk, run_opt, jdata): log.info(msg="k-points weights: {0}".format(self.wk)) log.info(msg="--------------------------------") - self.unit = jdata["unit"] - self.scf = jdata["scf"] - self.block_tridiagonal = jdata["block_tridiagonal"] + self.unit = unit + self.scf = scf + self.block_tridiagonal = block_tridiagonal - # computing the hamiltonian - self.negf_hamiltonian = NEGFHamiltonianInit(apiH=self.apiH, structase=self.structase, stru_options=jdata["stru_options"], results_path=self.results_path) + # computing the hamiltonian #需要改写NEGFHamiltonianInit + self.negf_hamiltonian = NEGFHamiltonianInit(apiH=self.apiH, structase=structase, stru_options=self.stru_options, results_path=self.results_path) with torch.no_grad(): struct_device, struct_leads = self.negf_hamiltonian.initialize(kpoints=self.kpoints) @@ -97,7 +118,7 @@ def __init__(self, apiHrk, run_opt, jdata): results_path=self.results_path, e_T=self.ele_T, efermi=self.e_fermi, - voltage=self.jdata["stru_options"]["lead_L"]["voltage"] + voltage=self.stru_options["lead_L"]["voltage"] ), lead_R=LeadProperty( hamiltonian=self.negf_hamiltonian, @@ -106,12 +127,13 @@ def __init__(self, apiHrk, run_opt, jdata): results_path=self.results_path, e_T=self.ele_T, efermi=self.e_fermi, - voltage=self.jdata["stru_options"]["lead_R"]["voltage"] + voltage=self.stru_options["lead_R"]["voltage"] ) ) # initialize density class - self.density_options = j_must_have(self.jdata, "density_options") + # self.density_options = j_must_have(self.jdata, "density_options") + self.density_options = density_options if self.density_options["method"] == "Ozaki": self.density = Ozaki(R=self.density_options["R"], M_cut=self.density_options["M_cut"], n_gauss=self.density_options["n_gauss"]) elif self.density_options["method"] == "Fiori": @@ -124,20 +146,20 @@ def __init__(self, apiHrk, run_opt, jdata): # np.save(self.results_path+"/device_atom_norbs.npy",self.device_atom_norbs) # geting the output settings - self.out_tc = jdata["out_tc"] - self.out_dos = jdata["out_dos"] - self.out_density = jdata["out_density"] - self.out_potential = jdata["out_potential"] - self.out_current = jdata["out_current"] - self.out_current_nscf = jdata["out_current_nscf"] - self.out_ldos = jdata["out_ldos"] - self.out_lcurrent = jdata["out_lcurrent"] + self.out_tc = out_tc + self.out_dos = out_dos + self.out_density = out_density + self.out_potential = out_potential + self.out_current = out_current + self.out_current_nscf = out_current_nscf + self.out_ldos = out_ldos + self.out_lcurrent = out_lcurrent assert not (self.out_lcurrent and self.block_tridiagonal) self.generate_energy_grid() self.out = {} ## Poisson equation settings - self.poisson_options = j_must_have(jdata, "poisson_options") + self.poisson_options = poisson_options # self.LDOS_integral = {} # for electron density integral self.free_charge = {} # net charge: hole - electron self.gate_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("gate")] From 0566f1573b894be3fce797403dc7ebec40725279 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 1 Apr 2024 22:02:54 +0800 Subject: [PATCH 084/209] move AtomicData creator to negf_hamiltonian_init.py --- dptb/postprocess/NEGF.py | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index b7e93954..7b576a94 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -39,31 +39,20 @@ def __init__(self, scf: bool, poisson_options: dict, stru_options: dict, block_tridiagonal: bool, - results_path: Optional[str]=None,device: Union[str, torch.device]=torch.device('cpu'), out_tc: bool=False,out_dos: bool=False,out_density: bool=False,out_potential: bool=False, - out_current: bool=False,out_current_nscf: bool=False,out_ldos: bool=False,out_lcurrent: bool=False,): + out_current: bool=False,out_current_nscf: bool=False,out_ldos: bool=False,out_lcurrent: bool=False, + results_path: Optional[str]=None, + overlap=False, + torch_device: Union[str, torch.device]=torch.device('cpu'),): # self.apiH = apiHrk - # load model and structure - # AtomicData is - self.model = model - if isinstance(structure,str): - structase = read(structure) - data = AtomicData.from_ase(structase, **AtomicData_options) - elif isinstance(structure,ase.Atoms): - structase = structure - data = AtomicData.from_ase(structase, **AtomicData_options) - elif isinstance(structure,AtomicData): - structase = structure.to_ase() - data = data - else: - raise ValueError('structure must be AtomicData, ase.Atoms or str') - + + self.model = model self.results_path = results_path # self.jdata = jdata self.cdtype = torch.complex128 - self.device = device + self.torch_device = torch_device # get the parameters self.ele_T = ele_T @@ -90,8 +79,8 @@ def __init__(self, self.stru_options["gamma_center"], self.stru_options["time_reversal_symmetry"]) log.info(msg="------ k-point for NEGF -----") - log.info(msg="Gamma Center: {0}".format(self.jdata["stru_options"]["gamma_center"])) - log.info(msg="Time Reversal: {0}".format(self.jdata["stru_options"]["time_reversal_symmetry"])) + log.info(msg="Gamma Center: {0}".format(self.stru_options["gamma_center"])) + log.info(msg="Time Reversal: {0}".format(self.stru_options["time_reversal_symmetry"])) log.info(msg="k-points Num: {0}".format(len(self.kpoints))) if len(self.wk)<10: log.info(msg="k-points: {0}".format(self.kpoints)) @@ -104,7 +93,12 @@ def __init__(self, # computing the hamiltonian #需要改写NEGFHamiltonianInit - self.negf_hamiltonian = NEGFHamiltonianInit(apiH=self.apiH, structase=structase, stru_options=self.stru_options, results_path=self.results_path) + self.negf_hamiltonian = NEGFHamiltonianInit(model=model, + AtomicData_options=AtomicData_options, + structure=structure, + stru_options=self.stru_options, + results_path=self.results_path, + torch_device = self.torch_device) with torch.no_grad(): struct_device, struct_leads = self.negf_hamiltonian.initialize(kpoints=self.kpoints) From 3880128d4400c26713e445b7e07a477a4e87c665 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 1 Apr 2024 22:03:45 +0800 Subject: [PATCH 085/209] modify init of negf_hamiltonian_init.py --- dptb/negf/negf_hamiltonian_init.py | 303 +++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index ebb5a9b0..011d0cea 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -16,6 +16,11 @@ import numpy as np from dptb.utils.make_kpoints import kmesh_sampling +import ase +from dptb.data import AtomicData, AtomicDataDict +from typing import Optional, Union +from dptb.nn.energy import Eigenvalues + ''' a Hamiltonian object that initializes and manipulates device and lead Hamiltonians for NEGF ''' @@ -48,6 +53,304 @@ class NEGFHamiltonianInit(object): ''' + def __init__(self, + model: torch.nn.Module, + AtomicData_options: dict, + structure: ase.Atoms, + stru_options:dict, + unit: str, + results_path:Optional[str]=None, + overlap: bool=False, + torch_device: Union[str, torch.device]=torch.device('cpu') + ) -> None: + + if isinstance(torch_device, str): + torch_device = torch.device(torch_device) + self.torch_device = torch_device + self.model = model + self.model.eval() + # self.apiH = apiH + if isinstance(structase,str): + structase = read(structure) + data = AtomicData.from_ase(structase, **AtomicData_options) + elif isinstance(structase,ase.Atoms): + structase = structure + data = AtomicData.from_ase(structase, **AtomicData_options) + elif isinstance(structure,AtomicData): + structase = structure.to_ase() + data = data + else: + raise ValueError('structure must be AtomicData, ase.Atoms or str') + + self.unit = unit + self.structase = structase + self.stru_options = stru_options + self.results_path = results_path + self.overlap = overlap + + if overlap: + self.eigv = Eigenvalues( + idp=model.idp, + device=self.torch_device, + s_edge_field=AtomicDataDict.EDGE_OVERLAP_KEY, + s_node_field=AtomicDataDict.NODE_OVERLAP_KEY, + s_out_field=AtomicDataDict.OVERLAP_KEY, + dtype=model.dtype, + ) + else: + self.eigv = Eigenvalues( + idp=model.idp, + device=self.torch_device, + dtype=model.dtype, + ) + + self.device_id = [int(x) for x in self.stru_options['device']["id"].split("-")] + self.lead_ids = {} + for kk in self.stru_options: + if kk.startswith("lead"): + self.lead_ids[kk] = [int(x) for x in self.stru_options.get(kk)["id"].split("-")] + + if self.unit == "Hartree": + self.h_factor = 13.605662285137 * 2 + elif self.unit == "eV": + self.h_factor = 1. + elif self.unit == "Ry": + self.h_factor = 13.605662285137 + else: + log.error("The unit name is not correct !") + raise ValueError + + def initialize(self, kpoints, block_tridiagnal=False): + """initializes the device and lead Hamiltonians + + construct device and lead Hamiltonians and return the structures respectively.The lead Hamiltonian + is k-resolved due to the transverse k point sampling. + + Args: + kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) + block_tridiagnal: A boolean parameter that determines whether to block-tridiagonalize the + device Hamiltonian or not. + + Returns: + structure_device and structure_leads corresponding to the structure of device and leads. + + Raises: + RuntimeError: if the lead hamiltonian attained from device and lead calculation does not match. + + """ + assert len(np.array(kpoints).shape) == 2 + + HS_device = {} + HS_leads = {} + HS_device["kpoints"] = kpoints + + self.apiH.update_struct(self.structase, mode="device", stru_options=j_must_have(self.stru_options, "device"), pbc=self.stru_options["pbc"]) + # change parameters to match the structure projection + n_proj_atom_pre = np.array([1]*len(self.structase))[:self.device_id[0]][self.apiH.structure.projatoms[:self.device_id[0]]].sum() + n_proj_atom_device = np.array([1]*len(self.structase))[self.device_id[0]:self.device_id[1]][self.apiH.structure.projatoms[self.device_id[0]:self.device_id[1]]].sum() + proj_device_id = [0,0] + proj_device_id[0] = n_proj_atom_pre + proj_device_id[1] = n_proj_atom_pre + n_proj_atom_device + self.proj_device_id = proj_device_id + projatoms = self.apiH.structure.projatoms + + self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] + self.apiH.get_HR() + # output the allbonds and hamil_block for check + # allbonds,hamil_block,_ =self.apiH.get_HR() + # torch.save(allbonds, os.path.join(self.results_path, "allbonds"+".pth")) + # torch.save(hamil_block, os.path.join(self.results_path, "hamil_block"+".pth")) + + H, S = self.apiH.get_HK(kpoints=kpoints) + d_start = int(np.sum(self.atom_norbs[:proj_device_id[0]])) + d_end = int(np.sum(self.atom_norbs)-np.sum(self.atom_norbs[proj_device_id[1]:])) + HD, SD = H[:,d_start:d_end, d_start:d_end], S[:, d_start:d_end, d_start:d_end] + + if not block_tridiagnal: + HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) + else: + hd, hu, hl, sd, su, sl = self.get_block_tridiagonal(HD*self.h_factor, SD) + HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl}) + + torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) + structure_device = self.apiH.structure.projected_struct[self.device_id[0]:self.device_id[1]] + + structure_leads = {} + for kk in self.stru_options: + if kk.startswith("lead"): + HS_leads = {} + stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] + # write(os.path.join(self.results_path, "stru_"+kk+".vasp"), stru_lead) + self.apiH.update_struct(stru_lead, mode="lead", stru_options=self.stru_options.get(kk), pbc=self.stru_options["pbc"]) + # update lead id + n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]][projatoms[:self.lead_ids[kk][0]]].sum() + n_proj_atom_lead = np.array([1]*len(self.structase))[self.lead_ids[kk][0]:self.lead_ids[kk][1]][projatoms[self.lead_ids[kk][0]:self.lead_ids[kk][1]]].sum() + proj_lead_id = [0,0] + proj_lead_id[0] = n_proj_atom_pre + proj_lead_id[1] = n_proj_atom_pre + n_proj_atom_lead + + l_start = int(np.sum(self.atom_norbs[:proj_lead_id[0]])) + l_end = int(l_start + np.sum(self.atom_norbs[proj_lead_id[0]:proj_lead_id[1]]) / 2) + HL, SL = H[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian in one principal layer + HDL, SDL = H[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] # device and lead's hopping + HS_leads.update({ + "HL":HL.cdouble()*self.h_factor, + "SL":SL.cdouble(), + "HDL":HDL.cdouble()*self.h_factor, + "SDL":SDL.cdouble()} + ) + + + structure_leads[kk] = self.apiH.structure.struct + self.apiH.get_HR() + # output the allbonds and hamil_block for check + # allbonds_lead,hamil_block_lead,_ = self.apiH.get_HR() + # torch.save(allbonds_lead, os.path.join(self.results_path, "allbonds_"+kk+".pth")) + # torch.save(hamil_block_lead, os.path.join(self.results_path, "hamil_block_"+kk+".pth")) + + h, s = self.apiH.get_HK(kpoints=kpoints) + nL = int(h.shape[1] / 2) + HLL, SLL = h[:, :nL, nL:], s[:, :nL, nL:] # H_{L_first2L_second} + err_l = (h[:, :nL, :nL] - HL).abs().max() + if err_l >= 1e-4: # check the lead hamiltonian get from device and lead calculation matches each other + log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") + raise RuntimeError + elif 1e-7 <= err_l <= 1e-4: + log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(err_l)) + + HS_leads.update({ + "HLL":HLL.cdouble()*self.h_factor, + "SLL":SLL.cdouble()} + ) + + HS_leads["kpoints"] = kpoints + + torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) + + return structure_device, structure_leads + + def get_hs_device(self, kpoint, V, block_tridiagonal=False): + """ get the device Hamiltonian and overlap matrix at a specific kpoint + + In diagonalization mode, the Hamiltonian and overlap matrix are block tridiagonalized, + and hd,hu,hl refers to the diagnonal, upper and lower blocks of the Hamiltonian, respectively. + The same rules apply to sd, su, sl. + + Args: + kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) + V: voltage bias + block_tridiagonal: a boolean flag that shows whether Hamiltonian has been diagonalized or not + + Returns: + if not diagonalized, return the whole Hamiltonian and Overlap HD-V*SD, SD + if diagonalized, return the block tridiagonalized Hamiltonian and Overlap component hd, hu, hl, + sd, su, sl. + """ + f = torch.load(os.path.join(self.results_path, "HS_device.pth")) + kpoints = f["kpoints"] + + ix = None + for i, k in enumerate(kpoints): + if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: + ix = i + break + + assert ix is not None + + if not block_tridiagonal: + HD, SD = f["HD"][ix], f["SD"][ix] + else: + hd, sd, hl, su, sl, hu = f["hd"][ix], f["sd"][ix], f["hl"][ix], f["su"][ix], f["sl"][ix], f["hu"][ix] + + if block_tridiagonal: + return hd, sd, hl, su, sl, hu + else: + # print('HD shape:', HD.shape) + # print('SD shape:', SD.shape) + # print('V shape:', V.shape) + log.info(msg='Device Hamiltonian shape: {0}x{0}'.format(HD.shape[0], HD.shape[1])) + + return [HD - V*SD], [SD], [], [], [], [] + + def get_hs_lead(self, kpoint, tab, v): + """get the lead Hamiltonian and overlap matrix at a specific kpoint + + In diagonalization mode, the Hamiltonian and overlap matrix are block tridiagonalized, + and hd,hu,hl refers to the diagnonal, upper and lower blocks of the Hamiltonian, respectively. + The same rules apply to sd, su, sl. + + Args: + kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) + V: voltage bias + block_tridiagonal: a boolean flag that shows whether Hamiltonian has been diagonalized or not + + Returns: + if not diagonalized, return the whole Hamiltonian and Overlap HD-V*SD, SD + if diagonalized, return the block tridiagonalized Hamiltonian and Overlap component hd, hu, hl, + sd, su, sl. + """ + f = torch.load(os.path.join(self.results_path, "HS_{0}.pth".format(tab))) + kpoints = f["kpoints"] + + ix = None + for i, k in enumerate(kpoints): + if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: + ix = i + break + + assert ix is not None + + hL, hLL, hDL, sL, sLL, sDL = f["HL"][ix], f["HLL"][ix], f["HDL"][ix], \ + f["SL"][ix], f["SLL"][ix], f["SDL"][ix] + + + return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL + + def attach_potential(): + pass + + def write(self): + pass + + @property + def device_norbs(self): + """ + return the number of atoms in the device Hamiltonian + """ + return self.atom_norbs[self.device_id[0]:self.device_id[1]] + + # def get_hs_block_tridiagonal(self, HD, SD): + + # return hd, hu, hl, sd, su, sl + + + +class _NEGFHamiltonianInit(object): + '''The Class for Hamiltonian object in negf module. + + It is used to initialize and manipulate device and lead Hamiltonians for negf. + It is different from the Hamiltonian object in the dptb module. + + Property + ---------- + apiH: the API object for Hamiltonian + unit: the unit of energy + structase: the structure object for the device and leads + stru_options: the options for structure from input file + results_path: the path to store the results + + device_id: the start-atom id and end-atom id of the device in the structure file + lead_ids: the start-atom id and end-atom id of the leads in the structure file + + + Methods + ---------- + initialize: initializes the device and lead Hamiltonians + get_hs_device: get the device Hamiltonian and overlap matrix at a specific kpoint + get_hs_lead: get the lead Hamiltonian and overlap matrix at a specific kpoint + + ''' + def __init__(self, apiH, structase, stru_options, results_path) -> None: self.apiH = apiH self.unit = apiH.unit From 519285feed5388f97697391fb7a49dc686278dfc Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 2 Apr 2024 16:42:26 +0800 Subject: [PATCH 086/209] add self.e3H in negf_hamiltonian_init.py --- dptb/negf/negf_hamiltonian_init.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 011d0cea..5e22acff 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -20,6 +20,7 @@ from dptb.data import AtomicData, AtomicDataDict from typing import Optional, Union from dptb.nn.energy import Eigenvalues +from dptb.nn.hamiltonian import E3Hamiltonian ''' a Hamiltonian object that initializes and manipulates device and lead Hamiltonians for NEGF @@ -89,20 +90,26 @@ def __init__(self, self.overlap = overlap if overlap: - self.eigv = Eigenvalues( + self.e3H = E3Hamiltonian( idp=model.idp, - device=self.torch_device, - s_edge_field=AtomicDataDict.EDGE_OVERLAP_KEY, - s_node_field=AtomicDataDict.NODE_OVERLAP_KEY, - s_out_field=AtomicDataDict.OVERLAP_KEY, + decompose=False, + edge_field=AtomicDataDict.EDGE_OVERLAP_KEY, + node_field=AtomicDataDict.NODE_OVERLAP_KEY, + overlap=True, dtype=model.dtype, + device=self.torch_device ) + else: - self.eigv = Eigenvalues( + self.e3H = E3Hamiltonian( idp=model.idp, - device=self.torch_device, + decompose=False, + edge_field=AtomicDataDict.EDGE_FEATURES_KEY, + node_field=AtomicDataDict.NODE_FEATURES_KEY, + overlap=False, dtype=model.dtype, - ) + device=self.torch_device + ) self.device_id = [int(x) for x in self.stru_options['device']["id"].split("-")] self.lead_ids = {} From 73ac9be2f2b7e1138fb2f9126b79db556069ae4b Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 3 Apr 2024 14:48:57 +0800 Subject: [PATCH 087/209] add notes --- dptb/nn/hr2hk.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dptb/nn/hr2hk.py b/dptb/nn/hr2hk.py index 3dfb7ad3..29b6205f 100644 --- a/dptb/nn/hr2hk.py +++ b/dptb/nn/hr2hk.py @@ -63,7 +63,7 @@ def forward(self, data: AtomicDataDict.Type) -> AtomicDataDict.Type: # constructing hopping blocks if iorb == jorb: - factor = 0.5 + factor = 0.5 # for diagonal elements, we need to divide by 2 for later conjugate transpose operation to construct the whole Hamiltonian else: factor = 1.0 @@ -108,6 +108,7 @@ def forward(self, data: AtomicDataDict.Type) -> AtomicDataDict.Type: jmask = self.idp.mask_to_basis[data[AtomicDataDict.ATOM_TYPE_KEY].flatten()[jatom]] masked_hblock = hblock[imask][:,jmask] + block[:,iatom_indices,jatom_indices] += masked_hblock.squeeze(0).type_as(block) * \ torch.exp(-1j * 2 * torch.pi * (data[AtomicDataDict.KPOINT_KEY] @ data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][i])).reshape(-1,1,1) From 7b36d501fa7a9349418aa8d8ea089084cb18c32c Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 4 Apr 2024 11:45:56 +0800 Subject: [PATCH 088/209] add atom_norb in hr2hk --- dptb/nn/hr2hk.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dptb/nn/hr2hk.py b/dptb/nn/hr2hk.py index 29b6205f..411eb488 100644 --- a/dptb/nn/hr2hk.py +++ b/dptb/nn/hr2hk.py @@ -41,6 +41,8 @@ def __init__( self.node_field = node_field self.out_field = out_field + self.atom_norb = [] + def forward(self, data: AtomicDataDict.Type) -> AtomicDataDict.Type: # construct bond wise hamiltonian block from obital pair wise node/edge features @@ -97,8 +99,9 @@ def forward(self, data: AtomicDataDict.Type) -> AtomicDataDict.Type: masked_oblock = oblock[mask][:,mask] block[:,ist:ist+masked_oblock.shape[0],ist:ist+masked_oblock.shape[1]] = masked_oblock.squeeze(0) atom_id_to_indices[i] = slice(ist, ist+masked_oblock.shape[0]) + self.atom_norb.append(masked_oblock.shape[0]) ist += masked_oblock.shape[0] - + for i, hblock in enumerate(bondwise_hopping): iatom = data[AtomicDataDict.EDGE_INDEX_KEY][0][i] jatom = data[AtomicDataDict.EDGE_INDEX_KEY][1][i] @@ -108,7 +111,6 @@ def forward(self, data: AtomicDataDict.Type) -> AtomicDataDict.Type: jmask = self.idp.mask_to_basis[data[AtomicDataDict.ATOM_TYPE_KEY].flatten()[jatom]] masked_hblock = hblock[imask][:,jmask] - block[:,iatom_indices,jatom_indices] += masked_hblock.squeeze(0).type_as(block) * \ torch.exp(-1j * 2 * torch.pi * (data[AtomicDataDict.KPOINT_KEY] @ data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][i])).reshape(-1,1,1) From 0e9595ba4ad764043e69e7a3c6c28e310061df33 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 4 Apr 2024 11:46:59 +0800 Subject: [PATCH 089/209] update NEGFHamiltonianInit initialize part for new api --- dptb/negf/negf_hamiltonian_init.py | 188 ++++++++++++++++++----------- 1 file changed, 116 insertions(+), 72 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 5e22acff..2c9f0667 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -21,6 +21,8 @@ from typing import Optional, Union from dptb.nn.energy import Eigenvalues from dptb.nn.hamiltonian import E3Hamiltonian +from dptb.nn.hr2hk import HR2HK +from ase import Atoms ''' a Hamiltonian object that initializes and manipulates device and lead Hamiltonians for NEGF @@ -58,6 +60,7 @@ def __init__(self, model: torch.nn.Module, AtomicData_options: dict, structure: ase.Atoms, + pbc_negf: List[bool], stru_options:dict, unit: str, results_path:Optional[str]=None, @@ -69,47 +72,46 @@ def __init__(self, torch_device = torch.device(torch_device) self.torch_device = torch_device self.model = model + self.AtomicData_options = AtomicData_options self.model.eval() # self.apiH = apiH - if isinstance(structase,str): - structase = read(structure) - data = AtomicData.from_ase(structase, **AtomicData_options) - elif isinstance(structase,ase.Atoms): - structase = structure - data = AtomicData.from_ase(structase, **AtomicData_options) - elif isinstance(structure,AtomicData): - structase = structure.to_ase() - data = data + if isinstance(structure,str): + self.structase = read(structure) + self.data = AtomicData.from_ase(structure, **AtomicData_options) + elif isinstance(structure,ase.Atoms): + self.structase = structure + self.data = AtomicData.from_ase(structure, **AtomicData_options) else: raise ValueError('structure must be AtomicData, ase.Atoms or str') + data = AtomicData.to_AtomicDataDict(data.to(self.device)) + data = self.model.idp(data) self.unit = unit - self.structase = structase self.stru_options = stru_options + self.pbc_negf = pbc_negf + assert len(self.pbc_negf) == 3 self.results_path = results_path self.overlap = overlap - if overlap: - self.e3H = E3Hamiltonian( - idp=model.idp, - decompose=False, - edge_field=AtomicDataDict.EDGE_OVERLAP_KEY, - node_field=AtomicDataDict.NODE_OVERLAP_KEY, - overlap=True, - dtype=model.dtype, - device=self.torch_device + self.h2k = HR2HK( + idp=model.idp, + edge_field=AtomicDataDict.EDGE_FEATURES_KEY, + node_field=AtomicDataDict.NODE_FEATURES_KEY, + out_field=AtomicDataDict.HAMILTONIAN_KEY, + dtype= model.dtype, + device=self.torch_device, ) - else: - self.e3H = E3Hamiltonian( - idp=model.idp, - decompose=False, - edge_field=AtomicDataDict.EDGE_FEATURES_KEY, - node_field=AtomicDataDict.NODE_FEATURES_KEY, - overlap=False, - dtype=model.dtype, - device=self.torch_device - ) + if overlap: + self.s2k = HR2HK( + idp=model.idp, + overlap=True, + edge_field=AtomicDataDict.EDGE_OVERLAP_KEY, + node_field=AtomicDataDict.NODE_OVERLAP_KEY, + out_field=AtomicDataDict.OVERLAP_KEY, + dtype=model.dtype, + device=self.torch_device, + ) self.device_id = [int(x) for x in self.stru_options['device']["id"].split("-")] self.lead_ids = {} @@ -151,27 +153,41 @@ def initialize(self, kpoints, block_tridiagnal=False): HS_leads = {} HS_device["kpoints"] = kpoints - self.apiH.update_struct(self.structase, mode="device", stru_options=j_must_have(self.stru_options, "device"), pbc=self.stru_options["pbc"]) - # change parameters to match the structure projection - n_proj_atom_pre = np.array([1]*len(self.structase))[:self.device_id[0]][self.apiH.structure.projatoms[:self.device_id[0]]].sum() - n_proj_atom_device = np.array([1]*len(self.structase))[self.device_id[0]:self.device_id[1]][self.apiH.structure.projatoms[self.device_id[0]:self.device_id[1]]].sum() - proj_device_id = [0,0] - proj_device_id[0] = n_proj_atom_pre - proj_device_id[1] = n_proj_atom_pre + n_proj_atom_device - self.proj_device_id = proj_device_id - projatoms = self.apiH.structure.projatoms + # self.apiH.update_struct(self.structase, mode="device", stru_options=j_must_have(self.stru_options, "device"), pbc=self.stru_options["pbc"]) - self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] - self.apiH.get_HR() - # output the allbonds and hamil_block for check - # allbonds,hamil_block,_ =self.apiH.get_HR() - # torch.save(allbonds, os.path.join(self.results_path, "allbonds"+".pth")) - # torch.save(hamil_block, os.path.join(self.results_path, "hamil_block"+".pth")) + # change parameters to match the structure projection + n_proj_atom_pre = np.array([1]*len(self.structase))[:self.device_id[0]].sum() + n_proj_atom_device = np.array([1]*len(self.structase))[self.device_id[0]:self.device_id[1]].sum() + device_id = [0,0] + device_id[0] = n_proj_atom_pre + device_id[1] = n_proj_atom_pre + n_proj_atom_device + self.device_id = device_id + # projatoms = self.apiH.structure.projatoms + #原子排序:data[AtomicDataDict.KPOINT_KEY]中的原子排序和pos相同,即和结构文件中相同 + # self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] + # self.apiH.get_HR() - H, S = self.apiH.get_HK(kpoints=kpoints) - d_start = int(np.sum(self.atom_norbs[:proj_device_id[0]])) - d_end = int(np.sum(self.atom_norbs)-np.sum(self.atom_norbs[proj_device_id[1]:])) - HD, SD = H[:,d_start:d_end, d_start:d_end], S[:, d_start:d_end, d_start:d_end] + + self.data[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_device["kpoints"], dtype=self.model.dtype, device=self.torch_device) + self.data = self.model(self.data) + for ip,p in enumerate(self.pbc_negf):# 加入pbc修正:根据想要的pbc取舍bond_list + if not p: + mask = self.data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip] == 0 + self.data[AtomicDataDict.EDGE_INDEX_KEY] = self.data[AtomicDataDict.EDGE_INDEX_KEY][:,mask] + self.data = self.h2k(self.data) + self.atom_norbs = self.h2k.atom_norbs + HK = self.data[AtomicDataDict.HAMILTONIAN_KEY] + if self.overlap: + self.data = self.s2k(self.data) + S = self.data[AtomicDataDict.OVERLAP_KEY] + + + # HK中元素轨道的排序是如何的? + + # H, S = self.apiH.get_HK(kpoints=kpoints) + d_start = int(np.sum(self.atom_norbs[:device_id[0]])) + d_end = int(np.sum(self.atom_norbs)-np.sum(self.atom_norbs[device_id[1]:])) + HD, SD = HK[:,d_start:d_end, d_start:d_end], S[:, d_start:d_end, d_start:d_end] if not block_tridiagnal: HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) @@ -180,7 +196,11 @@ def initialize(self, kpoints, block_tridiagnal=False): HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl}) torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) - structure_device = self.apiH.structure.projected_struct[self.device_id[0]:self.device_id[1]] + + # TODO: check structure_device is correct or not + structure_device = self.structase[device_id[0]:device_id[1]] + structure_device.pbc = self.pbc_negf + # structure_device = self.apiH.structure.projected_struct[self.device_id[0]:self.device_id[1]] structure_leads = {} for kk in self.stru_options: @@ -188,18 +208,18 @@ def initialize(self, kpoints, block_tridiagnal=False): HS_leads = {} stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] # write(os.path.join(self.results_path, "stru_"+kk+".vasp"), stru_lead) - self.apiH.update_struct(stru_lead, mode="lead", stru_options=self.stru_options.get(kk), pbc=self.stru_options["pbc"]) + # self.apiH.update_struct(stru_lead, mode="lead", stru_options=self.stru_options.get(kk), pbc=self.stru_options["pbc"]) # update lead id - n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]][projatoms[:self.lead_ids[kk][0]]].sum() - n_proj_atom_lead = np.array([1]*len(self.structase))[self.lead_ids[kk][0]:self.lead_ids[kk][1]][projatoms[self.lead_ids[kk][0]:self.lead_ids[kk][1]]].sum() - proj_lead_id = [0,0] - proj_lead_id[0] = n_proj_atom_pre - proj_lead_id[1] = n_proj_atom_pre + n_proj_atom_lead - - l_start = int(np.sum(self.atom_norbs[:proj_lead_id[0]])) - l_end = int(l_start + np.sum(self.atom_norbs[proj_lead_id[0]:proj_lead_id[1]]) / 2) - HL, SL = H[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian in one principal layer - HDL, SDL = H[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] # device and lead's hopping + n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]].sum() + n_proj_atom_lead = np.array([1]*len(self.structase))[self.lead_ids[kk][0]:self.lead_ids[kk][1]].sum() + lead_id = [0,0] + lead_id[0] = n_proj_atom_pre + lead_id[1] = n_proj_atom_pre + n_proj_atom_lead + + l_start = int(np.sum(self.atom_norbs[:lead_id[0]])) + l_end = int(l_start + np.sum(self.atom_norbs[lead_id[0]:lead_id[1]]) / 2) + HL, SL = HK[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian in one principal layer + HDL, SDL = HK[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] # device and lead's hopping HS_leads.update({ "HL":HL.cdouble()*self.h_factor, "SL":SL.cdouble(), @@ -208,18 +228,42 @@ def initialize(self, kpoints, block_tridiagnal=False): ) - structure_leads[kk] = self.apiH.structure.struct - self.apiH.get_HR() - # output the allbonds and hamil_block for check - # allbonds_lead,hamil_block_lead,_ = self.apiH.get_HR() - # torch.save(allbonds_lead, os.path.join(self.results_path, "allbonds_"+kk+".pth")) - # torch.save(hamil_block_lead, os.path.join(self.results_path, "hamil_block_"+kk+".pth")) - - h, s = self.apiH.get_HK(kpoints=kpoints) - nL = int(h.shape[1] / 2) - HLL, SLL = h[:, :nL, nL:], s[:, :nL, nL:] # H_{L_first2L_second} - err_l = (h[:, :nL, :nL] - HL).abs().max() - if err_l >= 1e-4: # check the lead hamiltonian get from device and lead calculation matches each other + # structure_leads[kk] = self.apiH.structure.struct + # self.apiH.get_HR() + cell = np.array(stru_lead.cell)[:2] + natom = lead_id[1] - lead_id[0] + R_vec = stru_lead[int(natom/2):].positions - stru_lead[:int(natom/2)].positions + assert np.abs(R_vec[0] - R_vec[-1]).sum() < 1e-5 + R_vec = R_vec.mean(axis=0) * 2 + cell = np.concatenate([cell, R_vec.reshape(1,-1)]) + pbc_lead = self.pbc_negf.copy() + pbc_lead[2] = True + stru_lead = Atoms(str(stru_lead.symbols), + positions=stru_lead.positions, + cell=cell, + pbc=pbc_lead) + stru_lead.set_chemical_symbols(stru_lead.get_chemical_symbols()) + structure_leads[kk] = stru_lead + + lead_data = AtomicData.from_ase(structure_leads[kk], **self.AtomicData_options) + lead_data = AtomicData.to_AtomicDataDict(lead_data.to(self.device)) + lead_data = self.model.idp(lead_data) + lead_data = self.model(lead_data) + + lead_data = self.h2k(lead_data) + HK_lead = lead_data[AtomicDataDict.HAMILTONIAN_KEY] + if self.overlap: + lead_data = self.s2k(lead_data) + S_lead = lead_data[AtomicDataDict.OVERLAP_KEY] + + + # h, s = self.apiH.get_HK(kpoints=kpoints) + nL = int(HK_lead.shape[1] / 2) + HLL, SLL = HK_lead[:, :nL, nL:], S_lead[:, :nL, nL:] # H_{L_first2L_second} + err_l = (HK_lead[:, :nL, :nL] - HL).abs().max() + if err_l >= 1e-4: + # check the lead hamiltonian get from device and lead calculation matches each other + # a standard check to see the lead environment is bulk-like or not log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") raise RuntimeError elif 1e-7 <= err_l <= 1e-4: From 006c0973f5f33b47b14baa5520a1c5ea56ff35f5 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 4 Apr 2024 11:48:15 +0800 Subject: [PATCH 090/209] add pbc_negf in NEGFHamiltonianInit init --- dptb/postprocess/NEGF.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 7b576a94..c10b3976 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -95,7 +95,8 @@ def __init__(self, # computing the hamiltonian #需要改写NEGFHamiltonianInit self.negf_hamiltonian = NEGFHamiltonianInit(model=model, AtomicData_options=AtomicData_options, - structure=structure, + structure=structure, + pbc_negf = self.pbc, stru_options=self.stru_options, results_path=self.results_path, torch_device = self.torch_device) From b56b6f6fcb1771b6dcd74cf47580285de725441b Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 8 Apr 2024 14:54:57 +0800 Subject: [PATCH 091/209] modify pbc setting in device/lead --- dptb/negf/negf_hamiltonian_init.py | 133 ++++++++++++++++++----------- dptb/nn/hr2hk.py | 4 +- dptb/postprocess/NEGF.py | 26 +++--- 3 files changed, 100 insertions(+), 63 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 2c9f0667..88b07bfd 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -74,17 +74,21 @@ def __init__(self, self.model = model self.AtomicData_options = AtomicData_options self.model.eval() - # self.apiH = apiH + + # get bondlist with pbc in all directions for complete chemical environment + # around atoms in the two ends of device when predicting HR if isinstance(structure,str): - self.structase = read(structure) - self.data = AtomicData.from_ase(structure, **AtomicData_options) + self.structase = read(structure) elif isinstance(structure,ase.Atoms): self.structase = structure - self.data = AtomicData.from_ase(structure, **AtomicData_options) else: - raise ValueError('structure must be AtomicData, ase.Atoms or str') - data = AtomicData.to_AtomicDataDict(data.to(self.device)) - data = self.model.idp(data) + raise ValueError('structure must be ase.Atoms or str') + self.structase.set_pbc(pbc_negf) + alldata = AtomicData.from_ase(self.structase, **self.AtomicData_options) + # alldata[AtomicDataDict.PBC_KEY][2] = True + + alldata = AtomicData.to_AtomicDataDict(alldata.to(self.torch_device)) + self.alldata = self.model.idp(alldata) self.unit = unit self.stru_options = stru_options @@ -150,7 +154,6 @@ def initialize(self, kpoints, block_tridiagnal=False): assert len(np.array(kpoints).shape) == 2 HS_device = {} - HS_leads = {} HS_device["kpoints"] = kpoints # self.apiH.update_struct(self.structase, mode="device", stru_options=j_must_have(self.stru_options, "device"), pbc=self.stru_options["pbc"]) @@ -163,30 +166,29 @@ def initialize(self, kpoints, block_tridiagnal=False): device_id[1] = n_proj_atom_pre + n_proj_atom_device self.device_id = device_id # projatoms = self.apiH.structure.projatoms - #原子排序:data[AtomicDataDict.KPOINT_KEY]中的原子排序和pos相同,即和结构文件中相同 # self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] # self.apiH.get_HR() - self.data[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_device["kpoints"], dtype=self.model.dtype, device=self.torch_device) - self.data = self.model(self.data) - for ip,p in enumerate(self.pbc_negf):# 加入pbc修正:根据想要的pbc取舍bond_list - if not p: - mask = self.data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip] == 0 - self.data[AtomicDataDict.EDGE_INDEX_KEY] = self.data[AtomicDataDict.EDGE_INDEX_KEY][:,mask] - self.data = self.h2k(self.data) - self.atom_norbs = self.h2k.atom_norbs - HK = self.data[AtomicDataDict.HAMILTONIAN_KEY] + self.alldata[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_device["kpoints"], dtype=self.model.dtype, device=self.torch_device) + self.alldata = self.model(self.alldata) + # remove the edges corresponding to z-direction pbc for HR2HK + # for ip,p in enumerate(self.pbc_negf): + # if not p: + # mask = self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip] == 0 + # self.alldata[AtomicDataDict.EDGE_INDEX_KEY] = self.alldata[AtomicDataDict.EDGE_INDEX_KEY][:,mask] + # self.alldata[AtomicDataDict.EDGE_FEATURES_KEY] = self.alldata[AtomicDataDict.EDGE_FEATURES_KEY][mask] + self.alldata = self.h2k(self.alldata) + HK = self.alldata[AtomicDataDict.HAMILTONIAN_KEY] if self.overlap: - self.data = self.s2k(self.data) - S = self.data[AtomicDataDict.OVERLAP_KEY] - - - # HK中元素轨道的排序是如何的? - + self.alldata = self.s2k(self.alldata) + S = self.alldata[AtomicDataDict.OVERLAP_KEY] + else: + S = torch.eye(HK.shape[1], dtype=self.model.dtype, device=self.torch_device).unsqueeze(0).repeat(HK.shape[0], 1, 1) + # H, S = self.apiH.get_HK(kpoints=kpoints) - d_start = int(np.sum(self.atom_norbs[:device_id[0]])) - d_end = int(np.sum(self.atom_norbs)-np.sum(self.atom_norbs[device_id[1]:])) + d_start = int(np.sum(self.h2k.atom_norbs[:device_id[0]])) + d_end = int(np.sum(self.h2k.atom_norbs)-np.sum(self.h2k.atom_norbs[device_id[1]:])) HD, SD = HK[:,d_start:d_end, d_start:d_end], S[:, d_start:d_end, d_start:d_end] if not block_tridiagnal: @@ -206,6 +208,7 @@ def initialize(self, kpoints, block_tridiagnal=False): for kk in self.stru_options: if kk.startswith("lead"): HS_leads = {} + HS_leads["kpoints"] = kpoints stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] # write(os.path.join(self.results_path, "stru_"+kk+".vasp"), stru_lead) # self.apiH.update_struct(stru_lead, mode="lead", stru_options=self.stru_options.get(kk), pbc=self.stru_options["pbc"]) @@ -216,26 +219,26 @@ def initialize(self, kpoints, block_tridiagnal=False): lead_id[0] = n_proj_atom_pre lead_id[1] = n_proj_atom_pre + n_proj_atom_lead - l_start = int(np.sum(self.atom_norbs[:lead_id[0]])) - l_end = int(l_start + np.sum(self.atom_norbs[lead_id[0]:lead_id[1]]) / 2) - HL, SL = HK[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian in one principal layer + l_start = int(np.sum(self.h2k.atom_norbs[:lead_id[0]])) + l_end = int(l_start + np.sum(self.h2k.atom_norbs[lead_id[0]:lead_id[1]]) / 2) + # HL, SL = HK[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian in the first principal layer HDL, SDL = HK[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] # device and lead's hopping - HS_leads.update({ - "HL":HL.cdouble()*self.h_factor, - "SL":SL.cdouble(), - "HDL":HDL.cdouble()*self.h_factor, - "SDL":SDL.cdouble()} - ) + # HS_leads.update({ + # "HL":HL.cdouble()*self.h_factor, + # "SL":SL.cdouble(), + # "HDL":HDL.cdouble()*self.h_factor, + # "SDL":SDL.cdouble()} + # ) - # structure_leads[kk] = self.apiH.structure.struct - # self.apiH.get_HR() cell = np.array(stru_lead.cell)[:2] natom = lead_id[1] - lead_id[0] R_vec = stru_lead[int(natom/2):].positions - stru_lead[:int(natom/2)].positions assert np.abs(R_vec[0] - R_vec[-1]).sum() < 1e-5 R_vec = R_vec.mean(axis=0) * 2 cell = np.concatenate([cell, R_vec.reshape(1,-1)]) + + # get lead structure in ase format pbc_lead = self.pbc_negf.copy() pbc_lead[2] = True stru_lead = Atoms(str(stru_lead.symbols), @@ -245,36 +248,64 @@ def initialize(self, kpoints, block_tridiagnal=False): stru_lead.set_chemical_symbols(stru_lead.get_chemical_symbols()) structure_leads[kk] = stru_lead + # for correct HLL, shutdown temporarily the z-direction pbc to get bondlist + # structure_leads[kk].pbc[2] = False lead_data = AtomicData.from_ase(structure_leads[kk], **self.AtomicData_options) - lead_data = AtomicData.to_AtomicDataDict(lead_data.to(self.device)) + # open z-pbc again for the complete chemical environment (like bulk) in leads + # lead_data[AtomicDataDict.PBC_KEY][2] = True + + lead_data = AtomicData.to_AtomicDataDict(lead_data.to(self.torch_device)) lead_data = self.model.idp(lead_data) + lead_data[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_leads["kpoints"], dtype=self.model.dtype, device=self.torch_device) lead_data = self.model(lead_data) + # remove the edges corresponding to z-direction pbc for HR2HK + for ip,p in enumerate(self.pbc_negf): + if not p: + mask = abs(lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip])<1e-7 + lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] + lead_data[AtomicDataDict.EDGE_INDEX_KEY] = lead_data[AtomicDataDict.EDGE_INDEX_KEY][:,mask] + lead_data[AtomicDataDict.EDGE_FEATURES_KEY] = lead_data[AtomicDataDict.EDGE_FEATURES_KEY][mask] lead_data = self.h2k(lead_data) HK_lead = lead_data[AtomicDataDict.HAMILTONIAN_KEY] if self.overlap: lead_data = self.s2k(lead_data) S_lead = lead_data[AtomicDataDict.OVERLAP_KEY] + else: + S_lead = torch.eye(HK_lead.shape[1], dtype=self.model.dtype, device=self.torch_device).unsqueeze(0).repeat(HK_lead.shape[0], 1, 1) # h, s = self.apiH.get_HK(kpoints=kpoints) nL = int(HK_lead.shape[1] / 2) HLL, SLL = HK_lead[:, :nL, nL:], S_lead[:, :nL, nL:] # H_{L_first2L_second} - err_l = (HK_lead[:, :nL, :nL] - HL).abs().max() - if err_l >= 1e-4: - # check the lead hamiltonian get from device and lead calculation matches each other - # a standard check to see the lead environment is bulk-like or not - log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") - raise RuntimeError - elif 1e-7 <= err_l <= 1e-4: - log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(err_l)) - + HL, SL = HK_lead[:,:nL,:nL], S[:,:nL,:nL] # lead hamiltonian in one principal layer + HS_leads.update({ + "HL":HL.cdouble()*self.h_factor, + "SL":SL.cdouble(), + "HDL":HDL.cdouble()*self.h_factor, + "SDL":SDL.cdouble(), "HLL":HLL.cdouble()*self.h_factor, - "SLL":SLL.cdouble()} - ) + "SLL":SLL.cdouble() + }) + - HS_leads["kpoints"] = kpoints + + # err_l = (HK_lead[:, :nL, :nL] - HL).abs().max() + # if err_l >= 1e-4: + # # check the lead hamiltonian get from device and lead calculation matches each other + # # a standard check to see the lead environment is bulk-like or not + # log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") + # raise RuntimeError + # elif 1e-7 <= err_l <= 1e-4: + # log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(err_l)) + + # HS_leads.update({ + # "HLL":HLL.cdouble()*self.h_factor, + # "SLL":SLL.cdouble()} + # ) + + # HS_leads["kpoints"] = kpoints torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) @@ -368,7 +399,7 @@ def device_norbs(self): """ return the number of atoms in the device Hamiltonian """ - return self.atom_norbs[self.device_id[0]:self.device_id[1]] + return self.h2k.atom_norbs[self.device_id[0]:self.device_id[1]] # def get_hs_block_tridiagonal(self, HD, SD): diff --git a/dptb/nn/hr2hk.py b/dptb/nn/hr2hk.py index 411eb488..310a8a7f 100644 --- a/dptb/nn/hr2hk.py +++ b/dptb/nn/hr2hk.py @@ -41,7 +41,7 @@ def __init__( self.node_field = node_field self.out_field = out_field - self.atom_norb = [] + self.atom_norbs = [] def forward(self, data: AtomicDataDict.Type) -> AtomicDataDict.Type: @@ -99,7 +99,7 @@ def forward(self, data: AtomicDataDict.Type) -> AtomicDataDict.Type: masked_oblock = oblock[mask][:,mask] block[:,ist:ist+masked_oblock.shape[0],ist:ist+masked_oblock.shape[1]] = masked_oblock.squeeze(0) atom_id_to_indices[i] = slice(ist, ist+masked_oblock.shape[0]) - self.atom_norb.append(masked_oblock.shape[0]) + self.atom_norbs.append(masked_oblock.shape[0]) ist += masked_oblock.shape[0] for i, hblock in enumerate(bondwise_hopping): diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index c10b3976..e52ff679 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -34,16 +34,18 @@ def __init__(self, AtomicData_options: dict, structure: Union[AtomicData, ase.Atoms, str], ele_T: float,e_fermi: float, + emin: float, emax: float, espacing: float, density_options: dict, unit: str, scf: bool, poisson_options: dict, - stru_options: dict, - block_tridiagonal: bool, + stru_options: dict,eta_lead: float,eta_device: float, + block_tridiagonal: bool,sgf_solver: str, out_tc: bool=False,out_dos: bool=False,out_density: bool=False,out_potential: bool=False, out_current: bool=False,out_current_nscf: bool=False,out_ldos: bool=False,out_lcurrent: bool=False, results_path: Optional[str]=None, overlap=False, - torch_device: Union[str, torch.device]=torch.device('cpu'),): + torch_device: Union[str, torch.device]=torch.device('cpu'), + **kwargs): # self.apiH = apiHrk @@ -58,7 +60,10 @@ def __init__(self, self.ele_T = ele_T self.kBT = Boltzmann * self.ele_T / eV2J # change to eV self.e_fermi = e_fermi + self.eta_lead = eta_lead; self.eta_device = eta_device + self.emin = emin; self.emax = emax; self.espacing = espacing self.stru_options = stru_options + self.sgf_solver = sgf_solver self.pbc = self.stru_options["pbc"] # check the consistency of the kmesh and pbc @@ -97,7 +102,8 @@ def __init__(self, AtomicData_options=AtomicData_options, structure=structure, pbc_negf = self.pbc, - stru_options=self.stru_options, + stru_options=self.stru_options, + unit = self.unit, results_path=self.results_path, torch_device = self.torch_device) with torch.no_grad(): @@ -137,7 +143,7 @@ def __init__(self, raise ValueError # number of orbitals on atoms in device region - self.device_atom_norbs = self.negf_hamiltonian.atom_norbs[self.negf_hamiltonian.proj_device_id[0]:self.negf_hamiltonian.proj_device_id[1]] + self.device_atom_norbs = self.negf_hamiltonian.h2k.atom_norbs[self.negf_hamiltonian.device_id[0]:self.negf_hamiltonian.device_id[1]] # np.save(self.results_path+"/device_atom_norbs.npy",self.device_atom_norbs) # geting the output settings @@ -192,10 +198,10 @@ def generate_energy_grid(self): if self.out_dos or self.out_tc or self.out_current_nscf or self.out_ldos: # Energy gird is set relative to Fermi level - self.uni_grid = torch.linspace(start=self.jdata["emin"], end=self.jdata["emax"], steps=int((self.jdata["emax"]-self.jdata["emin"])/self.jdata["espacing"])) + self.uni_grid = torch.linspace(start=self.emin, end=self.emax, steps=int((self.emax-self.emin)/self.espacing)) if cal_pole and self.density_options["method"] == "Ozaki": - self.poles, self.residues = ozaki_residues(M_cut=self.jdata["density_options"]["M_cut"]) + self.poles, self.residues = ozaki_residues(M_cut=self.density_options["M_cut"]) self.poles = 1j* self.poles * self.kBT + self.deviceprop.lead_L.mu - self.deviceprop.mu if cal_int_grid: @@ -400,14 +406,14 @@ def negf_compute(self,scf_require=False,Vbias=None): getattr(self.deviceprop, ll).self_energy( energy=e, kpoint=k, - eta_lead=self.jdata["eta_lead"], - method=self.jdata["sgf_solver"] + eta_lead=self.eta_lead, + method=self.sgf_solver ) # self.out[str(ll)+"_se"][str(e.numpy())] = getattr(self.deviceprop, ll).se self.deviceprop.cal_green_function( energy=e, kpoint=k, - eta_device=self.jdata["eta_device"], + eta_device=self.eta_device, block_tridiagonal=self.block_tridiagonal, Vbias=Vbias ) From 491ecb09dbc8e515602af7bcce2b5d12f66aeb01 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 8 Apr 2024 14:56:47 +0800 Subject: [PATCH 092/209] add dptb/data/try_test.ipynb to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5bb7f95f..c937e765 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,4 @@ dmypy.json _date.py _version.py /.idea/ +dptb/data/try_test.ipynb From 8d4a33d6c03bca5508140fe59e22549808a941e6 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Apr 2024 20:04:53 +0800 Subject: [PATCH 093/209] add test data for negf --- .../test_negf_hamiltonian/run_input.json | 92 +++++++++++++++++++ .../data/test_negf/test_negf_run/chain.vasp | 20 ++++ .../data/test_negf/test_negf_run/nnsk_C.json | 9 ++ 3 files changed, 121 insertions(+) create mode 100644 dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json create mode 100644 dptb/tests/data/test_negf/test_negf_run/chain.vasp create mode 100644 dptb/tests/data/test_negf/test_negf_run/nnsk_C.json diff --git a/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json b/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json new file mode 100644 index 00000000..5d83d8af --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json @@ -0,0 +1,92 @@ +{ + "task_options": { + "task": "negf", + "scf": false, + "block_tridiagonal": false, + "ele_T": 300, + "unit": "eV", + "scf_options": { + "mode": "PDIIS", + "mixing_period": 3, + "step_size": 0.05, + "n_history": 6, + "abs_err": 1e-06, + "rel_err": 0.0001, + "max_iter": 100 + }, + "stru_options": { + "kmesh": [ + 1, + 1, + 1 + ], + "pbc": [ + false, + false, + false + ], + "device": { + "id": "4-8", + "sort": true + }, + "lead_L": { + "id": "0-4", + "voltage": 0.0 + }, + "lead_R": { + "id": "8-12", + "voltage": 0.0 + } + }, + "poisson_options": { + "solver": "fmm", + "err": 1e-05 + }, + "sgf_solver": "Sancho-Rubio", + "espacing": 0.1, + "emin": -2, + "emax": 2, + "e_fermi": -13.638587951660156, + "density_options": { + "method": "Ozaki", + "M_cut": 50, + "R": 1000000.0, + "n_gauss": 10 + }, + "eta_lead": 1e-05, + "eta_device": 0.0, + "out_dos": true, + "out_tc": true, + "out_ldos": true, + "out_current_nscf": true, + "out_density": false, + "out_potential": false, + "out_current": false, + "out_lcurrent": false + }, + "AtomicData_options": { + "r_max":2.0 + }, + "common_options": { + "basis":{"C": [ + "2s"]}, + "device":"cpu", + "dtype": "float32", + "overlap":false + }, + "model_options": { + "nnsk":{ + "onsite":{ + "method": "none" + }, + "hopping":{ + "method":"powerlaw", + "rs":1.6, + "w":0.3 + }, + "freeze":false, + "push":false, + "std":0.01 + } + } +} \ No newline at end of file diff --git a/dptb/tests/data/test_negf/test_negf_run/chain.vasp b/dptb/tests/data/test_negf/test_negf_run/chain.vasp new file mode 100644 index 00000000..81a1ab2c --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_run/chain.vasp @@ -0,0 +1,20 @@ +long_chain +1.0 + 10.000000000 0.0000000000 0.0000000000 + 0.0000000000 10.0000000000 0.0000000000 + 0.0000000000 0.0000000000 19.2000000000 + C + 12 +Cartesian + 0.000000000 0.000000000 3.200000000 + 0.000000000 0.000000000 4.800000000 + 0.000000000 0.000000000 0.000000000 + 0.000000000 0.000000000 1.600000000 + 0.000000000 0.000000000 6.400000000 + 0.000000000 0.000000000 8.000000000 + 0.000000000 0.000000000 9.600000000 + 0.000000000 0.000000000 11.20000000 + 0.000000000 0.000000000 12.80000000 + 0.000000000 0.000000000 14.40000000 + 0.000000000 0.000000000 16.00000000 + 0.000000000 0.000000000 17.60000000 \ No newline at end of file diff --git a/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json b/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json new file mode 100644 index 00000000..90d11a61 --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json @@ -0,0 +1,9 @@ +{ "version":1, + "onsite": {}, + "hopping": { + "C-C-2s-2s-0": [ + 0.04, + 1.0 + ] + } +} \ No newline at end of file From 00848ecd1f76c229d1d835a3a433efba8696053a Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Apr 2024 20:06:15 +0800 Subject: [PATCH 094/209] fix api for device_property and density_Ozaki --- dptb/tests/test_negf_density_Ozaki.py | 107 +++++----- dptb/tests/test_negf_device_property.py | 268 +++++++++++++----------- 2 files changed, 190 insertions(+), 185 deletions(-) diff --git a/dptb/tests/test_negf_density_Ozaki.py b/dptb/tests/test_negf_density_Ozaki.py index b0eb8de5..96948db7 100644 --- a/dptb/tests/test_negf_density_Ozaki.py +++ b/dptb/tests/test_negf_density_Ozaki.py @@ -1,9 +1,9 @@ # test_negf_density_Ozaki from dptb.negf.density import Ozaki from dptb.negf.device_property import DeviceProperty -from dptb.v1.init_nnsk import InitSKModel -from dptb.nnops.v1.NN2HRK import NN2HRK -from dptb.nnops.v1.apihost import NNSKHost + +from dptb.nn.build import build_model +import json from dptb.utils.tools import j_must_have from dptb.utils.tools import j_loader import numpy as np @@ -27,78 +27,69 @@ def root_directory(request): def test_negf_density_Ozaki(root_directory): model_ckpt=root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json' - jdata = root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json" + results_path=root_directory +"/dptb/tests/data/test_negf" + input_path = root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json" structure=root_directory +"/dptb/tests/data/test_negf/test_negf_run/chain.vasp" - log_path=root_directory +"/dptb/tests/data/test_negf/test_negf_Device/test.log" - - apihost = NNSKHost(checkpoint=model_ckpt, config=jdata) - apihost.register_plugin(InitSKModel()) - apihost.build() - apiHrk = NN2HRK(apihost=apihost, mode='nnsk') - jdata = j_loader(jdata) - task_options = j_must_have(jdata, "task_options") - - - run_opt = { - "run_sk": True, - "init_model":model_ckpt, - "results_path":root_directory +"/dptb/tests/data/test_negf", - "structure":structure, - "log_path": log_path, - "log_level": 5, - "use_correction":False - } - - structase=read(run_opt['structure']) - results_path=run_opt.get('results_path') - kpoints= kmesh_sampling(task_options["stru_options"]["kmesh"]) - ele_T = task_options["ele_T"] - kBT = Boltzmann * ele_T / eV2J - e_fermi = task_options["e_fermi"] - - hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) + # log_path=root_directory +"/dptb/tests/data/test_negf/test_negf_Device/test.log" + negf_json = json.load(open(input_path)) + model = build_model(model_ckpt,model_options=negf_json['model_options'],common_options=negf_json['common_options']) + + + hamiltonian = NEGFHamiltonianInit(model=model, + AtomicData_options=negf_json['AtomicData_options'], + structure=structure, + pbc_negf = negf_json['task_options']["stru_options"]['pbc'], + stru_options = negf_json['task_options']['stru_options'], + unit = negf_json['task_options']['unit'], + results_path=results_path, + torch_device = torch.device('cpu')) + + # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) + kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) with torch.no_grad(): struct_device, struct_leads = hamiltonian.initialize(kpoints=kpoints) - device = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=e_fermi) - device.set_leadLR( - lead_L=LeadProperty( - hamiltonian=hamiltonian, - tab="lead_L", - structure=struct_leads["lead_L"], - results_path=results_path, - e_T=ele_T, - efermi=e_fermi, - voltage=task_options["stru_options"]["lead_L"]["voltage"] - ), - lead_R=LeadProperty( - hamiltonian=hamiltonian, - tab="lead_R", - structure=struct_leads["lead_R"], - results_path=results_path, - e_T=ele_T, - efermi=e_fermi, - voltage=task_options["stru_options"]["lead_R"]["voltage"] - ) - ) + + + deviceprop = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=negf_json['task_options']['e_fermi']) + deviceprop.set_leadLR( + lead_L=LeadProperty( + hamiltonian=hamiltonian, + tab="lead_L", + structure=struct_leads["lead_L"], + results_path=results_path, + e_T=negf_json['task_options']['ele_T'], + efermi=negf_json['task_options']['e_fermi'], + voltage=negf_json['task_options']["stru_options"]["lead_L"]["voltage"] + ), + lead_R=LeadProperty( + hamiltonian=hamiltonian, + tab="lead_R", + structure=struct_leads["lead_R"], + results_path=results_path, + e_T=negf_json['task_options']['ele_T'], + efermi=negf_json['task_options']['e_fermi'], + voltage=negf_json['task_options']["stru_options"]["lead_R"]["voltage"] + ) + ) # check Ozaki - kpoints= kmesh_sampling(task_options["stru_options"]["kmesh"]) - density_options = j_must_have(task_options, "density_options") + kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) + density_options = j_must_have(negf_json['task_options'], "density_options") density = Ozaki(R=density_options["R"], M_cut=density_options["M_cut"], n_gauss=density_options["n_gauss"]) #compute_density - DM_eq, DM_neq = density.integrate(deviceprop=device, kpoint=kpoints[0]) + DM_eq, DM_neq = density.integrate(deviceprop=deviceprop, kpoint=kpoints[0]) DM_eq_standard = torch.tensor([[ 1.0000e+00, -6.3615e-01, 3.4565e-07, 2.1080e-01], [-6.3615e-01, 1.0000e+00, -6.3615e-01, 3.4565e-07], [ 3.4565e-07, -6.3615e-01, 1.0000e+00, -6.3615e-01], [ 2.1080e-01, 3.4565e-07, -6.3615e-01, 1.0000e+00]],dtype=torch.float64) - + assert np.array(abs(DM_eq_standard-DM_eq)<1e-5).all() assert DM_neq==0.0 - onsite_density=density.get_density_onsite(deviceprop=device,DM=DM_eq) + onsite_density=density.get_density_onsite(deviceprop=deviceprop,DM=DM_eq) onsite_density_standard = torch.tensor([[ 0.0000, 0.0000, 6.4000, 1.0000],[ 0.0000, 0.0000, 8.0000, 1.0000], [ 0.0000, 0.0000, 9.6000, 1.0000],[ 0.0000, 0.0000, 11.2000, 1.0000]], dtype=torch.float64) assert np.array(abs(onsite_density_standard-onsite_density)<1e-5).all() \ No newline at end of file diff --git a/dptb/tests/test_negf_device_property.py b/dptb/tests/test_negf_device_property.py index cf24e0e4..abaa2038 100644 --- a/dptb/tests/test_negf_device_property.py +++ b/dptb/tests/test_negf_device_property.py @@ -1,8 +1,8 @@ #test_negf_Device_set_leadLR from dptb.negf.device_property import DeviceProperty -from dptb.v1.init_nnsk import InitSKModel -from dptb.nnops.v1.NN2HRK import NN2HRK -from dptb.nnops.v1.apihost import NNSKHost +from dptb.nn.build import build_model +import json +from dptb.utils.make_kpoints import kmesh_sampling from dptb.utils.tools import j_must_have from dptb.utils.tools import j_loader import numpy as np @@ -23,163 +23,177 @@ def root_directory(request): def test_negf_Device(root_directory): model_ckpt=root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json' - jdata = root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json" + results_path=root_directory +"/dptb/tests/data/test_negf" + input_path = root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json" structure=root_directory +"/dptb/tests/data/test_negf/test_negf_run/chain.vasp" - log_path=root_directory +"/dptb/tests/data/test_negf/test_negf_Device/test.log" - - - # read input files and generate Hamiltonian - apihost = NNSKHost(checkpoint=model_ckpt, config=jdata) - apihost.register_plugin(InitSKModel()) - apihost.build() - apiHrk = NN2HRK(apihost=apihost, mode='nnsk') - jdata = j_loader(jdata) - task_options = j_must_have(jdata, "task_options") - - run_opt = { - "run_sk": True, - "init_model":model_ckpt, - "results_path":root_directory +"/dptb/tests/data/test_negf/", - "structure":structure, - "log_path": log_path, - "log_level": 5, - "use_correction":False - } - - - structase=read(run_opt['structure']) - results_path=run_opt.get('results_path') - kpoints=np.array([[0,0,0]]) - ele_T = task_options["ele_T"] - kBT = Boltzmann * ele_T / eV2J - e_fermi = task_options["e_fermi"] - - hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) + # log_path=root_directory +"/dptb/tests/data/test_negf/test_negf_Device/test.log" + negf_json = json.load(open(input_path)) + model = build_model(model_ckpt,model_options=negf_json['model_options'],common_options=negf_json['common_options']) + + + hamiltonian = NEGFHamiltonianInit(model=model, + AtomicData_options=negf_json['AtomicData_options'], + structure=structure, + pbc_negf = negf_json['task_options']["stru_options"]['pbc'], + stru_options = negf_json['task_options']['stru_options'], + unit = negf_json['task_options']['unit'], + results_path=results_path, + torch_device = torch.device('cpu')) + + # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) + kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) with torch.no_grad(): struct_device, struct_leads = hamiltonian.initialize(kpoints=kpoints) - - device = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=e_fermi) - device.set_leadLR( - lead_L=LeadProperty( - hamiltonian=hamiltonian, - tab="lead_L", - structure=struct_leads["lead_L"], - results_path=results_path, - e_T=ele_T, - efermi=e_fermi, - voltage=task_options["stru_options"]["lead_L"]["voltage"] - ), - lead_R=LeadProperty( - hamiltonian=hamiltonian, - tab="lead_R", - structure=struct_leads["lead_R"], - results_path=results_path, - e_T=ele_T, - efermi=e_fermi, - voltage=task_options["stru_options"]["lead_R"]["voltage"] - ) - ) + + deviceprop = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=negf_json['task_options']['e_fermi']) + deviceprop.set_leadLR( + lead_L=LeadProperty( + hamiltonian=hamiltonian, + tab="lead_L", + structure=struct_leads["lead_L"], + results_path=results_path, + e_T=negf_json['task_options']['ele_T'], + efermi=negf_json['task_options']['e_fermi'], + voltage=negf_json['task_options']["stru_options"]["lead_L"]["voltage"] + ), + lead_R=LeadProperty( + hamiltonian=hamiltonian, + tab="lead_R", + structure=struct_leads["lead_R"], + results_path=results_path, + e_T=negf_json['task_options']['ele_T'], + efermi=negf_json['task_options']['e_fermi'], + voltage=negf_json['task_options']["stru_options"]["lead_R"]["voltage"] + ) + ) # check device.Lead_L.structure - assert all(device.lead_L.structure.symbols=='C4') - assert device.lead_L.structure.pbc[0]==False - assert device.lead_L.structure.pbc[1]==False - assert device.lead_L.structure.pbc[2]==True - assert np.diag(np.array((device.lead_L.structure.cell-[10.0, 10.0, 6.4])<1e-4)).all() - assert device.lead_L.tab=="lead_L" - assert abs(device.mu+13.638587951660156)<1e-5 + assert all(deviceprop.lead_L.structure.symbols=='C4') + assert deviceprop.lead_L.structure.pbc[0]==False + assert deviceprop.lead_L.structure.pbc[1]==False + assert deviceprop.lead_L.structure.pbc[2]==True + assert np.diag(np.array((deviceprop.lead_L.structure.cell-[10.0, 10.0, 6.4])<1e-4)).all() + assert deviceprop.lead_L.tab=="lead_L" + assert abs(deviceprop.mu+13.638587951660156)<1e-5 # check device.Lead_R.structure - assert all(device.lead_R.structure.symbols=='C4') - assert device.lead_R.structure.pbc[0]==False - assert device.lead_R.structure.pbc[1]==False - assert device.lead_R.structure.pbc[2]==True - assert np.diag(np.array((device.lead_R.structure.cell-[10.0, 10.0, 6.4])<1e-4)).all() - assert device.lead_R.tab=="lead_R" + assert all(deviceprop.lead_R.structure.symbols=='C4') + assert deviceprop.lead_R.structure.pbc[0]==False + assert deviceprop.lead_R.structure.pbc[1]==False + assert deviceprop.lead_R.structure.pbc[2]==True + assert np.diag(np.array((deviceprop.lead_R.structure.cell-[10.0, 10.0, 6.4])<1e-4)).all() + assert deviceprop.lead_R.tab=="lead_R" # calculate Self energy and Green function - stru_options = j_must_have(task_options, "stru_options") + stru_options = j_must_have(negf_json['task_options'], "stru_options") leads = stru_options.keys() for ll in leads: if ll.startswith("lead"): #calculate surface green function at E=0 - getattr(device, ll).self_energy( + getattr(deviceprop, ll).self_energy( energy=torch.tensor([0]), kpoint=kpoints[0], - eta_lead=task_options["eta_lead"], - method=task_options["sgf_solver"] + eta_lead=negf_json['task_options']["eta_lead"], + method=negf_json['task_options']["sgf_solver"] ) # check left and right leads' self-energy - lead_L_se_standard=torch.tensor([[-3.3171e-07-0.6096j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], + lead_L_se_standard=torch.tensor([[0.0000e+00-0.61690134j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j]], dtype=torch.complex128) lead_R_se_standard=torch.tensor([[ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], - [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,-3.3171e-07-0.6096j]], dtype=torch.complex128) + [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00-0.51628502j]], dtype=torch.complex128) - assert abs(device.lead_L.se-lead_L_se_standard).max()<1e-5 - assert abs(device.lead_R.se-lead_R_se_standard).max()<1e-5 + + assert abs(deviceprop.lead_L.se-lead_L_se_standard).max()<1e-5 + assert abs(deviceprop.lead_R.se-lead_R_se_standard).max()<1e-5 - device.cal_green_function( energy=torch.tensor([0]), #calculate device green function at E=0 + deviceprop.cal_green_function( energy=torch.tensor([0]), #calculate device green function at E=0 kpoint=kpoints[0], - eta_device=task_options["eta_device"], - block_tridiagonal=task_options["block_tridiagonal"] + eta_device=negf_json['task_options']["eta_device"], + block_tridiagonal=negf_json['task_options']["block_tridiagonal"] ) #check green functions' results - assert list(device.greenfuncs.keys())==['g_trans', 'grd', 'grl', 'gru', 'gr_left', 'gnd', 'gnl',\ + assert list(deviceprop.greenfuncs.keys())==['g_trans', 'grd', 'grl', 'gru', 'gr_left', 'gnd', 'gnl',\ 'gnu', 'gin_left', 'gpd', 'gpl', 'gpu', 'gip_left'] - g_trans= torch.tensor([[ 1.0983e-11-8.2022e-01j, -8.2022e-01+4.4634e-07j,8.9264e-07+8.2022e-01j, 8.2022e-01-1.3390e-06j], - [-8.2022e-01+4.4634e-07j, -3.6607e-12-8.2022e-01j,-8.2021e-01+4.4631e-07j, 8.9264e-07+8.2022e-01j], - [ 8.9264e-07+8.2022e-01j, -8.2021e-01+4.4631e-07j,-3.6607e-12-8.2022e-01j, -8.2022e-01+4.4634e-07j], - [ 8.2022e-01-1.3390e-06j, 8.9264e-07+8.2022e-01j,-8.2022e-01+4.4634e-07j, 1.0983e-11-8.2022e-01j]],dtype=torch.complex128) - grd= [torch.tensor([[ 1.0983e-11-8.2022e-01j, -8.2022e-01+4.4634e-07j,8.9264e-07+8.2022e-01j, 8.2022e-01-1.3390e-06j], - [-8.2022e-01+4.4634e-07j, -3.6607e-12-8.2022e-01j,-8.2021e-01+4.4631e-07j, 8.9264e-07+8.2022e-01j], - [ 8.9264e-07+8.2022e-01j, -8.2021e-01+4.4631e-07j,-3.6607e-12-8.2022e-01j, -8.2022e-01+4.4634e-07j], - [ 8.2022e-01-1.3390e-06j, 8.9264e-07+8.2022e-01j,-8.2022e-01+4.4634e-07j, 1.0983e-11-8.2022e-01j]],dtype=torch.complex128)] - - assert abs(g_trans-device.greenfuncs['g_trans']).max()<1e-5 - assert abs(grd[0]-device.greenfuncs['grd'][0]).max()<1e-5 - assert device.greenfuncs['grl'] == [] - assert device.greenfuncs['gru'] == [] - - gr_left= [torch.tensor([[ 1.0983e-11-8.2022e-01j, -8.2022e-01+4.4634e-07j,8.9264e-07+8.2022e-01j, 8.2022e-01-1.3390e-06j], - [-8.2022e-01+4.4634e-07j, -3.6607e-12-8.2022e-01j,-8.2021e-01+4.4631e-07j, 8.9264e-07+8.2022e-01j], - [ 8.9264e-07+8.2022e-01j, -8.2021e-01+4.4631e-07j,-3.6607e-12-8.2022e-01j, -8.2022e-01+4.4634e-07j], - [ 8.2022e-01-1.3390e-06j, 8.9264e-07+8.2022e-01j,-8.2022e-01+4.4634e-07j, 1.0983e-11-8.2022e-01j]],dtype=torch.complex128)] - - gnd = [torch.tensor([[ 8.2022e-01+0.0000e+00j, -4.4634e-07+2.2204e-16j,-8.2022e-01-3.1764e-22j, 1.3390e-06-5.5511e-17j], - [-4.4634e-07-2.7756e-16j, 8.2022e-01+2.6470e-23j, -4.4631e-07+2.7756e-16j, -8.2022e-01-2.3823e-22j], - [-8.2022e-01+2.9117e-22j, -4.4631e-07-2.2204e-16j, 8.2022e-01+7.9409e-23j, -4.4634e-07+1.1102e-16j], - [ 1.3390e-06+5.5511e-17j, -8.2022e-01+2.1176e-22j, -4.4634e-07-1.1102e-16j, 8.2022e-01+0.0000e+00j]],dtype=torch.complex128)] - - assert abs(gr_left[0]-device.greenfuncs['gr_left'][0]).max()<1e-5 - assert abs(gnd[0]-device.greenfuncs['gnd'][0]).max()<1e-5 - assert device.greenfuncs['gnl'] == [] - assert device.greenfuncs['gnu'] == [] - - gin_left=[torch.tensor([[ 8.2022e-01+0.0000e+00j, -4.4634e-07+2.2204e-16j, -8.2022e-01-3.1764e-22j, 1.3390e-06-5.5511e-17j], - [-4.4634e-07-2.7756e-16j, 8.2022e-01+2.6470e-23j, -4.4631e-07+2.7756e-16j, -8.2022e-01-2.3823e-22j], - [-8.2022e-01+2.9117e-22j, -4.4631e-07-2.2204e-16j, 8.2022e-01+7.9409e-23j, -4.4634e-07+1.1102e-16j], - [ 1.3390e-06+5.5511e-17j, -8.2022e-01+2.1176e-22j,-4.4634e-07-1.1102e-16j, 8.2022e-01+0.0000e+00j]],dtype=torch.complex128)] - assert abs(gin_left[0]-device.greenfuncs['gin_left'][0]).max()<1e-5 - - assert device.greenfuncs['gpd']== None - assert device.greenfuncs['gpl']== None - assert device.greenfuncs['gpu']== None - assert device.greenfuncs['gip_left']== None - - Tc=device._cal_tc_() #transmission - assert abs(Tc-1)<1e-5 - - dos = device._cal_dos_() - dos_standard = torch.tensor(2.0887, dtype=torch.float64) + g_trans= torch.tensor([[ 0.00000000-0.74812411j, -0.88334131-0.00000000j, + 0.00000000+0.74812499j, 0.88333889+0.00000000j], + [-0.88334131-0.00000000j, 0.00000000-0.89392734j, + -0.75709057-0.00000000j, -0.00000000+0.89392489j], + [-0.00000000+0.74812499j, -0.75709057-0.00000000j, + 0.00000000-0.74812586j, -0.88333993-0.00000000j], + [ 0.88333889+0.00000000j, 0.00000000+0.89392489j, + -0.88333993+0.00000000j, -0.00000000-0.89392244j]],dtype=torch.complex128) + + grd= [torch.tensor([[ 0.00000000-0.74812411j, -0.88334131-0.00000000j, + 0.00000000+0.74812499j, 0.88333889+0.00000000j], + [-0.88334131-0.00000000j, 0.00000000-0.89392734j, + -0.75709057-0.00000000j, -0.00000000+0.89392489j], + [-0.00000000+0.74812499j, -0.75709057-0.00000000j, + 0.00000000-0.74812586j, -0.88333993-0.00000000j], + [ 0.88333889+0.00000000j, 0.00000000+0.89392489j, + -0.88333993+0.00000000j, -0.00000000-0.89392244j]],dtype=torch.complex128)] + + assert abs(g_trans-deviceprop.greenfuncs['g_trans']).max()<1e-5 + assert abs(grd[0]-deviceprop.greenfuncs['grd'][0]).max()<1e-5 + assert deviceprop.greenfuncs['grl'] == [] + assert deviceprop.greenfuncs['gru'] == [] + + gr_left= [torch.tensor([[ 0.00000000-0.74812411j, -0.88334131-0.00000000j, + 0.00000000+0.74812499j, 0.88333889+0.00000000j], + [-0.88334131-0.00000000j, 0.00000000-0.89392734j, + -0.75709057-0.00000000j, -0.00000000+0.89392489j], + [-0.00000000+0.74812499j, -0.75709057-0.00000000j, + 0.00000000-0.74812586j, -0.88333993-0.00000000j], + [ 0.88333889+0.00000000j, 0.00000000+0.89392489j, + -0.88333993+0.00000000j, -0.00000000-0.89392244j]], + dtype=torch.complex128)] + + gnd = [torch.tensor([[ 0.74812411+0.00000000e+00j, 0.00000000-5.55111512e-17j, + -0.74812499+0.00000000e+00j, 0.00000000+5.55111512e-17j], + [ 0.00000000+1.11022302e-16j, 0.89392734+0.00000000e+00j, + 0.00000000+5.55111512e-17j, -0.89392489+0.00000000e+00j], + [-0.74812499+0.00000000e+00j, 0.00000000-1.11022302e-16j, + 0.74812586+0.00000000e+00j, 0.00000000+1.11022302e-16j], + [ 0.00000000-5.55111512e-17j, -0.89392489+0.00000000e+00j, + 0.00000000-1.11022302e-16j, 0.89392244+0.00000000e+00j]],dtype=torch.complex128)] + + assert abs(gr_left[0]-deviceprop.greenfuncs['gr_left'][0]).max()<1e-5 + assert abs(gnd[0]-deviceprop.greenfuncs['gnd'][0]).max()<1e-5 + assert deviceprop.greenfuncs['gnl'] == [] + assert deviceprop.greenfuncs['gnu'] == [] + + gin_left=[torch.tensor([[ 0.74812411+0.00000000e+00j, 0.00000000-5.55111512e-17j, + -0.74812499+0.00000000e+00j, 0.00000000+5.55111512e-17j], + [ 0.00000000+1.11022302e-16j, 0.89392734+0.00000000e+00j, + 0.00000000+5.55111512e-17j, -0.89392489+0.00000000e+00j], + [-0.74812499+0.00000000e+00j, 0.00000000-1.11022302e-16j, + 0.74812586+0.00000000e+00j, 0.00000000+1.11022302e-16j], + [ 0.00000000-5.55111512e-17j, -0.89392489+0.00000000e+00j, + 0.00000000-1.11022302e-16j, 0.89392244+0.00000000e+00j]],dtype=torch.complex128)] + + + assert abs(gin_left[0]-deviceprop.greenfuncs['gin_left'][0]).max()<1e-5 + + assert deviceprop.greenfuncs['gpd']== None + assert deviceprop.greenfuncs['gpl']== None + assert deviceprop.greenfuncs['gpu']== None + assert deviceprop.greenfuncs['gip_left']== None + + Tc=deviceprop._cal_tc_() #transmission + assert abs(Tc-1)<1e-2 + + dos = deviceprop._cal_dos_() + dos_standard = torch.tensor(2.090723, dtype=torch.float64) assert abs(dos-dos_standard)<1e-4 - ldos = device._cal_ldos_() + ldos = deviceprop._cal_ldos_() + torch.set_printoptions(precision=6) + print(ldos) ldos_standard = torch.tensor([0.2611, 0.2611, 0.2611, 0.2611], dtype=torch.float64)*2 assert abs(ldos_standard-ldos).max()<1e-4 From c6d45336770c42bed714d69275bfe7582135c174 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Apr 2024 20:06:54 +0800 Subject: [PATCH 095/209] fix hamilotonian_init temporarily --- dptb/tests/test_negf_negf_hamiltonian_init.py | 186 +++++++++++++++--- 1 file changed, 157 insertions(+), 29 deletions(-) diff --git a/dptb/tests/test_negf_negf_hamiltonian_init.py b/dptb/tests/test_negf_negf_hamiltonian_init.py index 13da5abe..0a8178c1 100644 --- a/dptb/tests/test_negf_negf_hamiltonian_init.py +++ b/dptb/tests/test_negf_negf_hamiltonian_init.py @@ -1,7 +1,10 @@ # Hamiltonian -from dptb.v1.init_nnsk import InitSKModel -from dptb.nnops.v1.NN2HRK import NN2HRK -from dptb.nnops.v1.apihost import NNSKHost +from dptb.utils.make_kpoints import kmesh_sampling +from dptb.negf.device_property import DeviceProperty +from dptb.negf.lead_property import LeadProperty +from dptb.nn.build import build_model +import json +from dptb.utils.tools import j_must_have from dptb.utils.tools import j_must_have from dptb.utils.tools import j_loader import numpy as np @@ -22,35 +25,51 @@ def root_directory(request): def test_negf_Hamiltonian(root_directory): model_ckpt=root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json' - jdata = root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json" + results_path=root_directory +"/dptb/tests/data/test_negf" + input_path = root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json" structure=root_directory +"/dptb/tests/data/test_negf/test_negf_run/chain.vasp" - log_path=root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/test.log" - - apihost = NNSKHost(checkpoint=model_ckpt, config=jdata) - apihost.register_plugin(InitSKModel()) - apihost.build() - apiHrk = NN2HRK(apihost=apihost, mode='nnsk') - jdata = j_loader(jdata) - task_options = j_must_have(jdata, "task_options") - - run_opt = { - "run_sk": True, - "init_model":model_ckpt, - "results_path":root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/", - "structure":structure, - "log_path": log_path, - "log_level": 5, - "use_correction":False - } - - - structase=read(run_opt['structure']) - results_path=run_opt.get('results_path') - kpoints=np.array([[0,0,0]]) - - hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) + # log_path=root_directory +"/dptb/tests/data/test_negf/test_negf_Device/test.log" + negf_json = json.load(open(input_path)) + model = build_model(model_ckpt,model_options=negf_json['model_options'],common_options=negf_json['common_options']) + + + hamiltonian = NEGFHamiltonianInit(model=model, + AtomicData_options=negf_json['AtomicData_options'], + structure=structure, + pbc_negf = negf_json['task_options']["stru_options"]['pbc'], + stru_options = negf_json['task_options']['stru_options'], + unit = negf_json['task_options']['unit'], + results_path=results_path, + torch_device = torch.device('cpu')) + + # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) + kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) with torch.no_grad(): struct_device, struct_leads = hamiltonian.initialize(kpoints=kpoints) + + + deviceprop = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=negf_json['task_options']['e_fermi']) + deviceprop.set_leadLR( + lead_L=LeadProperty( + hamiltonian=hamiltonian, + tab="lead_L", + structure=struct_leads["lead_L"], + results_path=results_path, + e_T=negf_json['task_options']['ele_T'], + efermi=negf_json['task_options']['e_fermi'], + voltage=negf_json['task_options']["stru_options"]["lead_L"]["voltage"] + ), + lead_R=LeadProperty( + hamiltonian=hamiltonian, + tab="lead_R", + structure=struct_leads["lead_R"], + results_path=results_path, + e_T=negf_json['task_options']['ele_T'], + efermi=negf_json['task_options']['e_fermi'], + voltage=negf_json['task_options']["stru_options"]["lead_R"]["voltage"] + ) + ) + #check device's Hamiltonian assert all(struct_device.symbols=="C4") @@ -126,4 +145,113 @@ def test_negf_Hamiltonian(root_directory): assert na == 4 assert hamiltonian.device_norbs==device_norbs_standard + +# def _test_negf_Hamiltonian(root_directory): + +# model_ckpt=root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json' +# jdata = root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json" +# structure=root_directory +"/dptb/tests/data/test_negf/test_negf_run/chain.vasp" +# log_path=root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/test.log" + +# apihost = NNSKHost(checkpoint=model_ckpt, config=jdata) +# apihost.register_plugin(InitSKModel()) +# apihost.build() +# apiHrk = NN2HRK(apihost=apihost, mode='nnsk') +# jdata = j_loader(jdata) +# task_options = j_must_have(jdata, "task_options") + +# run_opt = { +# "run_sk": True, +# "init_model":model_ckpt, +# "results_path":root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/", +# "structure":structure, +# "log_path": log_path, +# "log_level": 5, +# "use_correction":False +# } + + +# structase=read(run_opt['structure']) +# results_path=run_opt.get('results_path') +# kpoints=np.array([[0,0,0]]) + +# hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) +# with torch.no_grad(): +# struct_device, struct_leads = hamiltonian.initialize(kpoints=kpoints) + +# #check device's Hamiltonian +# assert all(struct_device.symbols=="C4") +# assert all(struct_device.pbc)==False +# assert np.diag(np.array(struct_device.cell==[10.0, 10.0, 19.2])).all() + +# #check lead_L's Hamiltonian + +# assert all(struct_leads["lead_L"].symbols=="C4") +# assert struct_leads["lead_L"].pbc[0]==False +# assert struct_leads["lead_L"].pbc[1]==False +# assert struct_leads["lead_L"].pbc[2]==True +# assert np.diag(np.array(struct_leads["lead_L"].cell==[10.0, 10.0, -6.4])).all() + +# #check lead_R's Hamiltonian + +# assert all(struct_leads["lead_R"].symbols=="C4") +# assert struct_leads["lead_R"].pbc[0]==False +# assert struct_leads["lead_R"].pbc[1]==False +# assert struct_leads["lead_R"].pbc[2]==True +# assert np.diag(np.array(struct_leads["lead_R"].cell==[10.0, 10.0, 6.4])).all() + + +# #check hs_device +# h_device = hamiltonian.get_hs_device(kpoint=np.array([0,0,0]),V=0,block_tridiagonal=False)[0][0] +# print(hamiltonian.get_hs_device(kpoint=np.array([0,0,0]),V=0,block_tridiagonal=False)[0]) +# h_device_standard = torch.tensor([[-13.6386+0.j, 0.6096+0.j, 0.0000+0.j, 0.0000+0.j], +# [ 0.6096+0.j, -13.6386+0.j, 0.6096+0.j, 0.0000+0.j], +# [ 0.0000+0.j, 0.6096+0.j, -13.6386+0.j, 0.6096+0.j], +# [ 0.0000+0.j, 0.0000+0.j, 0.6096+0.j, -13.6386+0.j]],dtype=torch.complex128) +# assert abs(h_device-h_device_standard).max()<1e-4 + +# s_device = hamiltonian.get_hs_device(kpoint=np.array([0,0,0]),V=0,block_tridiagonal=False)[1][0] +# print(hamiltonian.get_hs_device(kpoint=np.array([0,0,0]),V=0,block_tridiagonal=False)[1][0]) +# s_standard = torch.eye(4) +# assert abs(s_device-s_standard).max()<1e-5 + + + +# #check hs_lead +# hl_lead = hamiltonian.get_hs_lead(kpoint=np.array([0,0,0]),tab="lead_L",v=0)[0][0] +# hl_lead_standard = torch.tensor([-13.6386+0.j, 0.6096+0.j], dtype=torch.complex128) +# assert abs(hl_lead-hl_lead_standard).max()<1e-4 + +# hll_lead = hamiltonian.get_hs_lead(kpoint=np.array([0,0,0]),tab="lead_L",v=0)[1][0] +# hll_lead_standard = torch.tensor([0.0000+0.j, 0.6096+0.j], dtype=torch.complex128) +# print(hll_lead) +# assert abs(hll_lead-hll_lead_standard).max()<1e-4 + +# hDL_lead = hamiltonian.get_hs_lead(kpoint=np.array([0,0,0]),tab="lead_L",v=0)[2] +# hDL_lead_standard = torch.tensor([[0.0000+0.j, 0.6096+0.j], +# [0.0000+0.j, 0.0000+0.j], +# [0.0000+0.j, 0.0000+0.j], +# [0.0000+0.j, 0.0000+0.j]], dtype=torch.complex128) +# assert abs(hDL_lead-hDL_lead_standard).max()<1e-5 + +# sl_lead = hamiltonian.get_hs_lead(kpoint=np.array([0,0,0]),tab="lead_L",v=0)[3] +# sl_lead_standard = torch.eye(2) +# assert abs(sl_lead-sl_lead_standard).max()<1e-5 + +# sll_lead = hamiltonian.get_hs_lead(kpoint=np.array([0,0,0]),tab="lead_L",v=0)[4] +# sll_lead_standard = torch.zeros(2) +# assert abs(sll_lead-sll_lead_standard).max()<1e-5 + +# sDL_lead = hamiltonian.get_hs_lead(kpoint=np.array([0,0,0]),tab="lead_L",v=0)[5] +# sDL_lead_standard = torch.zeros([4,2]) +# assert abs(sDL_lead-sDL_lead_standard).max()<1e-5 + + +# # check device norbs +# na = len(hamiltonian.device_norbs) +# device_norbs_standard=[1,1,1,1] +# assert na == 4 +# assert hamiltonian.device_norbs==device_norbs_standard + + From 5a5acfabab1cc820e35c93afe85826f493a2943c Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Apr 2024 20:23:18 +0800 Subject: [PATCH 096/209] add pyamg() and scipy() in argcheck --- dptb/utils/argcheck.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 5e27e00c..da1c8be9 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -1006,6 +1006,20 @@ def fmm(): Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err) ] +def pyamg(): + doc_err = "" + + return [ + Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err) + ] + +def scipy(): + doc_err = "" + + return [ + Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err) + ] + def run_options(): doc_task = "the task to run, includes: band, dos, pdos, FS2D, FS3D, ifermi" doc_structure = "the structure to run the task" From 516e5272b8939070571023d13f7fcf7e66d0aceb Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Apr 2024 21:07:12 +0800 Subject: [PATCH 097/209] add soc in test_negf input --- dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json b/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json index 5d83d8af..11751291 100644 --- a/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json +++ b/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json @@ -86,7 +86,8 @@ }, "freeze":false, "push":false, - "std":0.01 + "std":0.01, + "soc":{} } } } \ No newline at end of file From 81a1b8427e4016414da432544e9fc109246fb13f Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Apr 2024 21:21:24 +0800 Subject: [PATCH 098/209] add new model update to test_tbtrans_init.py temporarily --- dptb/tests/test_tbtrans_init.py | 36 +++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/dptb/tests/test_tbtrans_init.py b/dptb/tests/test_tbtrans_init.py index 390e16f9..968ec26b 100644 --- a/dptb/tests/test_tbtrans_init.py +++ b/dptb/tests/test_tbtrans_init.py @@ -1,7 +1,9 @@ import pytest -from dptb.nnops.apihost import NNSKHost -from dptb.plugins.init_nnsk import InitSKModel -from dptb.nnops.NN2HRK import NN2HRK +import json +from dptb.negf.device_property import DeviceProperty +from dptb.nn.build import build_model +from dptb.negf.negf_hamiltonian_init import NEGFHamiltonianInit +import torch from dptb.postprocess.tbtrans_init import TBTransInputSet from dptb.utils.tools import j_loader import numpy as np @@ -20,11 +22,29 @@ def test_tbtrans_init(root_directory): pytest.skip('sisl is not installed which is necessary for TBtrans Input Generation. Therefore, skipping test_tbtrans_init.') model_ckpt = f'{root_directory}/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300.json' - config = f'{root_directory}/dptb/tests/data/test_tbtrans/negf_tbtrans.json' - apihost = NNSKHost(checkpoint=model_ckpt, config=config) - apihost.register_plugin(InitSKModel()) - apihost.build() - apiHrk = NN2HRK(apihost=apihost, mode='nnsk') + results_path=f'{root_directory}/dptb/tests/data/test_tbtrans/' + input_path = root_directory +"/dptb/tests/data/test_tbtrans/negf_tbtrans.json" + structure=root_directory +"/dptb/tests/data/test_tbtrans/structure_tbtrans.vasp" + + + negf_json = json.load(open(input_path)) + model = build_model(model_ckpt,model_options=negf_json['model_options'],common_options=negf_json['common_options']) + + + hamiltonian = NEGFHamiltonianInit(model=model, + AtomicData_options=negf_json['AtomicData_options'], + structure=structure, + pbc_negf = negf_json['task_options']["stru_options"]['pbc'], + stru_options = negf_json['task_options']['stru_options'], + unit = negf_json['task_options']['unit'], + results_path=results_path, + torch_device = torch.device('cpu')) + + + # apihost = NNSKHost(checkpoint=model_ckpt, config=config) + # apihost.register_plugin(InitSKModel()) + # apihost.build() + # apiHrk = NN2HRK(apihost=apihost, mode='nnsk') run_opt = { "run_sk": True, From d98b591e3dbb056d93b1c65c204e027c9371e079 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Apr 2024 21:28:16 +0800 Subject: [PATCH 099/209] comment import from tbtrans_init temporarily --- dptb/tests/test_tbtrans_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/tests/test_tbtrans_init.py b/dptb/tests/test_tbtrans_init.py index 968ec26b..e92c7eb2 100644 --- a/dptb/tests/test_tbtrans_init.py +++ b/dptb/tests/test_tbtrans_init.py @@ -4,7 +4,7 @@ from dptb.nn.build import build_model from dptb.negf.negf_hamiltonian_init import NEGFHamiltonianInit import torch -from dptb.postprocess.tbtrans_init import TBTransInputSet +# from dptb.postprocess.tbtrans_init import TBTransInputSet from dptb.utils.tools import j_loader import numpy as np From 560031f9d15102d68ab5fafbe5adae7847341a45 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Apr 2024 21:28:53 +0800 Subject: [PATCH 100/209] modify test_tbtrans files for v2 --- .../best_nnsk_b3.600_c3.600_w0.300.json | 122 ++++++++++++++++++ .../tests/data/test_tbtrans/negf_tbtrans.json | 85 ++++++++++++ .../test_tbtrans/test_hBN_zigzag_struct.xyz | 22 ++++ 3 files changed, 229 insertions(+) create mode 100644 dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300.json create mode 100644 dptb/tests/data/test_tbtrans/negf_tbtrans.json create mode 100644 dptb/tests/data/test_tbtrans/test_hBN_zigzag_struct.xyz diff --git a/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300.json b/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300.json new file mode 100644 index 00000000..7fddb9d8 --- /dev/null +++ b/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300.json @@ -0,0 +1,122 @@ +{ + "onsite": { + "N-N-2s-2s-0": [ + 0.02462027035653591, + 0.007205560803413391 + ], + "N-N-2s-2p-0": [ + 0.008309782482683659, + -0.007032226305454969 + ], + "N-N-2p-2p-0": [ + 0.012606431730091572, + 0.010783562436699867 + ], + "N-N-2p-2p-1": [ + 0.0068643586710095406, + -0.011892829090356827 + ], + "N-B-2s-2s-0": [ + 0.041020166128873825, + -0.007834071293473244 + ], + "N-B-2s-2p-0": [ + 26.214815139770508, + -28.22139549255371 + ], + "N-B-2p-2p-0": [ + 0.2541739046573639, + 0.3701082468032837 + ], + "N-B-2p-2p-1": [ + -0.052932459861040115, + 0.03325718641281128 + ], + "B-N-2s-2s-0": [ + -0.10863762348890305, + -0.10621777176856995 + ], + "B-N-2s-2p-0": [ + 24.486974716186523, + 26.85447883605957 + ], + "B-N-2p-2p-0": [ + 0.13345032930374146, + -0.3127709925174713 + ], + "B-N-2p-2p-1": [ + -0.03844165802001953, + 0.00011800970969488844 + ], + "B-B-2s-2s-0": [ + -0.007172069512307644, + 0.007495054975152016 + ], + "B-B-2s-2p-0": [ + 0.004442985635250807, + 0.0030813836492598057 + ], + "B-B-2p-2p-0": [ + -0.0013882589992135763, + -6.591381679754704e-05 + ], + "B-B-2p-2p-1": [ + -0.009361814707517624, + -0.017272837460041046 + ] + }, + "hopping": { + "N-N-2s-2s-0": [ + 0.05967297405004501, + -0.21457917988300323 + ], + "N-N-2s-2p-0": [ + 0.042178086936473846, + 0.5767796635627747 + ], + "N-N-2p-2p-0": [ + 0.1008036881685257, + 0.5027011632919312 + ], + "N-N-2p-2p-1": [ + -0.005753065925091505, + -1.0040007829666138 + ], + "N-B-2s-2s-0": [ + 0.06605493277311325, + 2.485130786895752 + ], + "N-B-2s-2p-0": [ + -0.19711704552173615, + -1.884203314781189 + ], + "N-B-2p-2s-0": [ + -0.06678403168916702, + -2.8326284885406494 + ], + "N-B-2p-2p-0": [ + -0.2129121571779251, + 3.601222276687622 + ], + "N-B-2p-2p-1": [ + 0.034046269953250885, + -4.79115104675293 + ], + "B-B-2s-2s-0": [ + -0.061647042632102966, + -0.4071168601512909 + ], + "B-B-2s-2p-0": [ + 0.048091448843479156, + 0.8385105729103088 + ], + "B-B-2p-2p-0": [ + -0.11344866454601288, + 0.5754562020301819 + ], + "B-B-2p-2p-1": [ + 0.007624409161508083, + -0.7790966033935547 + ] + } +} \ No newline at end of file diff --git a/dptb/tests/data/test_tbtrans/negf_tbtrans.json b/dptb/tests/data/test_tbtrans/negf_tbtrans.json new file mode 100644 index 00000000..8abee871 --- /dev/null +++ b/dptb/tests/data/test_tbtrans/negf_tbtrans.json @@ -0,0 +1,85 @@ +{ + "common_options": { + "basis":{"N": [ + "2s", + "2p" + ], + "B": [ + "2s", + "2p" + ]}, + "device":"cpu", + "dtype": "float32", + "overlap":false + }, + "model_options": { + "nnsk":{ + "onsite":{ + "method":"strain", + "rs": 6.0, + "w": 0.1 + }, + "hopping":{ + "method":"powerlaw", + "rs":3.6, + "w":0.3 + }, + "freeze":false, + "push":false + } + }, + "structure":"./test_hBN_zigzag_struct.xyz", + "task_options": + { + "task": "tbtrans_negf", + "scf": true, + "block_tridiagonal": false, + "ele_T": 500, + "unit": "eV", + "scf_options":{ + "mode": "PDIIS", + "mixing_period": 3, + "step_size": 0.05, + "n_history": 6, + "abs_err": 1e-6, + "rel_err": 1e-4, + "max_iter": 100 + }, + "stru_options":{ + "pbc":[false, true, false], + "kmesh":[1,1,1], + "device":{ + "id":"8-12", + "sort": true + }, + "lead_L":{ + "id":"0-8", + "voltage":0.0 + }, + "lead_R":{ + "id":"12-20", + "voltage":0.0 + } + }, + "poisson_options": { + "solver": "fmm", + "err": 1e-5 + }, + "sgf_solver": "Sancho-Rubio", + "espacing": 0.1, + "emin": -2, + "emax": 2, + "e_fermi": -9.874357223510742, + "density_options":{ + "method": "Ozaki" + }, + "eta_lead":1e-5, + "eta_device":0.0, + "out_dos": true, + "out_tc": true, + "out_ldos": true, + "out_current_nscf": true, + "out_density": true, + "out_lcurrent": true + } +} diff --git a/dptb/tests/data/test_tbtrans/test_hBN_zigzag_struct.xyz b/dptb/tests/data/test_tbtrans/test_hBN_zigzag_struct.xyz new file mode 100644 index 00000000..f05a9679 --- /dev/null +++ b/dptb/tests/data/test_tbtrans/test_hBN_zigzag_struct.xyz @@ -0,0 +1,22 @@ +20 +Lattice="30.0 0.0 0.0 0.0 4.337055133 0.0 0.0 0.0 12.519999742500001" Properties=species:S:1:pos:R:3 pbc="T T T" +N 15.00000000 3.97563387 0.62599999 +B 15.00000000 2.52994883 0.62599999 +N 15.00000000 1.80710631 1.87799996 +B 15.00000000 0.36142126 1.87799996 +N 15.00000000 3.97563387 3.12999994 +B 15.00000000 2.52994883 3.12999994 +N 15.00000000 1.80710631 4.38199991 +B 15.00000000 0.36142126 4.38199991 +N 15.00000000 3.97563387 5.63399988 +B 15.00000000 2.52994883 5.63399988 +N 15.00000000 1.80710631 6.88599986 +B 15.00000000 0.36142126 6.88599986 +N 15.00000000 3.97563387 8.13799983 +B 15.00000000 2.52994883 8.13799983 +N 15.00000000 1.80710631 9.38999981 +B 15.00000000 0.36142126 9.38999981 +N 15.00000000 3.97563387 10.64199978 +B 15.00000000 2.52994883 10.64199978 +N 15.00000000 1.80710631 11.89399976 +B 15.00000000 0.36142126 11.89399976 From 78eb02a8e3ce97081ed87554bee00fc2c135d196 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Apr 2024 21:41:46 +0800 Subject: [PATCH 101/209] update test_negf_hamiltonian_init.py --- dptb/tests/test_negf_negf_hamiltonian_init.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dptb/tests/test_negf_negf_hamiltonian_init.py b/dptb/tests/test_negf_negf_hamiltonian_init.py index 0a8178c1..4eba408f 100644 --- a/dptb/tests/test_negf_negf_hamiltonian_init.py +++ b/dptb/tests/test_negf_negf_hamiltonian_init.py @@ -145,6 +145,10 @@ def test_negf_Hamiltonian(root_directory): assert na == 4 assert hamiltonian.device_norbs==device_norbs_standard + + + + # def _test_negf_Hamiltonian(root_directory): From 5fd133a0d2f79887f8cdb9a9579c9af3282f4eaf Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Apr 2024 21:56:35 +0800 Subject: [PATCH 102/209] modify test_negf_device_property --- dptb/tests/test_negf_device_property.py | 148 ++++++++++-------------- 1 file changed, 62 insertions(+), 86 deletions(-) diff --git a/dptb/tests/test_negf_device_property.py b/dptb/tests/test_negf_device_property.py index abaa2038..8eb10e1b 100644 --- a/dptb/tests/test_negf_device_property.py +++ b/dptb/tests/test_negf_device_property.py @@ -85,115 +85,91 @@ def test_negf_Device(root_directory): assert deviceprop.lead_R.tab=="lead_R" - # calculate Self energy and Green function - stru_options = j_must_have(negf_json['task_options'], "stru_options") + # calculate Self energy and Green function + task_options = negf_json['task_options'] + device = deviceprop + + stru_options = j_must_have(task_options, "stru_options") leads = stru_options.keys() for ll in leads: if ll.startswith("lead"): #calculate surface green function at E=0 - getattr(deviceprop, ll).self_energy( + getattr(device, ll).self_energy( energy=torch.tensor([0]), kpoint=kpoints[0], - eta_lead=negf_json['task_options']["eta_lead"], - method=negf_json['task_options']["sgf_solver"] + eta_lead=task_options["eta_lead"], + method=task_options["sgf_solver"] ) # check left and right leads' self-energy - lead_L_se_standard=torch.tensor([[0.0000e+00-0.61690134j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], + lead_L_se_standard=torch.tensor([[-3.3171e-07-0.6096j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j]], dtype=torch.complex128) lead_R_se_standard=torch.tensor([[ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], - [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00-0.51628502j]], dtype=torch.complex128) + [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,-3.3171e-07-0.6096j]], dtype=torch.complex128) - - assert abs(deviceprop.lead_L.se-lead_L_se_standard).max()<1e-5 - assert abs(deviceprop.lead_R.se-lead_R_se_standard).max()<1e-5 + assert abs(device.lead_L.se-lead_L_se_standard).max()<1e-5 + assert abs(device.lead_R.se-lead_R_se_standard).max()<1e-5 - deviceprop.cal_green_function( energy=torch.tensor([0]), #calculate device green function at E=0 + device.cal_green_function( energy=torch.tensor([0]), #calculate device green function at E=0 kpoint=kpoints[0], - eta_device=negf_json['task_options']["eta_device"], - block_tridiagonal=negf_json['task_options']["block_tridiagonal"] + eta_device=task_options["eta_device"], + block_tridiagonal=task_options["block_tridiagonal"] ) #check green functions' results - assert list(deviceprop.greenfuncs.keys())==['g_trans', 'grd', 'grl', 'gru', 'gr_left', 'gnd', 'gnl',\ + assert list(device.greenfuncs.keys())==['g_trans', 'grd', 'grl', 'gru', 'gr_left', 'gnd', 'gnl',\ 'gnu', 'gin_left', 'gpd', 'gpl', 'gpu', 'gip_left'] - g_trans= torch.tensor([[ 0.00000000-0.74812411j, -0.88334131-0.00000000j, - 0.00000000+0.74812499j, 0.88333889+0.00000000j], - [-0.88334131-0.00000000j, 0.00000000-0.89392734j, - -0.75709057-0.00000000j, -0.00000000+0.89392489j], - [-0.00000000+0.74812499j, -0.75709057-0.00000000j, - 0.00000000-0.74812586j, -0.88333993-0.00000000j], - [ 0.88333889+0.00000000j, 0.00000000+0.89392489j, - -0.88333993+0.00000000j, -0.00000000-0.89392244j]],dtype=torch.complex128) - - grd= [torch.tensor([[ 0.00000000-0.74812411j, -0.88334131-0.00000000j, - 0.00000000+0.74812499j, 0.88333889+0.00000000j], - [-0.88334131-0.00000000j, 0.00000000-0.89392734j, - -0.75709057-0.00000000j, -0.00000000+0.89392489j], - [-0.00000000+0.74812499j, -0.75709057-0.00000000j, - 0.00000000-0.74812586j, -0.88333993-0.00000000j], - [ 0.88333889+0.00000000j, 0.00000000+0.89392489j, - -0.88333993+0.00000000j, -0.00000000-0.89392244j]],dtype=torch.complex128)] - - assert abs(g_trans-deviceprop.greenfuncs['g_trans']).max()<1e-5 - assert abs(grd[0]-deviceprop.greenfuncs['grd'][0]).max()<1e-5 - assert deviceprop.greenfuncs['grl'] == [] - assert deviceprop.greenfuncs['gru'] == [] - - gr_left= [torch.tensor([[ 0.00000000-0.74812411j, -0.88334131-0.00000000j, - 0.00000000+0.74812499j, 0.88333889+0.00000000j], - [-0.88334131-0.00000000j, 0.00000000-0.89392734j, - -0.75709057-0.00000000j, -0.00000000+0.89392489j], - [-0.00000000+0.74812499j, -0.75709057-0.00000000j, - 0.00000000-0.74812586j, -0.88333993-0.00000000j], - [ 0.88333889+0.00000000j, 0.00000000+0.89392489j, - -0.88333993+0.00000000j, -0.00000000-0.89392244j]], - dtype=torch.complex128)] - - gnd = [torch.tensor([[ 0.74812411+0.00000000e+00j, 0.00000000-5.55111512e-17j, - -0.74812499+0.00000000e+00j, 0.00000000+5.55111512e-17j], - [ 0.00000000+1.11022302e-16j, 0.89392734+0.00000000e+00j, - 0.00000000+5.55111512e-17j, -0.89392489+0.00000000e+00j], - [-0.74812499+0.00000000e+00j, 0.00000000-1.11022302e-16j, - 0.74812586+0.00000000e+00j, 0.00000000+1.11022302e-16j], - [ 0.00000000-5.55111512e-17j, -0.89392489+0.00000000e+00j, - 0.00000000-1.11022302e-16j, 0.89392244+0.00000000e+00j]],dtype=torch.complex128)] - - assert abs(gr_left[0]-deviceprop.greenfuncs['gr_left'][0]).max()<1e-5 - assert abs(gnd[0]-deviceprop.greenfuncs['gnd'][0]).max()<1e-5 - assert deviceprop.greenfuncs['gnl'] == [] - assert deviceprop.greenfuncs['gnu'] == [] - - gin_left=[torch.tensor([[ 0.74812411+0.00000000e+00j, 0.00000000-5.55111512e-17j, - -0.74812499+0.00000000e+00j, 0.00000000+5.55111512e-17j], - [ 0.00000000+1.11022302e-16j, 0.89392734+0.00000000e+00j, - 0.00000000+5.55111512e-17j, -0.89392489+0.00000000e+00j], - [-0.74812499+0.00000000e+00j, 0.00000000-1.11022302e-16j, - 0.74812586+0.00000000e+00j, 0.00000000+1.11022302e-16j], - [ 0.00000000-5.55111512e-17j, -0.89392489+0.00000000e+00j, - 0.00000000-1.11022302e-16j, 0.89392244+0.00000000e+00j]],dtype=torch.complex128)] - - - assert abs(gin_left[0]-deviceprop.greenfuncs['gin_left'][0]).max()<1e-5 - - assert deviceprop.greenfuncs['gpd']== None - assert deviceprop.greenfuncs['gpl']== None - assert deviceprop.greenfuncs['gpu']== None - assert deviceprop.greenfuncs['gip_left']== None - - Tc=deviceprop._cal_tc_() #transmission - assert abs(Tc-1)<1e-2 - - dos = deviceprop._cal_dos_() - dos_standard = torch.tensor(2.090723, dtype=torch.float64) + g_trans= torch.tensor([[ 1.0983e-11-8.2022e-01j, -8.2022e-01+4.4634e-07j,8.9264e-07+8.2022e-01j, 8.2022e-01-1.3390e-06j], + [-8.2022e-01+4.4634e-07j, -3.6607e-12-8.2022e-01j,-8.2021e-01+4.4631e-07j, 8.9264e-07+8.2022e-01j], + [ 8.9264e-07+8.2022e-01j, -8.2021e-01+4.4631e-07j,-3.6607e-12-8.2022e-01j, -8.2022e-01+4.4634e-07j], + [ 8.2022e-01-1.3390e-06j, 8.9264e-07+8.2022e-01j,-8.2022e-01+4.4634e-07j, 1.0983e-11-8.2022e-01j]],dtype=torch.complex128) + grd= [torch.tensor([[ 1.0983e-11-8.2022e-01j, -8.2022e-01+4.4634e-07j,8.9264e-07+8.2022e-01j, 8.2022e-01-1.3390e-06j], + [-8.2022e-01+4.4634e-07j, -3.6607e-12-8.2022e-01j,-8.2021e-01+4.4631e-07j, 8.9264e-07+8.2022e-01j], + [ 8.9264e-07+8.2022e-01j, -8.2021e-01+4.4631e-07j,-3.6607e-12-8.2022e-01j, -8.2022e-01+4.4634e-07j], + [ 8.2022e-01-1.3390e-06j, 8.9264e-07+8.2022e-01j,-8.2022e-01+4.4634e-07j, 1.0983e-11-8.2022e-01j]],dtype=torch.complex128)] + + assert abs(g_trans-device.greenfuncs['g_trans']).max()<1e-5 + assert abs(grd[0]-device.greenfuncs['grd'][0]).max()<1e-5 + assert device.greenfuncs['grl'] == [] + assert device.greenfuncs['gru'] == [] + + gr_left= [torch.tensor([[ 1.0983e-11-8.2022e-01j, -8.2022e-01+4.4634e-07j,8.9264e-07+8.2022e-01j, 8.2022e-01-1.3390e-06j], + [-8.2022e-01+4.4634e-07j, -3.6607e-12-8.2022e-01j,-8.2021e-01+4.4631e-07j, 8.9264e-07+8.2022e-01j], + [ 8.9264e-07+8.2022e-01j, -8.2021e-01+4.4631e-07j,-3.6607e-12-8.2022e-01j, -8.2022e-01+4.4634e-07j], + [ 8.2022e-01-1.3390e-06j, 8.9264e-07+8.2022e-01j,-8.2022e-01+4.4634e-07j, 1.0983e-11-8.2022e-01j]],dtype=torch.complex128)] + + gnd = [torch.tensor([[ 8.2022e-01+0.0000e+00j, -4.4634e-07+2.2204e-16j,-8.2022e-01-3.1764e-22j, 1.3390e-06-5.5511e-17j], + [-4.4634e-07-2.7756e-16j, 8.2022e-01+2.6470e-23j, -4.4631e-07+2.7756e-16j, -8.2022e-01-2.3823e-22j], + [-8.2022e-01+2.9117e-22j, -4.4631e-07-2.2204e-16j, 8.2022e-01+7.9409e-23j, -4.4634e-07+1.1102e-16j], + [ 1.3390e-06+5.5511e-17j, -8.2022e-01+2.1176e-22j, -4.4634e-07-1.1102e-16j, 8.2022e-01+0.0000e+00j]],dtype=torch.complex128)] + + assert abs(gr_left[0]-device.greenfuncs['gr_left'][0]).max()<1e-5 + assert abs(gnd[0]-device.greenfuncs['gnd'][0]).max()<1e-5 + assert device.greenfuncs['gnl'] == [] + assert device.greenfuncs['gnu'] == [] + + gin_left=[torch.tensor([[ 8.2022e-01+0.0000e+00j, -4.4634e-07+2.2204e-16j, -8.2022e-01-3.1764e-22j, 1.3390e-06-5.5511e-17j], + [-4.4634e-07-2.7756e-16j, 8.2022e-01+2.6470e-23j, -4.4631e-07+2.7756e-16j, -8.2022e-01-2.3823e-22j], + [-8.2022e-01+2.9117e-22j, -4.4631e-07-2.2204e-16j, 8.2022e-01+7.9409e-23j, -4.4634e-07+1.1102e-16j], + [ 1.3390e-06+5.5511e-17j, -8.2022e-01+2.1176e-22j,-4.4634e-07-1.1102e-16j, 8.2022e-01+0.0000e+00j]],dtype=torch.complex128)] + assert abs(gin_left[0]-device.greenfuncs['gin_left'][0]).max()<1e-5 + + assert device.greenfuncs['gpd']== None + assert device.greenfuncs['gpl']== None + assert device.greenfuncs['gpu']== None + assert device.greenfuncs['gip_left']== None + + Tc=device._cal_tc_() #transmission + assert abs(Tc-1)<1e-5 + + dos = device._cal_dos_() + dos_standard = torch.tensor(2.0887, dtype=torch.float64) assert abs(dos-dos_standard)<1e-4 - ldos = deviceprop._cal_ldos_() - torch.set_printoptions(precision=6) - print(ldos) + ldos = device._cal_ldos_() ldos_standard = torch.tensor([0.2611, 0.2611, 0.2611, 0.2611], dtype=torch.float64)*2 assert abs(ldos_standard-ldos).max()<1e-4 From b3c7da948e46a3bdc6eecc16a331c467e5f87a0b Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Apr 2024 21:57:46 +0800 Subject: [PATCH 103/209] add h_device compare --- dptb/tests/test_negf_negf_hamiltonian_init.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dptb/tests/test_negf_negf_hamiltonian_init.py b/dptb/tests/test_negf_negf_hamiltonian_init.py index 4eba408f..2b9c1226 100644 --- a/dptb/tests/test_negf_negf_hamiltonian_init.py +++ b/dptb/tests/test_negf_negf_hamiltonian_init.py @@ -100,7 +100,8 @@ def test_negf_Hamiltonian(root_directory): [ 0.6096+0.j, -13.6386+0.j, 0.6096+0.j, 0.0000+0.j], [ 0.0000+0.j, 0.6096+0.j, -13.6386+0.j, 0.6096+0.j], [ 0.0000+0.j, 0.0000+0.j, 0.6096+0.j, -13.6386+0.j]],dtype=torch.complex128) - assert abs(h_device-h_device_standard).max()<1e-4 + print(abs(h_device-h_device_standard).max()) + assert abs(h_device-h_device_standard).max()>1e-4 s_device = hamiltonian.get_hs_device(kpoint=np.array([0,0,0]),V=0,block_tridiagonal=False)[1][0] print(hamiltonian.get_hs_device(kpoint=np.array([0,0,0]),V=0,block_tridiagonal=False)[1][0]) From b7b26595087646ef14568d12e13cbf8aa2d919d4 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 11 Apr 2024 22:24:06 +0800 Subject: [PATCH 104/209] update negf_hamiltonian_init --- dptb/tests/test_negf_negf_hamiltonian_init.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dptb/tests/test_negf_negf_hamiltonian_init.py b/dptb/tests/test_negf_negf_hamiltonian_init.py index 2b9c1226..4eba408f 100644 --- a/dptb/tests/test_negf_negf_hamiltonian_init.py +++ b/dptb/tests/test_negf_negf_hamiltonian_init.py @@ -100,8 +100,7 @@ def test_negf_Hamiltonian(root_directory): [ 0.6096+0.j, -13.6386+0.j, 0.6096+0.j, 0.0000+0.j], [ 0.0000+0.j, 0.6096+0.j, -13.6386+0.j, 0.6096+0.j], [ 0.0000+0.j, 0.0000+0.j, 0.6096+0.j, -13.6386+0.j]],dtype=torch.complex128) - print(abs(h_device-h_device_standard).max()) - assert abs(h_device-h_device_standard).max()>1e-4 + assert abs(h_device-h_device_standard).max()<1e-4 s_device = hamiltonian.get_hs_device(kpoint=np.array([0,0,0]),V=0,block_tridiagonal=False)[1][0] print(hamiltonian.get_hs_device(kpoint=np.array([0,0,0]),V=0,block_tridiagonal=False)[1][0]) From dcd60544c2d386666dd9adc5a935b2d7b2f7d1a9 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 13 Apr 2024 10:56:31 +0800 Subject: [PATCH 105/209] add print ldos in test_negf_device_property --- dptb/tests/test_negf_device_property.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dptb/tests/test_negf_device_property.py b/dptb/tests/test_negf_device_property.py index 8eb10e1b..a616e928 100644 --- a/dptb/tests/test_negf_device_property.py +++ b/dptb/tests/test_negf_device_property.py @@ -109,7 +109,8 @@ def test_negf_Device(root_directory): [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,-3.3171e-07-0.6096j]], dtype=torch.complex128) - + print('device.lead_L.se:',device.lead_L.se) + print('device.lead_R.se:',device.lead_R.se) assert abs(device.lead_L.se-lead_L_se_standard).max()<1e-5 assert abs(device.lead_R.se-lead_R_se_standard).max()<1e-5 @@ -170,7 +171,10 @@ def test_negf_Device(root_directory): assert abs(dos-dos_standard)<1e-4 ldos = device._cal_ldos_() + torch.set_printoptions(precision=8) + print('ldos:',ldos) ldos_standard = torch.tensor([0.2611, 0.2611, 0.2611, 0.2611], dtype=torch.float64)*2 + assert abs(ldos_standard-ldos).max()<1e-4 From 55467997b3ca9d61dc96ccfd8175790fa02517c7 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 13 Apr 2024 15:07:52 +0800 Subject: [PATCH 106/209] add eta_lead option in density_integrate_Fiori --- dptb/negf/density.py | 4 +++- dptb/postprocess/NEGF.py | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index 564a6e77..fa05d6e5 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -240,7 +240,9 @@ def __init__(self, n_gauss): self.wlg = None self.e_grid_Fiori = None - def density_integrate_Fiori(self,e_grid,kpoint,Vbias,integrate_way,deviceprop,device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): + def density_integrate_Fiori(self,e_grid,kpoint,Vbias,integrate_way,deviceprop, + device_atom_norbs,potential_at_atom,free_charge, + eta_lead=1e-5, eta_device=1e-5): if integrate_way == "gauss": if self.xs is None: self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index e52ff679..9da0174c 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -378,7 +378,10 @@ def negf_compute(self,scf_require=False,Vbias=None): deviceprop=self.deviceprop, device_atom_norbs=self.device_atom_norbs, potential_at_atom = self.potential_at_atom, - free_charge = self.free_charge) + free_charge = self.free_charge, + eta_lead = self.eta_lead, + eta_device = self.eta_device + ) else: # TODO: add Ozaki support for NanoTCAD-style SCF raise ValueError("Ozaki method does not support Poisson-NEGF SCF in this version.") From 0466ba0d923662c15a671736bdc40dd2e748f182 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 13 Apr 2024 15:08:50 +0800 Subject: [PATCH 107/209] modify pbc and add dtype float 64 in negf_hamiltonian_init.py --- dptb/negf/negf_hamiltonian_init.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 88b07bfd..aae98640 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -67,7 +67,7 @@ def __init__(self, overlap: bool=False, torch_device: Union[str, torch.device]=torch.device('cpu') ) -> None: - + torch.set_default_dtype(torch.float64) if isinstance(torch_device, str): torch_device = torch.device(torch_device) self.torch_device = torch_device @@ -85,7 +85,7 @@ def __init__(self, raise ValueError('structure must be ase.Atoms or str') self.structase.set_pbc(pbc_negf) alldata = AtomicData.from_ase(self.structase, **self.AtomicData_options) - # alldata[AtomicDataDict.PBC_KEY][2] = True + alldata[AtomicDataDict.PBC_KEY][2] = True alldata = AtomicData.to_AtomicDataDict(alldata.to(self.torch_device)) self.alldata = self.model.idp(alldata) @@ -173,11 +173,14 @@ def initialize(self, kpoints, block_tridiagnal=False): self.alldata[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_device["kpoints"], dtype=self.model.dtype, device=self.torch_device) self.alldata = self.model(self.alldata) # remove the edges corresponding to z-direction pbc for HR2HK - # for ip,p in enumerate(self.pbc_negf): - # if not p: - # mask = self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip] == 0 - # self.alldata[AtomicDataDict.EDGE_INDEX_KEY] = self.alldata[AtomicDataDict.EDGE_INDEX_KEY][:,mask] - # self.alldata[AtomicDataDict.EDGE_FEATURES_KEY] = self.alldata[AtomicDataDict.EDGE_FEATURES_KEY][mask] + for ip,p in enumerate(self.pbc_negf): + if not p: + mask = self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip] == 0 + self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] + self.alldata[AtomicDataDict.EDGE_INDEX_KEY] = self.alldata[AtomicDataDict.EDGE_INDEX_KEY][:,mask] + self.alldata[AtomicDataDict.EDGE_FEATURES_KEY] = self.alldata[AtomicDataDict.EDGE_FEATURES_KEY][mask] + + self.alldata = self.h2k(self.alldata) HK = self.alldata[AtomicDataDict.HAMILTONIAN_KEY] if self.overlap: @@ -190,9 +193,11 @@ def initialize(self, kpoints, block_tridiagnal=False): d_start = int(np.sum(self.h2k.atom_norbs[:device_id[0]])) d_end = int(np.sum(self.h2k.atom_norbs)-np.sum(self.h2k.atom_norbs[device_id[1]:])) HD, SD = HK[:,d_start:d_end, d_start:d_end], S[:, d_start:d_end, d_start:d_end] + Hall, Sall = HK, S if not block_tridiagnal: HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) + HS_device.update({"Hall":Hall.cdouble()*self.h_factor, "Sall":Sall.cdouble()}) else: hd, hu, hl, sd, su, sl = self.get_block_tridiagonal(HD*self.h_factor, SD) HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl}) @@ -221,7 +226,7 @@ def initialize(self, kpoints, block_tridiagnal=False): l_start = int(np.sum(self.h2k.atom_norbs[:lead_id[0]])) l_end = int(l_start + np.sum(self.h2k.atom_norbs[lead_id[0]:lead_id[1]]) / 2) - # HL, SL = HK[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian in the first principal layer + HL, SL = HK[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian in the first principal layer HDL, SDL = HK[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] # device and lead's hopping # HS_leads.update({ # "HL":HL.cdouble()*self.h_factor, @@ -278,7 +283,14 @@ def initialize(self, kpoints, block_tridiagnal=False): # h, s = self.apiH.get_HK(kpoints=kpoints) nL = int(HK_lead.shape[1] / 2) HLL, SLL = HK_lead[:, :nL, nL:], S_lead[:, :nL, nL:] # H_{L_first2L_second} - HL, SL = HK_lead[:,:nL,:nL], S[:,:nL,:nL] # lead hamiltonian in one principal layer + hL, sL = HK_lead[:,:nL,:nL], S[:,:nL,:nL] # lead hamiltonian in one principal layer + if np.abs((hL - HL).flatten()).max() > 1e-7: + log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") + raise RuntimeError + # if kk == "lead_L": + # HDL[0][0,1]=HLL[0][0,1] + # else: + # HDL[0][-1,0]=HLL[0][-1,0] HS_leads.update({ "HL":HL.cdouble()*self.h_factor, From 245813098e99369fde4a36493cf98b344e886a15 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 13 Apr 2024 15:22:05 +0800 Subject: [PATCH 108/209] add TODO comment in negf_hamitonian_init --- dptb/negf/negf_hamiltonian_init.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index aae98640..3b57b717 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -67,7 +67,10 @@ def __init__(self, overlap: bool=False, torch_device: Union[str, torch.device]=torch.device('cpu') ) -> None: + + # TODO: add dtype and device setting to the model torch.set_default_dtype(torch.float64) + if isinstance(torch_device, str): torch_device = torch.device(torch_device) self.torch_device = torch_device From 732453a9b13c79334dc701c6c22218a5347534ab Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 13 Apr 2024 16:01:52 +0800 Subject: [PATCH 109/209] add some comments --- dptb/negf/negf_hamiltonian_init.py | 501 ++++++++++++++--------------- 1 file changed, 235 insertions(+), 266 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 3b57b717..66d043c4 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -86,12 +86,6 @@ def __init__(self, self.structase = structure else: raise ValueError('structure must be ase.Atoms or str') - self.structase.set_pbc(pbc_negf) - alldata = AtomicData.from_ase(self.structase, **self.AtomicData_options) - alldata[AtomicDataDict.PBC_KEY][2] = True - - alldata = AtomicData.to_AtomicDataDict(alldata.to(self.torch_device)) - self.alldata = self.model.idp(alldata) self.unit = unit self.stru_options = stru_options @@ -171,8 +165,13 @@ def initialize(self, kpoints, block_tridiagnal=False): # projatoms = self.apiH.structure.projatoms # self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] # self.apiH.get_HR() + + self.structase.set_pbc(self.pbc_negf) + alldata = AtomicData.from_ase(self.structase, **self.AtomicData_options) + alldata[AtomicDataDict.PBC_KEY][2] = True # force pbc in z-axis to get reasonable chemical environment in two ends - + alldata = AtomicData.to_AtomicDataDict(alldata.to(self.torch_device)) + self.alldata = self.model.idp(alldata) self.alldata[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_device["kpoints"], dtype=self.model.dtype, device=self.torch_device) self.alldata = self.model(self.alldata) # remove the edges corresponding to z-direction pbc for HR2HK @@ -183,7 +182,6 @@ def initialize(self, kpoints, block_tridiagnal=False): self.alldata[AtomicDataDict.EDGE_INDEX_KEY] = self.alldata[AtomicDataDict.EDGE_INDEX_KEY][:,mask] self.alldata[AtomicDataDict.EDGE_FEATURES_KEY] = self.alldata[AtomicDataDict.EDGE_FEATURES_KEY][mask] - self.alldata = self.h2k(self.alldata) HK = self.alldata[AtomicDataDict.HAMILTONIAN_KEY] if self.overlap: @@ -207,7 +205,7 @@ def initialize(self, kpoints, block_tridiagnal=False): torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) - # TODO: check structure_device is correct or not + structure_device = self.structase[device_id[0]:device_id[1]] structure_device.pbc = self.pbc_negf # structure_device = self.apiH.structure.projected_struct[self.device_id[0]:self.device_id[1]] @@ -218,8 +216,6 @@ def initialize(self, kpoints, block_tridiagnal=False): HS_leads = {} HS_leads["kpoints"] = kpoints stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] - # write(os.path.join(self.results_path, "stru_"+kk+".vasp"), stru_lead) - # self.apiH.update_struct(stru_lead, mode="lead", stru_options=self.stru_options.get(kk), pbc=self.stru_options["pbc"]) # update lead id n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]].sum() n_proj_atom_lead = np.array([1]*len(self.structase))[self.lead_ids[kk][0]:self.lead_ids[kk][1]].sum() @@ -229,14 +225,10 @@ def initialize(self, kpoints, block_tridiagnal=False): l_start = int(np.sum(self.h2k.atom_norbs[:lead_id[0]])) l_end = int(l_start + np.sum(self.h2k.atom_norbs[lead_id[0]:lead_id[1]]) / 2) - HL, SL = HK[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian in the first principal layer - HDL, SDL = HK[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] # device and lead's hopping - # HS_leads.update({ - # "HL":HL.cdouble()*self.h_factor, - # "SL":SL.cdouble(), - # "HDL":HDL.cdouble()*self.h_factor, - # "SDL":SDL.cdouble()} - # ) + # lead hamiltonian in the first principal layer(the layer close to the device) + HL, SL = HK[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] + # device and lead's hopping + HDL, SDL = HK[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] cell = np.array(stru_lead.cell)[:2] @@ -255,13 +247,8 @@ def initialize(self, kpoints, block_tridiagnal=False): pbc=pbc_lead) stru_lead.set_chemical_symbols(stru_lead.get_chemical_symbols()) structure_leads[kk] = stru_lead - - # for correct HLL, shutdown temporarily the z-direction pbc to get bondlist - # structure_leads[kk].pbc[2] = False + # get lead_data lead_data = AtomicData.from_ase(structure_leads[kk], **self.AtomicData_options) - # open z-pbc again for the complete chemical environment (like bulk) in leads - # lead_data[AtomicDataDict.PBC_KEY][2] = True - lead_data = AtomicData.to_AtomicDataDict(lead_data.to(self.torch_device)) lead_data = self.model.idp(lead_data) lead_data[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_leads["kpoints"], dtype=self.model.dtype, device=self.torch_device) @@ -283,18 +270,18 @@ def initialize(self, kpoints, block_tridiagnal=False): S_lead = torch.eye(HK_lead.shape[1], dtype=self.model.dtype, device=self.torch_device).unsqueeze(0).repeat(HK_lead.shape[0], 1, 1) - # h, s = self.apiH.get_HK(kpoints=kpoints) nL = int(HK_lead.shape[1] / 2) HLL, SLL = HK_lead[:, :nL, nL:], S_lead[:, :nL, nL:] # H_{L_first2L_second} hL, sL = HK_lead[:,:nL,:nL], S[:,:nL,:nL] # lead hamiltonian in one principal layer - if np.abs((hL - HL).flatten()).max() > 1e-7: + err_l = (HK_lead[:, :nL, :nL] - HL).abs().max() + if err_l >= 1e-4: + # check the lead hamiltonian get from device and lead calculation matches each other + # a standard check to see the lead environment is bulk-like or not log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") raise RuntimeError - # if kk == "lead_L": - # HDL[0][0,1]=HLL[0][0,1] - # else: - # HDL[0][-1,0]=HLL[0][-1,0] - + elif 1e-7 <= err_l <= 1e-4: + log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(err_l)) + HS_leads.update({ "HL":HL.cdouble()*self.h_factor, "SL":SL.cdouble(), @@ -303,25 +290,7 @@ def initialize(self, kpoints, block_tridiagnal=False): "HLL":HLL.cdouble()*self.h_factor, "SLL":SLL.cdouble() }) - - - - # err_l = (HK_lead[:, :nL, :nL] - HL).abs().max() - # if err_l >= 1e-4: - # # check the lead hamiltonian get from device and lead calculation matches each other - # # a standard check to see the lead environment is bulk-like or not - # log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") - # raise RuntimeError - # elif 1e-7 <= err_l <= 1e-4: - # log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(err_l)) - - # HS_leads.update({ - # "HLL":HLL.cdouble()*self.h_factor, - # "SLL":SLL.cdouble()} - # ) - - # HS_leads["kpoints"] = kpoints - + torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) return structure_device, structure_leads @@ -422,254 +391,254 @@ def device_norbs(self): -class _NEGFHamiltonianInit(object): - '''The Class for Hamiltonian object in negf module. +# class _NEGFHamiltonianInit(object): +# '''The Class for Hamiltonian object in negf module. - It is used to initialize and manipulate device and lead Hamiltonians for negf. - It is different from the Hamiltonian object in the dptb module. +# It is used to initialize and manipulate device and lead Hamiltonians for negf. +# It is different from the Hamiltonian object in the dptb module. - Property - ---------- - apiH: the API object for Hamiltonian - unit: the unit of energy - structase: the structure object for the device and leads - stru_options: the options for structure from input file - results_path: the path to store the results - - device_id: the start-atom id and end-atom id of the device in the structure file - lead_ids: the start-atom id and end-atom id of the leads in the structure file - - - Methods - ---------- - initialize: initializes the device and lead Hamiltonians - get_hs_device: get the device Hamiltonian and overlap matrix at a specific kpoint - get_hs_lead: get the lead Hamiltonian and overlap matrix at a specific kpoint +# Property +# ---------- +# apiH: the API object for Hamiltonian +# unit: the unit of energy +# structase: the structure object for the device and leads +# stru_options: the options for structure from input file +# results_path: the path to store the results + +# device_id: the start-atom id and end-atom id of the device in the structure file +# lead_ids: the start-atom id and end-atom id of the leads in the structure file + + +# Methods +# ---------- +# initialize: initializes the device and lead Hamiltonians +# get_hs_device: get the device Hamiltonian and overlap matrix at a specific kpoint +# get_hs_lead: get the lead Hamiltonian and overlap matrix at a specific kpoint - ''' - - def __init__(self, apiH, structase, stru_options, results_path) -> None: - self.apiH = apiH - self.unit = apiH.unit - self.structase = structase - self.stru_options = stru_options - self.results_path = results_path +# ''' + +# def __init__(self, apiH, structase, stru_options, results_path) -> None: +# self.apiH = apiH +# self.unit = apiH.unit +# self.structase = structase +# self.stru_options = stru_options +# self.results_path = results_path - self.device_id = [int(x) for x in self.stru_options.get("device")["id"].split("-")] - self.lead_ids = {} - for kk in self.stru_options: - if kk.startswith("lead"): - self.lead_ids[kk] = [int(x) for x in self.stru_options.get(kk)["id"].split("-")] - - if self.unit == "Hartree": - self.h_factor = 13.605662285137 * 2 - elif self.unit == "eV": - self.h_factor = 1. - elif self.unit == "Ry": - self.h_factor = 13.605662285137 - else: - log.error("The unit name is not correct !") - raise ValueError - - def initialize(self, kpoints, block_tridiagnal=False): - """initializes the device and lead Hamiltonians +# self.device_id = [int(x) for x in self.stru_options.get("device")["id"].split("-")] +# self.lead_ids = {} +# for kk in self.stru_options: +# if kk.startswith("lead"): +# self.lead_ids[kk] = [int(x) for x in self.stru_options.get(kk)["id"].split("-")] + +# if self.unit == "Hartree": +# self.h_factor = 13.605662285137 * 2 +# elif self.unit == "eV": +# self.h_factor = 1. +# elif self.unit == "Ry": +# self.h_factor = 13.605662285137 +# else: +# log.error("The unit name is not correct !") +# raise ValueError + +# def initialize(self, kpoints, block_tridiagnal=False): +# """initializes the device and lead Hamiltonians - construct device and lead Hamiltonians and return the structures respectively.The lead Hamiltonian - is k-resolved due to the transverse k point sampling. +# construct device and lead Hamiltonians and return the structures respectively.The lead Hamiltonian +# is k-resolved due to the transverse k point sampling. - Args: - kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) - block_tridiagnal: A boolean parameter that determines whether to block-tridiagonalize the - device Hamiltonian or not. +# Args: +# kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) +# block_tridiagnal: A boolean parameter that determines whether to block-tridiagonalize the +# device Hamiltonian or not. - Returns: - structure_device and structure_leads corresponding to the structure of device and leads. +# Returns: +# structure_device and structure_leads corresponding to the structure of device and leads. - Raises: - RuntimeError: if the lead hamiltonian attained from device and lead calculation does not match. +# Raises: +# RuntimeError: if the lead hamiltonian attained from device and lead calculation does not match. - """ - assert len(np.array(kpoints).shape) == 2 - - HS_device = {} - HS_leads = {} - HS_device["kpoints"] = kpoints - - self.apiH.update_struct(self.structase, mode="device", stru_options=j_must_have(self.stru_options, "device"), pbc=self.stru_options["pbc"]) - # change parameters to match the structure projection - n_proj_atom_pre = np.array([1]*len(self.structase))[:self.device_id[0]][self.apiH.structure.projatoms[:self.device_id[0]]].sum() - n_proj_atom_device = np.array([1]*len(self.structase))[self.device_id[0]:self.device_id[1]][self.apiH.structure.projatoms[self.device_id[0]:self.device_id[1]]].sum() - proj_device_id = [0,0] - proj_device_id[0] = n_proj_atom_pre - proj_device_id[1] = n_proj_atom_pre + n_proj_atom_device - self.proj_device_id = proj_device_id - projatoms = self.apiH.structure.projatoms - - self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] - self.apiH.get_HR() - # output the allbonds and hamil_block for check - # allbonds,hamil_block,_ =self.apiH.get_HR() - # torch.save(allbonds, os.path.join(self.results_path, "allbonds"+".pth")) - # torch.save(hamil_block, os.path.join(self.results_path, "hamil_block"+".pth")) - - H, S = self.apiH.get_HK(kpoints=kpoints) - d_start = int(np.sum(self.atom_norbs[:proj_device_id[0]])) - d_end = int(np.sum(self.atom_norbs)-np.sum(self.atom_norbs[proj_device_id[1]:])) - HD, SD = H[:,d_start:d_end, d_start:d_end], S[:, d_start:d_end, d_start:d_end] +# """ +# assert len(np.array(kpoints).shape) == 2 + +# HS_device = {} +# HS_leads = {} +# HS_device["kpoints"] = kpoints + +# self.apiH.update_struct(self.structase, mode="device", stru_options=j_must_have(self.stru_options, "device"), pbc=self.stru_options["pbc"]) +# # change parameters to match the structure projection +# n_proj_atom_pre = np.array([1]*len(self.structase))[:self.device_id[0]][self.apiH.structure.projatoms[:self.device_id[0]]].sum() +# n_proj_atom_device = np.array([1]*len(self.structase))[self.device_id[0]:self.device_id[1]][self.apiH.structure.projatoms[self.device_id[0]:self.device_id[1]]].sum() +# proj_device_id = [0,0] +# proj_device_id[0] = n_proj_atom_pre +# proj_device_id[1] = n_proj_atom_pre + n_proj_atom_device +# self.proj_device_id = proj_device_id +# projatoms = self.apiH.structure.projatoms + +# self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] +# self.apiH.get_HR() +# # output the allbonds and hamil_block for check +# # allbonds,hamil_block,_ =self.apiH.get_HR() +# # torch.save(allbonds, os.path.join(self.results_path, "allbonds"+".pth")) +# # torch.save(hamil_block, os.path.join(self.results_path, "hamil_block"+".pth")) + +# H, S = self.apiH.get_HK(kpoints=kpoints) +# d_start = int(np.sum(self.atom_norbs[:proj_device_id[0]])) +# d_end = int(np.sum(self.atom_norbs)-np.sum(self.atom_norbs[proj_device_id[1]:])) +# HD, SD = H[:,d_start:d_end, d_start:d_end], S[:, d_start:d_end, d_start:d_end] - if not block_tridiagnal: - HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) - else: - hd, hu, hl, sd, su, sl = self.get_block_tridiagonal(HD*self.h_factor, SD) - HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl}) - - torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) - structure_device = self.apiH.structure.projected_struct[self.device_id[0]:self.device_id[1]] +# if not block_tridiagnal: +# HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) +# else: +# hd, hu, hl, sd, su, sl = self.get_block_tridiagonal(HD*self.h_factor, SD) +# HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl}) + +# torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) +# structure_device = self.apiH.structure.projected_struct[self.device_id[0]:self.device_id[1]] - structure_leads = {} - for kk in self.stru_options: - if kk.startswith("lead"): - HS_leads = {} - stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] - # write(os.path.join(self.results_path, "stru_"+kk+".vasp"), stru_lead) - self.apiH.update_struct(stru_lead, mode="lead", stru_options=self.stru_options.get(kk), pbc=self.stru_options["pbc"]) - # update lead id - n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]][projatoms[:self.lead_ids[kk][0]]].sum() - n_proj_atom_lead = np.array([1]*len(self.structase))[self.lead_ids[kk][0]:self.lead_ids[kk][1]][projatoms[self.lead_ids[kk][0]:self.lead_ids[kk][1]]].sum() - proj_lead_id = [0,0] - proj_lead_id[0] = n_proj_atom_pre - proj_lead_id[1] = n_proj_atom_pre + n_proj_atom_lead - - l_start = int(np.sum(self.atom_norbs[:proj_lead_id[0]])) - l_end = int(l_start + np.sum(self.atom_norbs[proj_lead_id[0]:proj_lead_id[1]]) / 2) - HL, SL = H[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian in one principal layer - HDL, SDL = H[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] # device and lead's hopping - HS_leads.update({ - "HL":HL.cdouble()*self.h_factor, - "SL":SL.cdouble(), - "HDL":HDL.cdouble()*self.h_factor, - "SDL":SDL.cdouble()} - ) +# structure_leads = {} +# for kk in self.stru_options: +# if kk.startswith("lead"): +# HS_leads = {} +# stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] +# # write(os.path.join(self.results_path, "stru_"+kk+".vasp"), stru_lead) +# self.apiH.update_struct(stru_lead, mode="lead", stru_options=self.stru_options.get(kk), pbc=self.stru_options["pbc"]) +# # update lead id +# n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]][projatoms[:self.lead_ids[kk][0]]].sum() +# n_proj_atom_lead = np.array([1]*len(self.structase))[self.lead_ids[kk][0]:self.lead_ids[kk][1]][projatoms[self.lead_ids[kk][0]:self.lead_ids[kk][1]]].sum() +# proj_lead_id = [0,0] +# proj_lead_id[0] = n_proj_atom_pre +# proj_lead_id[1] = n_proj_atom_pre + n_proj_atom_lead + +# l_start = int(np.sum(self.atom_norbs[:proj_lead_id[0]])) +# l_end = int(l_start + np.sum(self.atom_norbs[proj_lead_id[0]:proj_lead_id[1]]) / 2) +# HL, SL = H[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian in one principal layer +# HDL, SDL = H[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] # device and lead's hopping +# HS_leads.update({ +# "HL":HL.cdouble()*self.h_factor, +# "SL":SL.cdouble(), +# "HDL":HDL.cdouble()*self.h_factor, +# "SDL":SDL.cdouble()} +# ) - structure_leads[kk] = self.apiH.structure.struct - self.apiH.get_HR() - # output the allbonds and hamil_block for check - # allbonds_lead,hamil_block_lead,_ = self.apiH.get_HR() - # torch.save(allbonds_lead, os.path.join(self.results_path, "allbonds_"+kk+".pth")) - # torch.save(hamil_block_lead, os.path.join(self.results_path, "hamil_block_"+kk+".pth")) - - h, s = self.apiH.get_HK(kpoints=kpoints) - nL = int(h.shape[1] / 2) - HLL, SLL = h[:, :nL, nL:], s[:, :nL, nL:] # H_{L_first2L_second} - err_l = (h[:, :nL, :nL] - HL).abs().max() - if err_l >= 1e-4: # check the lead hamiltonian get from device and lead calculation matches each other - log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") - raise RuntimeError - elif 1e-7 <= err_l <= 1e-4: - log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(err_l)) - - HS_leads.update({ - "HLL":HLL.cdouble()*self.h_factor, - "SLL":SLL.cdouble()} - ) +# structure_leads[kk] = self.apiH.structure.struct +# self.apiH.get_HR() +# # output the allbonds and hamil_block for check +# # allbonds_lead,hamil_block_lead,_ = self.apiH.get_HR() +# # torch.save(allbonds_lead, os.path.join(self.results_path, "allbonds_"+kk+".pth")) +# # torch.save(hamil_block_lead, os.path.join(self.results_path, "hamil_block_"+kk+".pth")) + +# h, s = self.apiH.get_HK(kpoints=kpoints) +# nL = int(h.shape[1] / 2) +# HLL, SLL = h[:, :nL, nL:], s[:, :nL, nL:] # H_{L_first2L_second} +# err_l = (h[:, :nL, :nL] - HL).abs().max() +# if err_l >= 1e-4: # check the lead hamiltonian get from device and lead calculation matches each other +# log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") +# raise RuntimeError +# elif 1e-7 <= err_l <= 1e-4: +# log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(err_l)) + +# HS_leads.update({ +# "HLL":HLL.cdouble()*self.h_factor, +# "SLL":SLL.cdouble()} +# ) - HS_leads["kpoints"] = kpoints +# HS_leads["kpoints"] = kpoints - torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) +# torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) - return structure_device, structure_leads +# return structure_device, structure_leads - def get_hs_device(self, kpoint, V, block_tridiagonal=False): - """ get the device Hamiltonian and overlap matrix at a specific kpoint +# def get_hs_device(self, kpoint, V, block_tridiagonal=False): +# """ get the device Hamiltonian and overlap matrix at a specific kpoint - In diagonalization mode, the Hamiltonian and overlap matrix are block tridiagonalized, - and hd,hu,hl refers to the diagnonal, upper and lower blocks of the Hamiltonian, respectively. - The same rules apply to sd, su, sl. +# In diagonalization mode, the Hamiltonian and overlap matrix are block tridiagonalized, +# and hd,hu,hl refers to the diagnonal, upper and lower blocks of the Hamiltonian, respectively. +# The same rules apply to sd, su, sl. - Args: - kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) - V: voltage bias - block_tridiagonal: a boolean flag that shows whether Hamiltonian has been diagonalized or not +# Args: +# kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) +# V: voltage bias +# block_tridiagonal: a boolean flag that shows whether Hamiltonian has been diagonalized or not - Returns: - if not diagonalized, return the whole Hamiltonian and Overlap HD-V*SD, SD - if diagonalized, return the block tridiagonalized Hamiltonian and Overlap component hd, hu, hl, - sd, su, sl. - """ - f = torch.load(os.path.join(self.results_path, "HS_device.pth")) - kpoints = f["kpoints"] - - ix = None - for i, k in enumerate(kpoints): - if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: - ix = i - break - - assert ix is not None - - if not block_tridiagonal: - HD, SD = f["HD"][ix], f["SD"][ix] - else: - hd, sd, hl, su, sl, hu = f["hd"][ix], f["sd"][ix], f["hl"][ix], f["su"][ix], f["sl"][ix], f["hu"][ix] +# Returns: +# if not diagonalized, return the whole Hamiltonian and Overlap HD-V*SD, SD +# if diagonalized, return the block tridiagonalized Hamiltonian and Overlap component hd, hu, hl, +# sd, su, sl. +# """ +# f = torch.load(os.path.join(self.results_path, "HS_device.pth")) +# kpoints = f["kpoints"] + +# ix = None +# for i, k in enumerate(kpoints): +# if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: +# ix = i +# break + +# assert ix is not None + +# if not block_tridiagonal: +# HD, SD = f["HD"][ix], f["SD"][ix] +# else: +# hd, sd, hl, su, sl, hu = f["hd"][ix], f["sd"][ix], f["hl"][ix], f["su"][ix], f["sl"][ix], f["hu"][ix] - if block_tridiagonal: - return hd, sd, hl, su, sl, hu - else: - # print('HD shape:', HD.shape) - # print('SD shape:', SD.shape) - # print('V shape:', V.shape) - log.info(msg='Device Hamiltonian shape: {0}x{0}'.format(HD.shape[0], HD.shape[1])) +# if block_tridiagonal: +# return hd, sd, hl, su, sl, hu +# else: +# # print('HD shape:', HD.shape) +# # print('SD shape:', SD.shape) +# # print('V shape:', V.shape) +# log.info(msg='Device Hamiltonian shape: {0}x{0}'.format(HD.shape[0], HD.shape[1])) - return [HD - V*SD], [SD], [], [], [], [] +# return [HD - V*SD], [SD], [], [], [], [] - def get_hs_lead(self, kpoint, tab, v): - """get the lead Hamiltonian and overlap matrix at a specific kpoint +# def get_hs_lead(self, kpoint, tab, v): +# """get the lead Hamiltonian and overlap matrix at a specific kpoint - In diagonalization mode, the Hamiltonian and overlap matrix are block tridiagonalized, - and hd,hu,hl refers to the diagnonal, upper and lower blocks of the Hamiltonian, respectively. - The same rules apply to sd, su, sl. +# In diagonalization mode, the Hamiltonian and overlap matrix are block tridiagonalized, +# and hd,hu,hl refers to the diagnonal, upper and lower blocks of the Hamiltonian, respectively. +# The same rules apply to sd, su, sl. - Args: - kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) - V: voltage bias - block_tridiagonal: a boolean flag that shows whether Hamiltonian has been diagonalized or not +# Args: +# kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) +# V: voltage bias +# block_tridiagonal: a boolean flag that shows whether Hamiltonian has been diagonalized or not - Returns: - if not diagonalized, return the whole Hamiltonian and Overlap HD-V*SD, SD - if diagonalized, return the block tridiagonalized Hamiltonian and Overlap component hd, hu, hl, - sd, su, sl. - """ - f = torch.load(os.path.join(self.results_path, "HS_{0}.pth".format(tab))) - kpoints = f["kpoints"] +# Returns: +# if not diagonalized, return the whole Hamiltonian and Overlap HD-V*SD, SD +# if diagonalized, return the block tridiagonalized Hamiltonian and Overlap component hd, hu, hl, +# sd, su, sl. +# """ +# f = torch.load(os.path.join(self.results_path, "HS_{0}.pth".format(tab))) +# kpoints = f["kpoints"] - ix = None - for i, k in enumerate(kpoints): - if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: - ix = i - break +# ix = None +# for i, k in enumerate(kpoints): +# if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: +# ix = i +# break - assert ix is not None +# assert ix is not None - hL, hLL, hDL, sL, sLL, sDL = f["HL"][ix], f["HLL"][ix], f["HDL"][ix], \ - f["SL"][ix], f["SLL"][ix], f["SDL"][ix] +# hL, hLL, hDL, sL, sLL, sDL = f["HL"][ix], f["HLL"][ix], f["HDL"][ix], \ +# f["SL"][ix], f["SLL"][ix], f["SDL"][ix] - return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL +# return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL - def attach_potential(): - pass +# def attach_potential(): +# pass - def write(self): - pass +# def write(self): +# pass - @property - def device_norbs(self): - """ - return the number of atoms in the device Hamiltonian - """ - return self.atom_norbs[self.device_id[0]:self.device_id[1]] +# @property +# def device_norbs(self): +# """ +# return the number of atoms in the device Hamiltonian +# """ +# return self.atom_norbs[self.device_id[0]:self.device_id[1]] - # def get_hs_block_tridiagonal(self, HD, SD): +# # def get_hs_block_tridiagonal(self, HD, SD): - # return hd, hu, hl, sd, su, sl +# # return hd, hu, hl, sd, su, sl From e0ad0d11d4b05e2269078b83536573275caf9800 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 13 Apr 2024 16:02:20 +0800 Subject: [PATCH 110/209] rename path --- dptb/tests/test_negf_negf_hamiltonian_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/tests/test_negf_negf_hamiltonian_init.py b/dptb/tests/test_negf_negf_hamiltonian_init.py index 4eba408f..79942d93 100644 --- a/dptb/tests/test_negf_negf_hamiltonian_init.py +++ b/dptb/tests/test_negf_negf_hamiltonian_init.py @@ -25,7 +25,7 @@ def root_directory(request): def test_negf_Hamiltonian(root_directory): model_ckpt=root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json' - results_path=root_directory +"/dptb/tests/data/test_negf" + results_path=root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/test_negf_hamiltonian_init" input_path = root_directory +"/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json" structure=root_directory +"/dptb/tests/data/test_negf/test_negf_run/chain.vasp" # log_path=root_directory +"/dptb/tests/data/test_negf/test_negf_Device/test.log" From 03e3750c86ac07a9a295b0f282a117d2323a94be Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 13 Apr 2024 16:02:44 +0800 Subject: [PATCH 111/209] force test for negf in float64 --- .../tests/data/test_negf/test_negf_hamiltonian/run_input.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json b/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json index 11751291..a2bb6a46 100644 --- a/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json +++ b/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json @@ -53,7 +53,7 @@ "R": 1000000.0, "n_gauss": 10 }, - "eta_lead": 1e-05, + "eta_lead": 1e-5, "eta_device": 0.0, "out_dos": true, "out_tc": true, @@ -71,7 +71,7 @@ "basis":{"C": [ "2s"]}, "device":"cpu", - "dtype": "float32", + "dtype": "float64", "overlap":false }, "model_options": { From b83a1bd06226ef683302d33a9a09a0a6a6adb372 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 13 Apr 2024 16:05:04 +0800 Subject: [PATCH 112/209] add test files for negf --- .gitignore | 4 +- .../test_negf/test_negf_hamiltonian/band.json | 56 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 dptb/tests/data/test_negf/test_negf_hamiltonian/band.json diff --git a/.gitignore b/.gitignore index c937e765..de422d42 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ dptb/tests/data/test_all/checkpoint/best_nnsk_b5.000_c6.615_w0.265.json dptb/tests/data/test_all/checkpoint/best_nnsk_b4.000_c4.000_w0.300.json dptb/tests/data/test_all/fancy_ones/checkpoint/best_nnsk_b4.000_c4.000_w0.300.json dptb/tests/data/test_negf/test_negf_run/out_negf/run_config.json +dptb/data/try_test.ipynb +dptb/tests/data/test_negf/show.ipynb run_config.json dptb/nnet/__pycache__/ dptb/sktb/__pycache__/ @@ -169,4 +171,4 @@ dmypy.json _date.py _version.py /.idea/ -dptb/data/try_test.ipynb + diff --git a/dptb/tests/data/test_negf/test_negf_hamiltonian/band.json b/dptb/tests/data/test_negf/test_negf_hamiltonian/band.json new file mode 100644 index 00000000..e5e2aa5b --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_hamiltonian/band.json @@ -0,0 +1,56 @@ +{ + "structure": "/personal/DeepTB/dptb_Zjj/DeePTB/dptb/tests/data/test_negf/test_negf_run/chain.vasp", + "task_options": { + "task": "band", + "kline_type": "abacus", + "kpath": [ + [ + 0, + 0, + 0, + 50 + ], + [ + 0, + 0, + 1, + 1 + ] + ], + "nkpoints": 51, + "klabels": [ + "G", + "M" + ], + "E_fermi": -13.638589859008789, + "emin": -1.5, + "emax": 1.5, + "nel_atom": {"C":1}, + "ref_band": null + }, + "AtomicData_options": { + "r_max":2.0 + }, + "common_options": { + "basis":{"C":["2s"]}, + "device":"cpu", + "dtype": "float32", + "overlap":false + }, + "model_options":{ + "nnsk":{ + "onsite":{ + "method":"none" + }, + "hopping":{ + "method":"powerlaw", + "rs":1.6, + "w":0.3 + }, + "soc":{}, + "freeze":false, + "push":false, + "std":0.01 + } + } +} \ No newline at end of file From e0afd0c9f3dc1fd6c68c91032868e61c0048e177 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 15 Apr 2024 13:28:00 +0800 Subject: [PATCH 113/209] add task negf option in run.py --- dptb/entrypoints/run.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dptb/entrypoints/run.py b/dptb/entrypoints/run.py index 690b13ac..e4688e58 100644 --- a/dptb/entrypoints/run.py +++ b/dptb/entrypoints/run.py @@ -9,6 +9,7 @@ from dptb.utils.argcheck import normalize_run from dptb.utils.tools import j_loader from dptb.utils.tools import j_must_have +from dptb.postprocess.NEGF import NEGF log = logging.getLogger(__name__) @@ -75,3 +76,15 @@ def run( emax=jdata["task_options"].get("emax", None)) log.info(msg='band calculation successfully completed.') + if task=='negf': + negf = NEGF( + model=model, + AtomicData_options=jdata['AtomicData_options'], + structure=structure, + results_path=results_path, + **task_options + ) + + negf.compute() + log.info(msg='negf calculation successfully completed.') + From 34e7812a07253de3ee7b4da075245df31ee5bfd3 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 15 Apr 2024 13:29:11 +0800 Subject: [PATCH 114/209] set torch default float32 after extract H and S --- dptb/negf/negf_hamiltonian_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 66d043c4..08822042 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -292,7 +292,7 @@ def initialize(self, kpoints, block_tridiagnal=False): }) torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) - + torch.set_default_dtype(torch.float32) return structure_device, structure_leads def get_hs_device(self, kpoint, V, block_tridiagonal=False): From c4a9f6bd3f0e05c7f461762a5e275fe75a58b625 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 15 Apr 2024 14:41:53 +0800 Subject: [PATCH 115/209] update test_negf_run.py for v2 --- .../test_negf_run/negf_chain_new.json | 59 ++++++++++++++++++ .../test_negf_run/negf_graphene_new.json | 62 +++++++++++++++++++ .../test_negf/test_negf_run/nnsk_C_new.json | 39 ++++++++++++ dptb/tests/test_negf_run.py | 16 ++--- 4 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 dptb/tests/data/test_negf/test_negf_run/negf_chain_new.json create mode 100644 dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json create mode 100644 dptb/tests/data/test_negf/test_negf_run/nnsk_C_new.json diff --git a/dptb/tests/data/test_negf/test_negf_run/negf_chain_new.json b/dptb/tests/data/test_negf/test_negf_run/negf_chain_new.json new file mode 100644 index 00000000..0ad717d3 --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_run/negf_chain_new.json @@ -0,0 +1,59 @@ +{ + "task_options": { + "task": "negf", + "scf": false, + "block_tridiagonal": false, + "ele_T": 300, + "unit": "eV", + "scf_options":{ + "mode": "PDIIS", + "mixing_period": 3, + "step_size": 0.05, + "n_history": 6, + "abs_err": 1e-6, + "rel_err": 1e-4, + "max_iter": 100 + }, + "stru_options":{ + "kmesh":[1,1,1], + "pbc":[false, false, false], + "device":{ + "id":"4-8", + "sort": true + }, + "lead_L":{ + "id":"0-4", + "voltage":0.0 + }, + "lead_R":{ + "id":"8-12", + "voltage":0.0 + } + }, + "poisson_options": { + "solver": "fmm", + "err": 1e-5 + }, + "sgf_solver": "Sancho-Rubio", + "espacing": 0.01, + "emin": -2, + "emax": 2, + "e_fermi": -13.638587951660156, + "density_options": { + "method": "Ozaki", + "M_cut": 50, + "R": 1e6 + }, + "eta_lead":1e-5, + "eta_device":0.0, + "out_dos": true, + "out_tc": true, + "out_ldos": true, + "out_current_nscf": true +}, +"AtomicData_options" :{ + "r_max": 2.0, + "pbc": true +}, +"structure":"./chain.vasp" +} \ No newline at end of file diff --git a/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json b/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json new file mode 100644 index 00000000..2faea8da --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json @@ -0,0 +1,62 @@ +{ + "task_options": + { + "task": "negf", + "scf": false, + "block_tridiagonal": false, + "ele_T": 500, + "unit": "eV", + "scf_options":{ + "mode": "PDIIS", + "mixing_period": 3, + "step_size": 0.05, + "n_history": 6, + "abs_err": 1e-6, + "rel_err": 1e-4, + "max_iter": 100 + }, + "stru_options":{ + "pbc":[false, true, false], + "kmesh":[1,3,1], + "gamma_center": true, + "time_reversal_symmetry": true, + "device":{ + "id":"16-32", + "sort": true + }, + "lead_L":{ + "id":"0-16", + "voltage":0.0 + }, + "lead_R":{ + "id":"32-48", + "voltage":0.0 + } + }, + "poisson_options": { + "solver": "fmm", + "err": 1e-5 + }, + "sgf_solver": "Sancho-Rubio", + "espacing": 0.2, + "emin": -5, + "emax": 5, + "e_fermi": -13.638589859008789, + "density_options":{ + "method": "Ozaki" + }, + "eta_lead":1e-4, + "eta_device":1e-5, + "out_dos": false, + "out_tc": true, + "out_ldos": false, + "out_current_nscf": false, + "out_density": false, + "out_lcurrent": false + }, + "AtomicData_options" :{ + "r_max": 2.0, + "pbc": true + }, + "structure":"./graphene.xyz" +} \ No newline at end of file diff --git a/dptb/tests/data/test_negf/test_negf_run/nnsk_C_new.json b/dptb/tests/data/test_negf/test_negf_run/nnsk_C_new.json new file mode 100644 index 00000000..76d56b47 --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_run/nnsk_C_new.json @@ -0,0 +1,39 @@ +{ "version":2, +"model_params": { + "onsite": {}, + "hopping": { + "C-C-2s-2s-0": [ + 1.0884529828109601, + 1.0 + ] + } +}, +"unit": "eV", +"model_options": { + "nnsk": { + "onsite": { + "method": "none" + }, + "hopping": { + "method": "powerlaw", + "rs": 1.6, + "w": 0.3 + }, + "soc": {}, + "freeze": false, + "push": false, + "std": 0.01 + } +}, +"common_options": { + "basis": { + "C": [ + "2s" + ] + }, + "dtype": "float64", + "device": "cpu", + "overlap": false +} +} + diff --git a/dptb/tests/test_negf_run.py b/dptb/tests/test_negf_run.py index 61187d1f..0351e674 100644 --- a/dptb/tests/test_negf_run.py +++ b/dptb/tests/test_negf_run.py @@ -1,4 +1,4 @@ -from dptb.entrypoints import run +from dptb.entrypoints.run import run import pytest import torch import numpy as np @@ -14,24 +14,24 @@ def root_directory(request): # NEGF calculaion in 1D carbon chain with zero-bias transmission 1 G0 def test_negf_run_chain(root_directory): - INPUT_file = root_directory +"/dptb/tests/data/test_negf/test_negf_run/negf_chain.json" + INPUT_file = root_directory +"/dptb/tests/data/test_negf/test_negf_run/negf_chain_new.json" output = root_directory +"/dptb/tests/data/test_negf/test_negf_run/out_negf_chain" - checkfile = root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json' + checkfile = root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C_new.json' structure = root_directory +"/dptb/tests/data/test_negf/test_negf_run/chain.vasp" - run(INPUT=INPUT_file,init_model=checkfile,output=output,run_sk=True,structure=structure,\ - log_level=5,log_path=output+"/test.log",use_correction=False) + run(INPUT=INPUT_file,init_model=checkfile,structure=structure,output=output,\ + log_level=5,log_path=output+"/output.log") negf_results = torch.load(output+"/results/negf.out.pth") trans = negf_results['T_avg'] assert(abs(trans[int(len(trans)/2)]-1)<1e-5) #compare with calculated transmission at efermi -# NEGF calculation in 2D graphene with zero-bias and multiple kpoints + def test_negf_run(root_directory): - INPUT_file = root_directory +"/dptb/tests/data/test_negf/test_negf_run/negf_graphene.json" + INPUT_file = root_directory +"/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json" output = root_directory +"/dptb/tests/data/test_negf/test_negf_run/out_negf_graphene" - checkfile = root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C.json' + checkfile = root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C_new.json' structure = root_directory +"/dptb/tests/data/test_negf/test_negf_run/graphene.xyz" run(INPUT=INPUT_file,init_model=checkfile,output=output,run_sk=True,structure=structure,\ From 5d71a8346cbb22cc866fb5d356aace1e9348f4a5 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 15 Apr 2024 16:43:10 +0800 Subject: [PATCH 116/209] add sort_btd.py and split_btd.py --- dptb/negf/sort_btd.py | 106 +++++++ dptb/negf/split_btd.py | 698 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 804 insertions(+) create mode 100644 dptb/negf/sort_btd.py create mode 100644 dptb/negf/split_btd.py diff --git a/dptb/negf/sort_btd.py b/dptb/negf/sort_btd.py new file mode 100644 index 00000000..4da72975 --- /dev/null +++ b/dptb/negf/sort_btd.py @@ -0,0 +1,106 @@ +"""This module contains three sorting function: lexicographic sort of atomic coordinates, +sort that uses projections on a vector pointing from one electrode to another as the sorting keys +and sort that uses a potential function over atomic coordinates as the sorting keys. +A user can define his own sorting procedure - the user-defined sorting function should contain +`**kwargs` in the list of arguments and it can uses in its body one of the arguments with following name convention: +`coords` is the list of atomic coordinates, +`left_lead` is the list of the indices of the atoms contacting the left lead, +`right_lead` is the list of the indices of the atoms contacting the right lead, and +`mat` is the adjacency matrix of the tight-binding model. +All functions return the list of sorted atomic indices. +""" +import numpy as np +import matplotlib.pyplot as plt +from scipy.sparse.linalg import lgmres + + +def sort_lexico(coords=None, **kwargs): + """Lexicographic sort + + Parameters + ---------- + coords : array + list of atomic coordinates (Default value = None) + **kwargs : + + + Returns + ------- + + + """ + return np.lexsort((coords[:, 0], coords[:, 1], coords[:, 2])) + + +def sort_projection(coords=None, left_lead=None, right_lead=None, **kwargs): + """Sorting procedure that uses projections on a vector pointing from one electrode to another as the sorting keys. + + Parameters + ---------- + coords : array + list of atomic coordinates (Default value = None) + left_lead : array + list of the atom indices contacting the left lead (Default value = None) + right_lead : array + list of the atom indices contacting the right lead (Default value = None) + **kwargs : + + + Returns + ------- + + + """ + vec = np.mean(coords[left_lead], axis=0) - np.mean(coords[right_lead], axis=0) + keys = np.dot(coords, vec) / np.linalg.norm(vec) + + return np.argsort(keys, kind='mergesort') + + +def sort_capacitance(coords, mat, left_lead, right_lead, **kwargs): + """Sorting procedure that uses a potential function defined over atomic coordinates as the sorting keys. + + Parameters + ---------- + coords : array + list of atomic coordinates + mat : 2D array + adjacency matrix of the tight-binding model + left_lead : array + list of the atom indices contacting the left lead + right_lead : array + list of the atom indices contacting the right lead + **kwargs : + + + Returns + ------- + + + """ + + charge = np.zeros(coords.shape[0], dtype=complex) + charge[left_lead] = 1e3 + charge[right_lead] = -1e3 + + x = coords[:, 1].T + y = coords[:, 0].T + + mat = (mat != 0.0).astype(float) + mat = 10 * (mat - np.diag(np.diag(mat))) + mat = mat - np.diag(np.sum(mat, axis=1)) + 0.001 * np.identity(mat.shape[0]) + + col, info = lgmres(mat, charge.T, x0=1.0 / np.diag(mat), tol=1e-5, maxiter=15) + col = col / np.max(col) + + indices = np.argsort(col, kind='heapsort') + + mat = mat[indices, :] + mat = mat[:, indices] + + plt.scatter(x, y, c=col, cmap=plt.cm.get_cmap('seismic'), s=50, marker="o", edgecolors="k") + plt.colorbar() + plt.axis('off') + plt.show() + + return indices diff --git a/dptb/negf/split_btd.py b/dptb/negf/split_btd.py new file mode 100644 index 00000000..57d7fbaa --- /dev/null +++ b/dptb/negf/split_btd.py @@ -0,0 +1,698 @@ +"""This module contains a set of functions facilitating computations of +the block-tridiagonal structure of a band matrix. +""" +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle +from itertools import product +import math +import scipy + + +def accum(accmap, input, func=None, size=None, fill_value=0, dtype=None): + """An accumulation function similar to Matlab's `accumarray` function. + + Parameters + ---------- + accmap : ndarray + This is the "accumulation map". It maps input (i.e. indices into + `a`) to their destination in the output array. The first `a.ndim` + dimensions of `accmap` must be the same as `a.shape`. That is, + `accmap.shape[:a.ndim]` must equal `a.shape`. For example, if `a` + has shape (15,4), then `accmap.shape[:2]` must equal (15,4). In this + case `accmap[i,j]` gives the index into the output array where + element (i,j) of `a` is to be accumulated. If the output is, say, + a 2D, then `accmap` must have shape (15,4,2). The value in the + last dimension give indices into the output array. If the output is + 1D, then the shape of `accmap` can be either (15,4) or (15,4,1) + input : ndarray + The input data to be accumulated. + func : callable or None + The accumulation function. The function will be passed a list + of values from `a` to be accumulated. + If None, numpy.sum is assumed. (Default value = None) + size : ndarray or None + The size of the output array. If None, the size will be determined + from `accmap`. (Default value = None) + fill_value : scalar + The default value for elements of the output array. + dtype : numpy data type, or None + The data type of the output array. If None, the data type of + `a` is used. (Default value = None) + + Returns + ------- + + + """ + + # Check for bad arguments and handle the defaults. + if accmap.shape[:input.ndim] != input.shape: + raise ValueError("The initial dimensions of accmap must be the same as a.shape") + if func is None: + func = np.sum + if dtype is None: + dtype = input.dtype + if accmap.shape == input.shape: + accmap = np.expand_dims(accmap, -1) + adims = tuple(range(input.ndim)) + if size is None: + size = 1 + np.squeeze(np.apply_over_axes(np.max, accmap, axes=adims)) + size = np.atleast_1d(size) + + # Create an array of python lists of values. + vals = np.empty(size, dtype='O') + for s in product(*[range(k) for k in size]): + vals[s] = [] + for s in product(*[range(k) for k in input.shape]): + indx = tuple(accmap[s]) + val = input[s] + vals[indx].append(val) + + # Create the output array. + out = np.empty(size, dtype=dtype) + for s in product(*[range(k) for k in size]): + if vals[s] == []: + out[s] = fill_value + else: + out[s] = func(vals[s]) + + return out + + +def cut_in_blocks(h_0, blocks): + """Cut a matrix into diagonal, upper-diagonal and lower-diagonal blocks + if sizes of the diagonal blocks are specified. + + Parameters + ---------- + h_0 : ndarray + Input matrix + blocks : ndarray(dtype=int) + Sizes of diagonal blocks + + Returns + ------- + h_0_s, h_l_s, h_r_s : ndarray + List of diagonal matrices, + list of lower-diagonal matrices and + list of upper-diagonal matrices. + Note that if the size of the list h_0_s is N, + the sizes of h_l_s, h_r_s are N-1. + + Examples + -------- + >>> import numpy as np + >>> from nanonet.tb.block_tridiagonalization import cut_in_blocks + >>> a = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> a + array([[1, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> # Sum the diagonals. + >>> blocks = [2, 2] + >>> blocks + [2, 2] + >>> h0, h1, h2 = cut_in_blocks(a, blocks) + >>> h0 + [array([[1, 1], + [1, 1]]), array([[1, 1], + [1, 1]])] + >>> h1 + [array([[0, 1], + [0, 0]])] + >>> h2 + [array([[0, 0], + [1, 0]])] + """ + + j1 = 0 + + h_0_s = [] + h_l_s = [] + h_r_s = [] + + for j, block in enumerate(blocks): + h_0_s.append(h_0[j1:block + j1, j1:block + j1]) + if j < len(blocks) - 1: + h_l_s.append(h_0[block + j1:block + j1 + blocks[j + 1], j1:block + j1]) + h_r_s.append(h_0[j1:block + j1, j1 + block:j1 + block + blocks[j + 1]]) + j1 += block + + return h_0_s, h_l_s, h_r_s + + +def find_optimal_cut(edge, edge1, left, right): + """Computes the index corresponding to the optimal cut such that applying + the function compute_blocks() to the sub-blocks defined by the cut reduces + the cost function comparing to the case when the function compute_blocks() is + applied to the whole matrix. If cutting point can not be find, the algorithm returns + the result from the function compute_blocks(). + + Parameters + ---------- + edge : ndarray + sparsity pattern profile of the matrix + edge1 : ndarray + conjugated sparsity pattern profile of the matrix + left : int + size of the leftmost diagonal block + right : int + size of the rightmost diagonal block + + Returns + ------- + + + """ + + unique_indices = np.arange(left, len(edge) - right + 1) + blocks = [] + seps = [] + sizes = [] + metric = [] + size = len(edge) + + for j1, item1 in enumerate(unique_indices): + seps.append(item1) + item2 = size - item1 + + # print(item1, item2) + # print(item1) + + edge_1 = edge[:item1] + edge_2 = (edge1 - np.arange(len(edge1)))[item2:] + np.arange(item1) + + edge_3 = edge1[:item2] + edge_4 = (edge - np.arange(len(edge)))[item1:] + np.arange(item2) + + block1 = compute_blocks(left, (edge1 - np.arange(len(edge)))[item2], + edge_1, edge_2) + + block2 = compute_blocks(right, (edge - np.arange(len(edge1)))[item1], + edge_3, edge_4) + + block = block1 + block2[::-1] + blocks.append(block) + metric.append(np.sum(np.array(block) ** 3)) + sizes.append((block1[-1], block2[-1])) + + if len(metric) == 0: + return [left, right], np.nan, 0, 0 + else: + + best = np.argmin(np.array(metric)) + + blocks = blocks[best] + blocks = [item for item in blocks if item != 0] + + sep = seps[best] + + right_block, left_block = sizes[best] + + return blocks, sep, right_block, left_block + + +def compute_blocks_optimized(edge, edge1, left=1, right=1): + """Computes optimal sizes of diagonal blocks of a matrix whose + sparsity pattern is defined by the sparsity pattern profiles edge and edge1. + This function is based on the algorithm which uses defined above function + find_optimal_cut() to subdivide the problem into sub-problems in a optimal way + according to some cost function. + + Parameters + ---------- + edge : ndarray + sparsity pattern profile of the matrix + edge1 : ndarray + conjugated sparsity pattern profile of the matrix + left : int + size of the leftmost diagonal block (constrained) (Default value = 1) + right : int + size of the rightmost diagonal block (constrained) (Default value = 1) + + Returns + ------- + + + """ + + blocks, sep, right_block, left_block = find_optimal_cut(edge, edge1, left=left, right=right) + flag = False + + if not math.isnan(sep): + + # print(left, right_block, sep) + + if left + right_block < sep: + + edge_1 = edge[:sep] + # edge_1[edge_1 > sep] = sep + edge_2 = (edge1 - np.arange(len(edge1)))[-sep:] + np.arange(sep) + + blocks1 = compute_blocks_optimized(edge_1, edge_2, left=left, right=right_block) + + elif left + right_block == sep: + + blocks1 = [left, right_block] + else: + + flag = True + + # print(left_block, right, len(edge) - sep) + + if right + left_block < len(edge) - sep: + + edge_3 = (edge - np.arange(len(edge)))[sep:] + np.arange(len(edge) - sep) + edge_4 = edge1[:-sep] + # edge_4[edge_4 > len(edge) - sep] = len(edge) - sep + + blocks2 = compute_blocks_optimized(edge_3, edge_4, left=left_block, right=right) + + elif right + left_block == len(edge) - sep: + blocks2 = [left_block, right] + else: + flag = True + + if flag: + return blocks + else: + blocks = blocks1 + blocks2 + + return blocks + + +def find_nonzero_lines(mat, order): + """ + + Parameters + ---------- + mat : + + order : + + + Returns + ------- + + """ + + if scipy.sparse.issparse(mat): + lines = _find_nonzero_lines_sparse(mat, order) + else: + lines = _find_nonzero_lines(mat, order) + + if lines == max(mat.shape[0], mat.shape[1]) - 1: + lines = 1 + if lines == 0: + lines = 1 + + return lines + + +def _find_nonzero_lines(mat, order): + """ + + Parameters + ---------- + mat : + + order : + + + Returns + ------- + + """ + if order == 'top': + line = mat.shape[0] + while line > 0: + if np.count_nonzero(mat[line - 1, :]) == 0: + line -= 1 + else: + break + elif order == 'bottom': + line = -1 + while line < mat.shape[0] - 1: + if np.count_nonzero(mat[line + 1, :]) == 0: + line += 1 + else: + line = mat.shape[0] - (line + 1) + break + elif order == 'left': + line = mat.shape[1] + while line > 0: + if np.count_nonzero(mat[:, line - 1]) == 0: + line -= 1 + else: + break + elif order == 'right': + line = -1 + while line < mat.shape[1] - 1: + if np.count_nonzero(mat[:, line + 1]) == 0: + line += 1 + else: + line = mat.shape[1] - (line + 1) + break + else: + raise ValueError('Wrong value of the parameter order') + + return line + + +def _find_nonzero_lines_sparse(mat, order): + """ + + Parameters + ---------- + mat : + + order : + + + Returns + ------- + + """ + if order == 'top': + line = mat.shape[0] + while line > 0: + if np.count_nonzero(mat[line - 1, :].todense()) == 0: + line -= 1 + else: + break + elif order == 'bottom': + line = -1 + while line < mat.shape[0] - 1: + if np.count_nonzero(mat[line + 1, :].todense()) == 0: + line += 1 + else: + line = mat.shape[0] - (line + 1) + break + elif order == 'left': + line = mat.shape[1] + while line > 0: + if np.count_nonzero(mat[:, line - 1].todense()) == 0: + line -= 1 + else: + break + elif order == 'right': + line = -1 + while line < mat.shape[1] - 1: + if np.count_nonzero(mat[:, line + 1].todense()) == 0: + line += 1 + else: + line = mat.shape[1] - (line + 1) + break + else: + raise ValueError('Wrong value of the parameter order') + + return line + + +def split_into_subblocks_optimized(h_0, left=1, right=1): + """ + + Parameters + ---------- + h_0 : + param left: + right : + return: (Default value = 1) + left : + (Default value = 1) + + Returns + ------- + + """ + + if not (isinstance(left, int) and isinstance(right, int)): + h_r_h = find_nonzero_lines(right, 'bottom') + h_r_v = find_nonzero_lines(right[-h_r_h:, :], 'left') + h_l_h = find_nonzero_lines(left, 'top') + h_l_v = find_nonzero_lines(left[:h_l_h, :], 'right') + left = max(h_l_h, h_r_v) + right = max(h_r_h, h_l_v) + + if left + right > h_0.shape[0]: + return [h_0.shape[0]] + else: + edge, edge1 = compute_edge(h_0) + return compute_blocks_optimized(edge, edge1, left=left, right=right) + + +def split_into_subblocks(h_0, h_l, h_r): + """Split Hamiltonian matrix and coupling matrices into subblocks + + Parameters + ---------- + h_0 : + Hamiltonian matrix + h_l : + left inter-cell coupling matrices + h_r : + right inter-cell coupling matrices + :return h_0_s, h_l_s, h_r_s: lists of subblocks + + Returns + ------- + + """ + + if isinstance(h_l, np.ndarray) and isinstance(h_r, np.ndarray): + h_r_h = find_nonzero_lines(h_r, 'bottom') + h_r_v = find_nonzero_lines(h_r[-h_r_h:, :], 'left') + h_l_h = find_nonzero_lines(h_l, 'top') + h_l_v = find_nonzero_lines(h_l[:h_l_h, :], 'right') + left_block = max(h_l_h, h_r_v) + right_block = max(h_r_h, h_l_v) + elif isinstance(h_l, int) and isinstance(h_r, int): + left_block = h_l + right_block = h_r + else: + raise TypeError + + edge, edge1 = compute_edge(h_0) + + blocks = compute_blocks(left_block, right_block, edge, edge1) + + return blocks + + +def compute_edge(mat): + """Computes edges of the sparsity pattern of a matrix. + + Parameters + ---------- + mat : ndarray + Input matrix + + Returns + ------- + edge : ndarray + edge of the sparsity pattern + edge1 : ndarray + conjugate edge of the sparsity pattern + + Examples + -------- + >>> import numpy as np + >>> from nanonet.tb.block_tridiagonalization import compute_edge + >>> input_matrix = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> e1 + array([2, 3, 4, 4]) + >>> e2 + array([2, 3, 4, 4]) + >>> input_matrix = np.array([[1, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 0, 0, 0], + [0, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> e1 + array([1, 3, 4, 4]) + >>> e2 + array([2, 3, 3, 4]) + """ + + # First get some statistics + if isinstance(mat, scipy.sparse.lil.lil_matrix): + row, col = mat.nonzero() + else: + row, col = np.where(mat != 0.0) # Output rows and columns of all non-zero elements. + + # Clever use of accumarray: + edge = accum(row, col, np.max) + 1 + edge[0] = max(0, edge[0]) + edge = np.maximum.accumulate(edge) + + edge1 = accum(np.max(row) - row[::-1], np.max(row) - col[::-1], np.max) + 1 + edge1[0] = max(0, edge1[0]) + edge1 = np.maximum.accumulate(edge1) + + return edge, edge1 + + +def compute_blocks(left_block, right_block, edge, edge1): + """This is an implementation of the greedy algorithm for + computing block-tridiagonal representation of a matrix. + The information regarding the input matrix is represented + by the sparsity patters edges, `edge` and `edge1`. + + Parameters + ---------- + left_block : int + a predefined size of the leftmost block + right_block : int + a predefined size of the rightmost block + edge : ndarray + edge of sparsity pattern + edge1 : ndarray + conjugate edge of sparsity pattern + + Returns + ------- + ans : list + + + Examples + -------- + >>> import numpy as np + >>> from nanonet.tb.block_tridiagonalization import compute_edge + >>> input_matrix = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> compute_blocks(1, 1, e1, e2) + [1, 1, 1, 1] + >>> input_matrix = np.array([[1, 1, 1, 0], [1, 1, 1, 0], [1, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 1, 1, 0], + [1, 1, 1, 0], + [1, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> compute_blocks(1, 1, e1, e2) + [1, 2, 1] + >>> e1, e2 = compute_edge(input_matrix) + >>> compute_blocks(2, 2, e1, e2) + [2, 2] + """ + + size = len(edge) + left_block = max(1, left_block) + right_block = max(1, right_block) + + if left_block + right_block < size: # if blocks do not overlap + + new_left_block = edge[left_block - 1] - left_block + new_right_block = edge1[right_block - 1] - right_block + # + # new_right_block = np.max(np.argwhere(np.abs(edge - (size - right_block)) - + # np.min(np.abs(edge - (size - right_block))) == 0)) + 1 + # new_right_block = size - new_right_block - right_block + + if left_block + new_left_block <= size - right_block and \ + size - right_block - new_right_block >= left_block: # spacing between blocks is sufficient + + blocks = compute_blocks(new_left_block, + new_right_block, + edge[left_block:-right_block] - left_block, + edge1[right_block:-left_block] - right_block) + + return [left_block] + blocks + [right_block] + else: + if new_left_block > new_right_block: + return [left_block] + [size - left_block] + else: + return [size - right_block] + [right_block] + + elif left_block + right_block == size: # sum of blocks equal to the matrix size + return [left_block] + [right_block] + else: # blocks overlap + return [size] + + +def show_blocks(subblocks, input_mat): + """This is a script for visualizing the sparsity pattern and + a block-tridiagonal structure of a matrix. + + Parameters + ---------- + subblocks : + + input_mat : + + + Returns + ------- + + + """ + + cumsum = np.cumsum(np.array(subblocks))[:-1] + cumsum = np.insert(cumsum, 0, 0) + + fig, ax = plt.subplots(1) + plt.spy(input_mat, markersize=0.9, c='k') + # plt.plot(edge) + + for jj in range(2): + cumsum = cumsum + jj * input_mat.shape[0] + + if jj == 1: + rect = Rectangle((input_mat.shape[0] - subblocks[-1] - 0.5, input_mat.shape[1] - 0.5), + subblocks[-1], subblocks[0], + linestyle='--', + linewidth=1.3, + edgecolor='b', + facecolor='none', zorder=200) + ax.add_patch(rect) + rect = Rectangle((input_mat.shape[0] - 0.5, input_mat.shape[1] - subblocks[-1] - 0.5), + subblocks[0], subblocks[-1], + linestyle='--', + linewidth=1.3, + edgecolor='g', + facecolor='none', zorder=200) + ax.add_patch(rect) + + for j, item in enumerate(cumsum): + if j < len(cumsum) - 1: + rect = Rectangle((item - 0.5, cumsum[j + 1] - 0.5), subblocks[j], subblocks[j + 1], + linewidth=1.3, + edgecolor='b', + facecolor='none', zorder=200) + ax.add_patch(rect) + rect = Rectangle((cumsum[j + 1] - 0.5, item - 0.5), subblocks[j + 1], subblocks[j], + linewidth=1.3, + edgecolor='g', + facecolor='none', zorder=200) + ax.add_patch(rect) + rect = Rectangle((item - 0.5, item - 0.5), subblocks[j], subblocks[j], + linewidth=1.3, + edgecolor='r', + facecolor='none', zorder=200) + ax.add_patch(rect) + + plt.xlim(input_mat.shape[0] - 0.5, -1.0) + plt.ylim(-1.0, input_mat.shape[0] - 0.5) + plt.axis('off') + plt.show() + + +# if __name__ == "__main__": +# import doctest + +# doctest.testmod() From 826cb980d16b700aa6c64f652e7c28e918659294 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 15 Apr 2024 16:48:22 +0800 Subject: [PATCH 117/209] add tbtrans_init in run.py --- dptb/entrypoints/run.py | 15 +++++++ dptb/negf/negf_hamiltonian_init.py | 46 ++++++++++++++++++++ dptb/postprocess/NEGF.py | 6 +-- dptb/postprocess/tbtrans_init.py | 67 ++++++++++++++++++++++-------- 4 files changed, 114 insertions(+), 20 deletions(-) diff --git a/dptb/entrypoints/run.py b/dptb/entrypoints/run.py index e4688e58..1302bae6 100644 --- a/dptb/entrypoints/run.py +++ b/dptb/entrypoints/run.py @@ -10,6 +10,8 @@ from dptb.utils.tools import j_loader from dptb.utils.tools import j_must_have from dptb.postprocess.NEGF import NEGF +from dptb.postprocess.tbtrans_init import TBTransInputSet,sisl_installed + log = logging.getLogger(__name__) @@ -88,3 +90,16 @@ def run( negf.compute() log.info(msg='negf calculation successfully completed.') + if task == 'tbtrans_negf': + if not(sisl_installed): + log.error(msg="sisl is required to perform tbtrans calculation !") + raise RuntimeError + + tbtrans_init = TBTransInputSet( + model=model, + AtomicData_options=jdata['AtomicData_options'], + structure=structure, + results_path=results_path, + **task_options) + tbtrans_init.hamil_get_write(write_nc=True) + log.info(msg='TBtrans input files are successfully generated.') \ No newline at end of file diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 08822042..3e6e349c 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -24,6 +24,8 @@ from dptb.nn.hr2hk import HR2HK from ase import Atoms +from dptb.negf.sort_btd import sort_lexico, sort_projection, sort_capacitance +from dptb.negf.split_btd import compute_edge,compute_blocks,show_blocks,compute_blocks_optimized,split_into_subblocks,split_into_subblocks_optimized ''' a Hamiltonian object that initializes and manipulates device and lead Hamiltonians for NEGF ''' @@ -60,6 +62,7 @@ def __init__(self, model: torch.nn.Module, AtomicData_options: dict, structure: ase.Atoms, + block_tridiagonal: bool, pbc_negf: List[bool], stru_options:dict, unit: str, @@ -86,6 +89,12 @@ def __init__(self, self.structase = structure else: raise ValueError('structure must be ase.Atoms or str') + + # sort the atoms lexicographically + if block_tridiagonal: + self.structase.positions = self.structase.positions[sort_lexico(self.structase.positions)] + log.info(msg="The structure is sorted lexicographically in this version!") + self.unit = unit self.stru_options = stru_options @@ -295,6 +304,43 @@ def initialize(self, kpoints, block_tridiagnal=False): torch.set_default_dtype(torch.float32) return structure_device, structure_leads + + def get_block_tridiagonal(self,HK,SK): + + hd,hu,hl,sd,su,sl = [],[],[],[],[],[] + + leftmost_orb_num = 1 + rightmost_orb_num = 1 + subblocks = split_into_subblocks(HK[:],leftmost_orb_num,rightmost_orb_num) + subblocks = [0]+subblocks + edge1,edge2 = compute_edge(HK[:]) + + for id in range(len(subblocks)-1): + # if id== 0: + # iu = id+1; il = None + # elif id == len(subblocks)-1: + # iu = None; il = id-1 + # else: + # iu = id+1; il = id-1 + if id < len(subblocks)-2: + iu = id+1 + hd.append(HK[:,subblocks[id]:subblocks[id+1],subblocks[id]:subblocks[id+1]]) + sd.append(SK[:,subblocks[id]:subblocks[id+1],subblocks[id]:subblocks[id+1]]) + hu.append(HK[:,subblocks[id]:subblocks[id+1],subblocks[id+1]:subblocks[id+2]]) + su.append(SK[:,subblocks[id]:subblocks[id+1],subblocks[id+1]:subblocks[id+2]]) + hl.append(HK[:,subblocks[id+1]:subblocks[id+2],subblocks[id]:subblocks[id+1]]) + sl.append(SK[:,subblocks[id+1]:subblocks[id+2],subblocks[id]:subblocks[id+1]]) + + + + + + + + + + return hd, hu, hl, sd, su, sl + def get_hs_device(self, kpoint, V, block_tridiagonal=False): """ get the device Hamiltonian and overlap matrix at a specific kpoint diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 9da0174c..bec65b81 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -95,19 +95,19 @@ def __init__(self, self.unit = unit self.scf = scf self.block_tridiagonal = block_tridiagonal - - # computing the hamiltonian #需要改写NEGFHamiltonianInit self.negf_hamiltonian = NEGFHamiltonianInit(model=model, AtomicData_options=AtomicData_options, structure=structure, + block_tridiagonal=self.block_tridiagonal, pbc_negf = self.pbc, stru_options=self.stru_options, unit = self.unit, results_path=self.results_path, torch_device = self.torch_device) with torch.no_grad(): - struct_device, struct_leads = self.negf_hamiltonian.initialize(kpoints=self.kpoints) + struct_device, struct_leads = self.negf_hamiltonian.initialize(kpoints=self.kpoints, + block_tridiagnal=self.block_tridiagonal) self.deviceprop = DeviceProperty(self.negf_hamiltonian, struct_device, results_path=self.results_path, efermi=self.e_fermi) diff --git a/dptb/postprocess/tbtrans_init.py b/dptb/postprocess/tbtrans_init.py index 1dc8af17..610fed3c 100644 --- a/dptb/postprocess/tbtrans_init.py +++ b/dptb/postprocess/tbtrans_init.py @@ -4,14 +4,14 @@ import re from dptb.structure.structure import BaseStruct from dptb.utils.tools import j_loader,j_must_have - +from typing import Optional, Union from ase.io import read,write from ase.build import sort import ase.atoms import torch from dptb.utils.constants import atomic_num_dict_r - +from dptb.data import AtomicData, AtomicDataDict log = logging.getLogger(__name__) @@ -88,21 +88,28 @@ class TBTransInputSet(object): - overlap_block_lead_R The `overlap_block_lead_R` parameter is a tensor that contains the overlap matrix elements for each specific basis in the right lead. """ - def __init__(self, apiHrk, run_opt, jdata): - self.apiHrk = apiHrk #apiHrk has been loaded in run.py + def __init__(self, + model: torch.nn.Module, + AtomicData_options: dict, + structure: Union[AtomicData, ase.Atoms, str], + stru_options: dict, + results_path=Optional[str]=None, + **kwargs): + + self.model = model #apiHrk has been loaded in run.py self.jdata = jdata #jdata has been loaded in run.py, jdata is written in negf.json - self.results_path = run_opt['results_path'] + self.results_path = results_path if not self.results_path.endswith('/'):self.results_path += '/' - self.stru_options = j_must_have(jdata, "stru_options") + self.stru_options = stru_options self.energy_unit_option = 'eV' # enenrgy unit for TBtrans calculation self.geom_all,self.geom_lead_L,self.geom_lead_R,self.all_tbtrans_stru,self.lead_L_tbtrans_stru,self.lead_R_tbtrans_stru\ - = self.read_rewrite_structure(run_opt['structure'],self.stru_options,self.results_path) + = self.read_rewrite_structure(structure,self.stru_options,self.results_path) - self.orbitals_get(self.geom_all,self.geom_lead_L,self.geom_lead_R,apiHrk=apiHrk) + # self.orbitals_get(self.geom_all,self.geom_lead_L,self.geom_lead_R,apiHrk=apiHrk) self.H_all = sisl.Hamiltonian(self.geom_all) self.H_lead_L = sisl.Hamiltonian(self.geom_lead_L) @@ -124,6 +131,41 @@ def __init__(self, apiHrk, run_opt, jdata): self.hamil_block_lead_R = None self.overlap_block_lead_R = None + # def __init__(self, apiHrk, run_opt, jdata): + # self.apiHrk = apiHrk #apiHrk has been loaded in run.py + # self.jdata = jdata #jdata has been loaded in run.py, jdata is written in negf.json + + # self.results_path = run_opt['results_path'] + # if not self.results_path.endswith('/'):self.results_path += '/' + # self.stru_options = j_must_have(jdata, "stru_options") + # self.energy_unit_option = 'eV' # enenrgy unit for TBtrans calculation + + + # self.geom_all,self.geom_lead_L,self.geom_lead_R,self.all_tbtrans_stru,self.lead_L_tbtrans_stru,self.lead_R_tbtrans_stru\ + # = self.read_rewrite_structure(run_opt['structure'],self.stru_options,self.results_path) + + + # self.orbitals_get(self.geom_all,self.geom_lead_L,self.geom_lead_R,apiHrk=apiHrk) + + # self.H_all = sisl.Hamiltonian(self.geom_all) + # self.H_lead_L = sisl.Hamiltonian(self.geom_lead_L) + # self.H_lead_R = sisl.Hamiltonian(self.geom_lead_R) + + + # #important properties for later use + + # ##allbonds matrx, hamiltonian matrix, overlap matrix for the whole structure + # self.allbonds_all = None + # self.hamil_block_all = None + # self.overlap_block_all = None + # ##allbonds matrx, hamiltonian matrix, overlap matrix for lead_L + # self.allbonds_lead_L = None + # self.hamil_block_lead_L = None + # self.overlap_block_lead_L = None + # ##allbonds matrx, hamiltonian matrix, overlap matrix for lead_R + # self.allbonds_lead_R = None + # self.hamil_block_lead_R = None + # self.overlap_block_lead_R = None def hamil_get_write(self,write_nc:bool=True): @@ -169,15 +211,6 @@ def hamil_get_write(self,write_nc:bool=True): else: print('Hamiltonian matrices have been generated, but not written to nc files(TBtrans input file).') - # def hamil_write(self): - # '''The function `hamil_write` writes the contents of `self.H_all`, `self.H_lead_L`, and `self.H_lead_R` - # to separate files in the `results_path` directory. - - # ''' - # self.H_all.write(self.results_path+'structure.nc') - # self.H_lead_L.write(self.results_path+'lead_L.nc') - # self.H_lead_L.write(self.results_path+'lead_R.nc') - def read_rewrite_structure(self,structure_file:str,struct_options:dict,results_path:str): From b0eff44c822bf289bdec6f8104ba5a1b60ae8d05 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 16 Apr 2024 09:32:59 +0800 Subject: [PATCH 118/209] add tbtrans_init in run.py and modify tbtrans_init.py --- dptb/entrypoints/run.py | 5 +- dptb/postprocess/tbtrans_init.py | 119 ++++++++++++++++++------------- 2 files changed, 71 insertions(+), 53 deletions(-) diff --git a/dptb/entrypoints/run.py b/dptb/entrypoints/run.py index 35b6f100..b63489e9 100644 --- a/dptb/entrypoints/run.py +++ b/dptb/entrypoints/run.py @@ -102,12 +102,13 @@ def run( if not(sisl_installed): log.error(msg="sisl is required to perform tbtrans calculation !") raise RuntimeError - + basis_dict = json.load(open(init_model))['common_options']['basis'] tbtrans_init = TBTransInputSet( model=model, AtomicData_options=jdata['AtomicData_options'], structure=structure, - results_path=results_path, + basis_dict=basis_dict, + results_path=results_path, **task_options) tbtrans_init.hamil_get_write(write_nc=True) log.info(msg='TBtrans input files are successfully generated.') \ No newline at end of file diff --git a/dptb/postprocess/tbtrans_init.py b/dptb/postprocess/tbtrans_init.py index 610fed3c..15844793 100644 --- a/dptb/postprocess/tbtrans_init.py +++ b/dptb/postprocess/tbtrans_init.py @@ -2,7 +2,6 @@ import logging import shutil import re -from dptb.structure.structure import BaseStruct from dptb.utils.tools import j_loader,j_must_have from typing import Optional, Union from ase.io import read,write @@ -12,6 +11,7 @@ from dptb.utils.constants import atomic_num_dict_r from dptb.data import AtomicData, AtomicDataDict +from dptb.data.interfaces.ham_to_feature import feature_to_block log = logging.getLogger(__name__) @@ -88,28 +88,35 @@ class TBTransInputSet(object): - overlap_block_lead_R The `overlap_block_lead_R` parameter is a tensor that contains the overlap matrix elements for each specific basis in the right lead. """ - def __init__(self, - model: torch.nn.Module, - AtomicData_options: dict, - structure: Union[AtomicData, ase.Atoms, str], - stru_options: dict, - results_path=Optional[str]=None, - **kwargs): + + def __init__(self, + model: torch.nn.Module, + AtomicData_options: dict, + structure: Union[AtomicData, ase.Atoms, str], + stru_options: dict, + basis_dict:dict, + results_path: Optional[str]=None, + unit: str='eV', + nel_atom: Optional[dict]=None, + **kwargs): self.model = model #apiHrk has been loaded in run.py - self.jdata = jdata #jdata has been loaded in run.py, jdata is written in negf.json + self.AtomicData_options = AtomicData_options + # self.jdata = jdata #jdata has been loaded in run.py, jdata is written in negf.json self.results_path = results_path if not self.results_path.endswith('/'):self.results_path += '/' self.stru_options = stru_options - self.energy_unit_option = 'eV' # enenrgy unit for TBtrans calculation + self.energy_unit_option = unit # enenrgy unit for TBtrans calculation + self.nel_atom = nel_atom self.geom_all,self.geom_lead_L,self.geom_lead_R,self.all_tbtrans_stru,self.lead_L_tbtrans_stru,self.lead_R_tbtrans_stru\ = self.read_rewrite_structure(structure,self.stru_options,self.results_path) - - # self.orbitals_get(self.geom_all,self.geom_lead_L,self.geom_lead_R,apiHrk=apiHrk) + if nel_atom is not None: + self.orbitals_get(self.geom_all,self.geom_lead_L,self.geom_lead_R,basis_dict) + log.warning('nel_atom is none!') self.H_all = sisl.Hamiltonian(self.geom_all) self.H_lead_L = sisl.Hamiltonian(self.geom_lead_L) @@ -131,6 +138,15 @@ def __init__(self, self.hamil_block_lead_R = None self.overlap_block_lead_R = None + if self.energy_unit_option=='Hartree': + self.unit_constant = 1.0000/13.605662285137 /2 + + elif self.energy_unit_option=='eV': + self.unit_constant = 1.0000000000 + + else: + raise RuntimeError("energy_unit_option should be 'Hartree' or 'eV'") + # def __init__(self, apiHrk, run_opt, jdata): # self.apiHrk = apiHrk #apiHrk has been loaded in run.py # self.jdata = jdata #jdata has been loaded in run.py, jdata is written in negf.json @@ -188,21 +204,18 @@ def hamil_get_write(self,write_nc:bool=True): # get the Hamiltonian matrix for the entire system self.allbonds_all,self.hamil_block_all,self.overlap_block_all\ - =self._load_model(self.apiHrk,self.all_tbtrans_stru) - self.hamiltonian_get(self.allbonds_all,self.hamil_block_all,self.overlap_block_all,\ - self.H_all,self.energy_unit_option) + =self._load_model(self.model,self.AtomicData_options,self.all_tbtrans_stru) + self.hamiltonian_get(self.allbonds_all,self.hamil_block_all,self.overlap_block_all,self.H_all) # get the Hamiltonian matrix for the left lead self.allbonds_lead_L,self.hamil_block_lead_L,self.overlap_block_lead_L\ - =self._load_model(self.apiHrk,self.lead_L_tbtrans_stru) - self.hamiltonian_get(self.allbonds_lead_L,self.hamil_block_lead_L,self.overlap_block_lead_L,\ - self.H_lead_L,self.energy_unit_option) + =self._load_model(self.model,self.AtomicData_options,self.lead_L_tbtrans_stru) + self.hamiltonian_get(self.allbonds_lead_L,self.hamil_block_lead_L,self.overlap_block_lead_L,self.H_lead_L) # get the Hamiltonian matrix for the right lead self.allbonds_lead_R,self.hamil_block_lead_R,self.overlap_block_lead_R\ - =self._load_model(self.apiHrk,self.lead_R_tbtrans_stru) - self.hamiltonian_get(self.allbonds_lead_R,self.hamil_block_lead_R,self.overlap_block_lead_R,\ - self.H_lead_R,self.energy_unit_option) + =self._load_model(self.model,self.AtomicData_options,self.lead_R_tbtrans_stru) + self.hamiltonian_get(self.allbonds_lead_R,self.hamil_block_lead_R,self.overlap_block_lead_R,self.H_lead_R) if write_nc: self.H_all.write(self.results_path+'structure.nc') @@ -349,7 +362,7 @@ def read_rewrite_structure(self,structure_file:str,struct_options:dict,results_p - def orbitals_get(self,geom_all, geom_lead_L,geom_lead_R,apiHrk): + def orbitals_get(self,geom_all, geom_lead_L,geom_lead_R,basis_dict:dict): '''The function `orbitals_get` takes in various inputs such as geometric devices, leads, deeptb model, and configurations, and assigns orbitals number, orbital names, shell-electron numbers to the atoms in the given sisl geometries . @@ -379,8 +392,8 @@ def orbitals_get(self,geom_all, geom_lead_L,geom_lead_R,apiHrk): geom_list = [geom_lead_L,geom_lead_R,geom_all] - dict_element_orbital = apiHrk.apihost.model_config['proj_atom_anglr_m'] - dict_shell_electron = apiHrk.apihost.model_config['proj_atom_neles'] + dict_element_orbital = basis_dict + dict_shell_electron = self.nel_atom for n_species, geom_part in zip(n_species_list,geom_list): # species_symbols = split_string(geom_part.atoms.formula()) @@ -558,7 +571,7 @@ def _orbitals_name_get(self,element_orbital_class:str): - def _load_model(self,apiHrk,structure_tbtrans_file:str): + def _load_model(self,model,AtomicData_options,structure_tbtrans_file:str): '''The `load_dptb_model` function loads a DPTB or NNSK model and returns the Hamiltonian elements. Here run_sk is a boolean flag that determines whether to run the model using the NNSK or DPTB. @@ -591,23 +604,35 @@ def _load_model(self,apiHrk,structure_tbtrans_file:str): `overlap_block`. ''' - ## create BaseStruct - structure_base =BaseStruct( - atom=ase.io.read(structure_tbtrans_file), - format='ase', - cutoff=apiHrk.apihost.model_config['bond_cutoff'], - proj_atom_anglr_m=apiHrk.apihost.model_config['proj_atom_anglr_m'], - proj_atom_neles=apiHrk.apihost.model_config['proj_atom_neles'], - onsitemode=apiHrk.apihost.model_config['onsitemode'], - time_symm=apiHrk.apihost.model_config['time_symm'] - ) - - apiHrk.update_struct(structure_base) - allbonds,hamil_block,overlap_block = apiHrk.get_HR() + structase = read(structure_tbtrans_file) + + data = AtomicData.from_ase(structase,**AtomicData_options) + data = AtomicData.to_AtomicDataDict(data) + data = model.idp(data) + + data = model(data) + HR_dict = feature_to_block(data,model.idp) + allbonds = [] + hamil_block = [] + overlap_block = [] + + for key,value in HR_dict.items(): + bond = key.split('_') + bond = torch.as_tensor([int(bond[i]) for i in range(len(bond))]) + + iatom,jatom = model.idp.untransform(data[AtomicDataDict.ATOM_TYPE_KEY][bond[0]-1]),\ + model.idp.untransform(data[AtomicDataDict.ATOM_TYPE_KEY][bond[1]-1]) + allbonds.append(torch.cat([iatom,torch.tensor([bond[0]-1]),jatom,torch.tensor([bond[1]-1]),bond[2:]])) + hamil_block.append(value) + + allbonds = torch.stack(allbonds) + hamil_block = torch.stack(hamil_block) + overlap_block = torch.stack([torch.eye(hamil_block.shape[-1]) for i in range(hamil_block.shape[0])]) + # TODO: check tbtrans support overlap matrix or not in TB-NEGF return allbonds,hamil_block,overlap_block - def hamiltonian_get(self,allbonds:torch.tensor,hamil_block:torch.tensor,overlap_block:torch.tensor,Hamil_sisl,energy_unit_option:str): + def hamiltonian_get(self,allbonds:torch.tensor,hamil_block:torch.tensor,overlap_block:torch.tensor,Hamil_sisl): '''The function `hamiltonian_get` takes in various parameters and calculates the Hamiltonian matrix for a given set of bonds, storing the result in the `Hamil_sisl` matrix. @@ -632,16 +657,8 @@ def hamiltonian_get(self,allbonds:torch.tensor,hamil_block:torch.tensor,overlap_ The `energy_unit_option` parameter is a string that specifies the unit of energy for the calculation. It can be either "Hartree" or "eV". - ''' - - if energy_unit_option=='Hartree': - unit_constant = 1.0000 - - elif energy_unit_option=='eV': - unit_constant = 13.605662285137 * 2 - - else: - raise RuntimeError("energy_unit_option should be 'Hartree' or 'eV'") + ''' + # print(len(allbonds)) @@ -669,7 +686,7 @@ def hamiltonian_get(self,allbonds:torch.tensor,hamil_block:torch.tensor,overlap_ for orb_a in range(orb_first_a,orb_last_a): for orb_b in range(orb_first_b,orb_last_b): - Hamil_sisl[orb_a,orb_b]=hamil_block[i].detach().numpy()[orb_a-orb_first_a,orb_b-orb_first_b]*unit_constant + Hamil_sisl[orb_a,orb_b]=hamil_block[i].detach().numpy()[orb_a-orb_first_a,orb_b-orb_first_b]*self.unit_constant # Hamil_sisl[orb_b,orb_a]=hamil_block[i].detach().numpy()[orb_b-orb_first_b,orb_a-orb_first_a]*unit_constant Hamil_sisl[orb_b,orb_a]=np.conjugate(Hamil_sisl[orb_a,orb_b]) else: @@ -685,7 +702,7 @@ def hamiltonian_get(self,allbonds:torch.tensor,hamil_block:torch.tensor,overlap_ for orb_a in range(orb_first_a,orb_last_a): for orb_b in range(orb_first_b,orb_last_b): - H_value = hamil_block[i].detach().numpy()[orb_a-orb_first_a,orb_b-orb_first_b]*unit_constant + H_value = hamil_block[i].detach().numpy()[orb_a-orb_first_a,orb_b-orb_first_b]*self.unit_constant if H_value != 0: Hamil_sisl[orb_a,orb_b,(x,y,z)]=H_value Hamil_sisl[orb_b,orb_a,(-1*x,-1*y,-1*z)]=np.conjugate(Hamil_sisl[orb_a,orb_b,(x,y,z)]) From 5067295a14205d87695aade475e06be1f2d1bec6 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 16 Apr 2024 09:33:45 +0800 Subject: [PATCH 119/209] add unit test for tbtrans --- .../best_nnsk_b3.600_c3.600_w0.300.json | 2 +- .../best_nnsk_b3.600_c3.600_w0.300_v2.json | 154 ++++++++++++++++++ .../tests/data/test_tbtrans/negf_tbtrans.json | 11 +- .../data/test_tbtrans/negf_tbtrans_v2.json | 61 +++++++ dptb/tests/test_tbtrans_init.py | 95 ++++++----- 5 files changed, 273 insertions(+), 50 deletions(-) create mode 100644 dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300_v2.json create mode 100644 dptb/tests/data/test_tbtrans/negf_tbtrans_v2.json diff --git a/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300.json b/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300.json index 7fddb9d8..c813e086 100644 --- a/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300.json +++ b/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300.json @@ -1,4 +1,4 @@ -{ +{ "version": 1, "onsite": { "N-N-2s-2s-0": [ 0.02462027035653591, diff --git a/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300_v2.json b/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300_v2.json new file mode 100644 index 00000000..c787f44e --- /dev/null +++ b/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300_v2.json @@ -0,0 +1,154 @@ +{ "version": 2, +"model_params": { + "onsite": { + "N-N-2s-2s-0": [ + 0.6699501676795943, + 0.007205560803413391 + ], + "N-N-2s-2p-0": [ + 0.22612018824468233, + -0.007032226305454969 + ], + "N-N-2p-2p-0": [ + 0.3430377054805226, + 0.010783562436699867 + ], + "N-N-2p-2p-1": [ + 0.1867882917636153, + -0.011892829090356827 + ], + "N-B-2s-2s-0": [ + 1.1162130544593456, + -0.007834071293473244 + ], + "N-B-2s-2p-0": [ + 713.3398433180281, + -28.22139549255371 + ], + "N-B-2p-2p-0": [ + 6.916408616925407, + 0.3701082468032837 + ], + "N-B-2p-2p-1": [ + -1.4403623455817633, + 0.03325718641281128 + ], + "B-N-2s-2s-0": [ + -2.9561736332997635, + -0.10621777176856995 + ], + "B-N-2s-2p-0": [ + 666.3230167462445, + 26.85447883605957 + ], + "B-N-2p-2p-0": [ + 3.6313602246940566, + -0.3127709925174713 + ], + "B-N-2p-2p-1": [ + -1.0460484334022282, + 0.00011800970969488844 + ], + "B-B-2s-2s-0": [ + -0.19516151133997006, + 0.007495054975152016 + ], + "B-B-2s-2p-0": [ + 0.12089952418187472, + 0.0030813836492598057 + ], + "B-B-2p-2p-0": [ + -0.03777636621520439, + -6.591381679754704e-05 + ], + "B-B-2p-2p-1": [ + -0.2547473785730268, + -0.017272837460041046 + ] + }, + "hopping": { + "N-N-2s-2s-0": [ + 1.6237806649493127, + -0.21457917988300323 + ], + "N-N-2s-2p-0": [ + 1.1477216133816237, + 0.5767796635627747 + ], + "N-N-2p-2p-0": [ + 2.743001876634442, + 0.5027011632919312 + ], + "N-N-2p-2p-1": [ + -0.1565485441618486, + -1.0040007829666138 + ], + "N-B-2s-2s-0": [ + 1.797442215156814, + 2.485130786895752 + ], + "N-B-2s-2p-0": [ + -5.363815904025437, + -1.884203314781189 + ], + "N-B-2p-2s-0": [ + -1.8172819624053882, + -2.8326284885406494 + ], + "N-B-2p-2p-0": [ + -5.793621813925713, + 3.601222276687622 + ], + "N-B-2p-2p-1": [ + 0.9264441021050773, + -4.79115104675293 + ], + "B-B-2s-2s-0": [ + -1.6774976858596724, + -0.4071168601512909 + ], + "B-B-2s-2p-0": [ + 1.3086320235346396, + 0.8385105729103088 + ], + "B-B-2p-2p-0": [ + -3.087088433025693, + 0.5754562020301819 + ], + "B-B-2p-2p-1": [ + 0.2074702723503671, + -0.7790966033935547 + ] + }}, + "unit": "eV", + "common_options": { + "basis":{"N": [ + "2s", + "2p" + ], + "B": [ + "2s", + "2p" + ]}, + "device":"cpu", + "dtype": "float32", + "overlap":false + }, + "model_options": { + "nnsk":{ + "onsite":{ + "method":"strain", + "rs": 6.0, + "w": 0.1 + }, + "hopping":{ + "method":"powerlaw", + "rs":3.6, + "w":0.3 + }, + "freeze":false, + "push":false, + "soc":{} + } + } +} \ No newline at end of file diff --git a/dptb/tests/data/test_tbtrans/negf_tbtrans.json b/dptb/tests/data/test_tbtrans/negf_tbtrans.json index 8abee871..e8579bba 100644 --- a/dptb/tests/data/test_tbtrans/negf_tbtrans.json +++ b/dptb/tests/data/test_tbtrans/negf_tbtrans.json @@ -25,10 +25,15 @@ "w":0.3 }, "freeze":false, - "push":false + "push":false, + "soc":{} } }, "structure":"./test_hBN_zigzag_struct.xyz", + "AtomicData_options": { + "r_max":3.6, + "oer_max": 1.6 + }, "task_options": { "task": "tbtrans_negf", @@ -69,6 +74,10 @@ "espacing": 0.1, "emin": -2, "emax": 2, + "nel_atom": { + "N": 5, + "B": 3 + }, "e_fermi": -9.874357223510742, "density_options":{ "method": "Ozaki" diff --git a/dptb/tests/data/test_tbtrans/negf_tbtrans_v2.json b/dptb/tests/data/test_tbtrans/negf_tbtrans_v2.json new file mode 100644 index 00000000..e53237d9 --- /dev/null +++ b/dptb/tests/data/test_tbtrans/negf_tbtrans_v2.json @@ -0,0 +1,61 @@ +{ + "structure":"./test_hBN_zigzag_struct.xyz", + "AtomicData_options": { + "r_max":3.6, + "oer_max": 1.6, + "pbc":true + }, + "task_options": + { + "task": "tbtrans_negf", + "scf": true, + "block_tridiagonal": false, + "ele_T": 500, + "unit": "eV", + "scf_options":{ + "mode": "PDIIS", + "mixing_period": 3, + "step_size": 0.05, + "n_history": 6, + "abs_err": 1e-6, + "rel_err": 1e-4, + "max_iter": 100 + }, + "stru_options":{ + "pbc":[false, true, false], + "kmesh":[1,1,1], + "device":{ + "id":"8-12", + "sort": true + }, + "lead_L":{ + "id":"0-8", + "voltage":0.0 + }, + "lead_R":{ + "id":"12-20", + "voltage":0.0 + } + }, + "poisson_options": { + "solver": "fmm", + "err": 1e-5 + }, + "sgf_solver": "Sancho-Rubio", + "espacing": 0.1, + "emin": -2, + "emax": 2, + "e_fermi": -9.874357223510742, + "density_options":{ + "method": "Ozaki" + }, + "eta_lead":1e-5, + "eta_device":0.0, + "out_dos": true, + "out_tc": true, + "out_ldos": true, + "out_current_nscf": true, + "out_density": true, + "out_lcurrent": true + } +} diff --git a/dptb/tests/test_tbtrans_init.py b/dptb/tests/test_tbtrans_init.py index e92c7eb2..fd8a134e 100644 --- a/dptb/tests/test_tbtrans_init.py +++ b/dptb/tests/test_tbtrans_init.py @@ -4,7 +4,7 @@ from dptb.nn.build import build_model from dptb.negf.negf_hamiltonian_init import NEGFHamiltonianInit import torch -# from dptb.postprocess.tbtrans_init import TBTransInputSet +from dptb.postprocess.tbtrans_init import TBTransInputSet from dptb.utils.tools import j_loader import numpy as np @@ -22,45 +22,42 @@ def test_tbtrans_init(root_directory): pytest.skip('sisl is not installed which is necessary for TBtrans Input Generation. Therefore, skipping test_tbtrans_init.') model_ckpt = f'{root_directory}/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300.json' - results_path=f'{root_directory}/dptb/tests/data/test_tbtrans/' + results_path=f'{root_directory}/dptb/tests/data/test_tbtrans/test_output' input_path = root_directory +"/dptb/tests/data/test_tbtrans/negf_tbtrans.json" - structure=root_directory +"/dptb/tests/data/test_tbtrans/structure_tbtrans.vasp" + structure=root_directory +"/dptb/tests/data/test_tbtrans/test_hBN_zigzag_struct.xyz" negf_json = json.load(open(input_path)) + model_json = json.load(open(model_ckpt)) model = build_model(model_ckpt,model_options=negf_json['model_options'],common_options=negf_json['common_options']) - hamiltonian = NEGFHamiltonianInit(model=model, - AtomicData_options=negf_json['AtomicData_options'], - structure=structure, - pbc_negf = negf_json['task_options']["stru_options"]['pbc'], - stru_options = negf_json['task_options']['stru_options'], - unit = negf_json['task_options']['unit'], - results_path=results_path, - torch_device = torch.device('cpu')) - # apihost = NNSKHost(checkpoint=model_ckpt, config=config) # apihost.register_plugin(InitSKModel()) # apihost.build() # apiHrk = NN2HRK(apihost=apihost, mode='nnsk') - run_opt = { - "run_sk": True, - "init_model":model_ckpt, - "results_path":f'{root_directory}/dptb/tests/data/test_tbtrans/', - "structure":f'{root_directory}/dptb/tests/data/test_tbtrans/test_hBN_zigzag_struct.xyz', - "log_path": '/data/DeepTB/dptb_Zjj/DeePTB/dptb/tests/data/test_tbtrans/output', - "log_level": 5, - "use_correction":False - } - - jdata = j_loader(config) - jdata = jdata['task_options'] - tbtrans_hBN = TBTransInputSet(apiHrk=apiHrk,run_opt=run_opt, jdata=jdata) - - # check _orbitals_name_get + # run_opt = { + # "run_sk": True, + # "init_model":model_ckpt, + # "results_path":f'{root_directory}/dptb/tests/data/test_tbtrans/', + # "structure":f'{root_directory}/dptb/tests/data/test_tbtrans/test_hBN_zigzag_struct.xyz', + # "log_path": '/data/DeepTB/dptb_Zjj/DeePTB/dptb/tests/data/test_tbtrans/output', + # "log_level": 5, + # "use_correction":False + # } + + # jdata = j_loader(config) + # jdata = jdata['task_options'] + tbtrans_hBN = TBTransInputSet(model = model, + AtomicData_options = negf_json['AtomicData_options'], + structure = structure, + results_path= results_path, + basis_dict=negf_json['common_options']['basis'], + **negf_json['task_options']) + + # # check _orbitals_name_get element_orbital_name = tbtrans_hBN._orbitals_name_get(['2s', '2p']) assert element_orbital_name == ['2s', '2py', '2pz', '2px'] element_orbital_name = tbtrans_hBN._orbitals_name_get(['3s', '3p', 'd*']) @@ -71,21 +68,21 @@ def test_tbtrans_init(root_directory): tbtrans_hBN.hamil_get_write(write_nc=False) assert (tbtrans_hBN.allbonds_all[0].detach().numpy()-np.array([5, 0, 5, 0, 0, 0, 0])).max()<1e-5 assert (tbtrans_hBN.allbonds_all[50].detach().numpy()-np.array([ 5, 2, 5, 18, 0, 0, -1])).max()<1e-5 - assert (tbtrans_hBN.hamil_block_all[0].detach().numpy()- np.array([[-0.73303634, 0. , 0. , 0. ], + assert (tbtrans_hBN.hamil_block_all[0].detach().numpy()*1.0000/13.605662285137/2- np.array([[-0.73303634, 0. , 0. , 0. ], [ 0. , 0.04233637, 0. , 0. ], [ 0. , 0. , 0.04233636, 0. ], [ 0. , 0. , 0. , -0.27156556]])).max()<1e-5 - assert (tbtrans_hBN.hamil_block_all[50].detach().numpy()-np.array([[-0.03164609, -0. , 0.02028139, -0. ], + assert (tbtrans_hBN.hamil_block_all[50].detach().numpy()*1.0000/13.605662285137/2-np.array([[-0.03164609, -0. , 0.02028139, -0. ], [ 0. , 0.00330366, 0. , 0. ], [-0.02028139, 0. , -0.05393751, 0. ], [ 0. , 0. , 0. , 0.00330366]])).max()<1e-5 assert (tbtrans_hBN.allbonds_lead_L[0].detach().numpy()-np.array([5, 0, 5, 0, 0, 0, 0])).max()<1e-5 assert (tbtrans_hBN.allbonds_lead_L[50].detach().numpy()-np.array([5, 4, 7, 7, 0, 0, 0])).max()<1e-5 - assert (tbtrans_hBN.hamil_block_lead_L[0].detach().numpy()-np.array([[-0.73303634, 0. , 0. , 0. ], + assert (tbtrans_hBN.hamil_block_lead_L[0].detach().numpy()*1.0000/13.605662285137/2-np.array([[-0.73303634, 0. , 0. , 0. ], [ 0. , 0.04233637, 0. , 0. ], [ 0. , 0. , 0.04233636, 0. ], [ 0. , 0. , 0. , -0.27156556]])).max()<1e-5 - assert (tbtrans_hBN.hamil_block_lead_L[50].detach().numpy()-np.array([[ 0.1145315 , -0.06116847, 0.10594689, 0. ], + assert (tbtrans_hBN.hamil_block_lead_L[50].detach().numpy()*1.0000/13.605662285137/2-np.array([[ 0.1145315 , -0.06116847, 0.10594689, 0. ], [ 0.15539739, -0.04634972, 0.22751862, 0. ], [-0.26915616, 0.22751862, -0.30906558, 0. ], [-0. , 0. , 0. , 0.08500822]])).max()<1e-5 @@ -100,25 +97,27 @@ def test_tbtrans_init(root_directory): G_eigs = G_eigs.apply.array.eigh() -Ef M_eigs = M_eigs.apply.array.eigh() -Ef - G_eigs_right = np.array([[-19.95431228, -17.10836511, -17.10836511, -16.91249093, -11.20658215, - -10.01096331, -10.01096331, -8.11484357, -8.11484357, -7.60482165, - -6.65543971, -3.79243033, -3.79243032, -3.22893608, -3.22893608, - -3.17565711, 3.18687914, 3.2401581, 3.2401581, 6.4529848, - 7.70578579, 7.91102607 , 10.91061078 , 10.91061078 , 23.6481713, - 23.6481713, 28.30797724 , 28.30797738 , 28.65762375 , 30.78500847, - 33.2545355 , 33.2545355 ]]) - - M_eigs_right = np.array([[-18.69719147, -18.69719147, -16.91249065, -16.91249065, -11.20658214, - -11.20658214, -7.4476675, -7.4476675 , -6.65543987 ,-6.65543987, - -5.79288269, -5.79288269, -5.21972335 , -5.21972335 , -3.17565711, - -3.17565711, 3.18687914, 3.18687914 , 5.84897577 , 5.84897577, - 7.74721734, 7.74721734, 7.91102615 , 7.91102615 , 28.12953979, - 28.12953979, 28.65762339, 28.65762339, 29.54228118, 29.54228118, - 30.78500872, 30.78500872]]) + G_eigs_correct = np.array([[-19.9606056213, -17.1669273376, -17.0796546936, -16.8952560425, + -11.2022151947, -10.1263637543, -9.9411945343, -8.1748561859, + -8.0049209595, -7.6048202515, -6.6505813599, -3.7902746201, + -3.7882986069, -3.2289390564, -3.2289323807, -3.1756639481, + 3.1868720055, 3.2401518822, 3.2401537895, 6.4502696991, + 7.7058019638, 7.8654155731, 10.8850431442, 10.9806480408, + 23.6024589539, 23.6807479858, 28.3076782227, 28.3091220856, + 28.6845626831, 30.7864990234, 33.2417755127, 33.2541275024]]) + + M_eigs_correct = np.array([[-18.7672195435, -18.6261577606, -16.9293708801, -16.9104366302, + -11.2307147980, -11.1986064911, -7.5301399231, -7.3464851379, + -6.6541309357, -6.6479454041, -5.7928838730, -5.7928771973, + -5.2177238464, -5.2155799866, -3.1756563187, -3.1756496429, + 3.1868739128, 3.1868796349, 5.8489637375, 5.8489723206, + 7.6596388817, 7.7445230484, 7.9219026566, 7.9709992409, + 28.1335067749, 28.1563091278, 28.6396503448, 28.6454830170, + 29.5444660187, 29.5520248413, 30.7856063843, 30.7873668671]]) - assert (G_eigs[0]-G_eigs_right[0]).max()<1e-5 - assert (M_eigs[0]-M_eigs_right[0]).max()<1e-5 + assert (G_eigs[0]-G_eigs_correct[0]).max()<1e-4 + assert (M_eigs[0]-M_eigs_correct[0]).max()<1e-4 From db076ee053e285c7ce9e89e53641e019edae54ad Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 16 Apr 2024 09:45:25 +0800 Subject: [PATCH 120/209] add btd term in test_negf* --- .gitignore | 2 ++ dptb/tests/test_negf_density_Ozaki.py | 3 ++- dptb/tests/test_negf_device_property.py | 3 ++- dptb/tests/test_negf_negf_hamiltonian_init.py | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index de422d42..10428c4f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ dptb/tests/data/test_all/fancy_ones/checkpoint/best_nnsk_b4.000_c4.000_w0.300.js dptb/tests/data/test_negf/test_negf_run/out_negf/run_config.json dptb/data/try_test.ipynb dptb/tests/data/test_negf/show.ipynb +dptb/tests/data/test_tbtrans/show.ipynb run_config.json dptb/nnet/__pycache__/ dptb/sktb/__pycache__/ @@ -172,3 +173,4 @@ _date.py _version.py /.idea/ + diff --git a/dptb/tests/test_negf_density_Ozaki.py b/dptb/tests/test_negf_density_Ozaki.py index 96948db7..329d9df8 100644 --- a/dptb/tests/test_negf_density_Ozaki.py +++ b/dptb/tests/test_negf_density_Ozaki.py @@ -42,7 +42,8 @@ def test_negf_density_Ozaki(root_directory): stru_options = negf_json['task_options']['stru_options'], unit = negf_json['task_options']['unit'], results_path=results_path, - torch_device = torch.device('cpu')) + torch_device = torch.device('cpu'), + block_tridiagonal=negf_json['task_options']['block_tridiagonal']) # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) diff --git a/dptb/tests/test_negf_device_property.py b/dptb/tests/test_negf_device_property.py index a616e928..5ee5d58b 100644 --- a/dptb/tests/test_negf_device_property.py +++ b/dptb/tests/test_negf_device_property.py @@ -38,7 +38,8 @@ def test_negf_Device(root_directory): stru_options = negf_json['task_options']['stru_options'], unit = negf_json['task_options']['unit'], results_path=results_path, - torch_device = torch.device('cpu')) + torch_device = torch.device('cpu'), + block_tridiagonal=negf_json['task_options']['block_tridiagonal']) # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) diff --git a/dptb/tests/test_negf_negf_hamiltonian_init.py b/dptb/tests/test_negf_negf_hamiltonian_init.py index 79942d93..6ea4b356 100644 --- a/dptb/tests/test_negf_negf_hamiltonian_init.py +++ b/dptb/tests/test_negf_negf_hamiltonian_init.py @@ -40,7 +40,8 @@ def test_negf_Hamiltonian(root_directory): stru_options = negf_json['task_options']['stru_options'], unit = negf_json['task_options']['unit'], results_path=results_path, - torch_device = torch.device('cpu')) + torch_device = torch.device('cpu'), + block_tridiagonal=negf_json['task_options']['block_tridiagonal']) # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) From dc737c8f2f600179980db6910c156cd3ad39adac Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 16 Apr 2024 10:04:22 +0800 Subject: [PATCH 121/209] fix pyamg import error --- dptb/negf/poisson_init.py | 65 ++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 13205e6a..44afdb57 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -1,6 +1,6 @@ import numpy as np -import pyamg #TODO: later add it to optional dependencies,like sisl -from pyamg.gallery import poisson +# import pyamg #TODO: later add it to optional dependencies,like sisl +# from pyamg.gallery import poisson from dptb.utils.constants import elementary_charge from dptb.utils.constants import Boltzmann, eV2J from scipy.constants import epsilon_0 as eps0 #TODO:later add to untils.constants.py @@ -217,35 +217,6 @@ def to_scipy_Jac_B(self,dtype=np.float64): return Jacobian,B - def solver_pyamg(self,A,b,tolerance=1e-7,accel=None): - # solve the Poisson equation - # log.info(msg="Solve Poisson equation by pyamg") - - pyamg_solver = pyamg.aggregation.smoothed_aggregation_solver(A, max_levels=1000) - del A - # print('Poisson equation solver: ',pyamg_solver) - residuals = [] - - def callback(x): - # residuals calculated in solver is a pre-conditioned residual - # residuals.append(np.linalg.norm(b - A.dot(x)) ** 0.5) - print( - " {:4d} residual = {:.5e} x0-residual = {:.5e}".format( - len(residuals) - 1, residuals[-1], residuals[-1] / residuals[0] - ) - ) - - x = pyamg_solver.solve( - b, - tol=tolerance, - # callback=callback, - residuals=residuals, - accel=accel, - cycle="W", - maxiter=1e3, - ) - return x - def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7): # solve the Poisson equation with Newton-Raphson method @@ -295,6 +266,38 @@ def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7): max_diff = np.max(abs(self.phi-self.phi_old)) return max_diff + def solver_pyamg(self,A,b,tolerance=1e-7,accel=None): + # solve the Poisson equation + # log.info(msg="Solve Poisson equation by pyamg") + try: + import pyamg + except: + raise ImportError("pyamg is required for Poisson solver. Please install pyamg firstly! ") + + pyamg_solver = pyamg.aggregation.smoothed_aggregation_solver(A, max_levels=1000) + del A + # print('Poisson equation solver: ',pyamg_solver) + residuals = [] + + def callback(x): + # residuals calculated in solver is a pre-conditioned residual + # residuals.append(np.linalg.norm(b - A.dot(x)) ** 0.5) + print( + " {:4d} residual = {:.5e} x0-residual = {:.5e}".format( + len(residuals) - 1, residuals[-1], residuals[-1] / residuals[0] + ) + ) + + x = pyamg_solver.solve( + b, + tol=tolerance, + # callback=callback, + residuals=residuals, + accel=accel, + cycle="W", + maxiter=1e3, + ) + return x def NR_construct_Jac_B(self,J,B): # construct the Jacobian and B for the Poisson equation From 6d2ef8f4da7696e702fb0ce284f9df7eb6d95a8a Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 16 Apr 2024 13:58:27 +0800 Subject: [PATCH 122/209] update docstring in tbtrans_init --- dptb/postprocess/tbtrans_init.py | 269 +++++-------------------------- 1 file changed, 37 insertions(+), 232 deletions(-) diff --git a/dptb/postprocess/tbtrans_init.py b/dptb/postprocess/tbtrans_init.py index 15844793..8264c292 100644 --- a/dptb/postprocess/tbtrans_init.py +++ b/dptb/postprocess/tbtrans_init.py @@ -37,57 +37,7 @@ class TBTransInputSet(object): - """ The TBTransInputSet class is used to transform input data for DeePTB-negf into a TBTrans object. - Attention: the transport direction is forced to be z direction in this stage, please make sure the structure is in - correct direction. - - Properties - ----------- - - apiHrk - apiHrk has been loaded in the run.py file. It is used as an API for - performing certain operations or accessing certain functionalities. - - run_opt - The `run_opt` parameter is a dictionary that contains options for running the model. - It has been loaded and prepared in the run.py file. - - jdata - jdata is a JSON object that contains options and parameters for the task Generation of Input Files for TBtrans. - It is loaded in the run.py. - - results_path - The `results_path` parameter is a string that represents the path to the directory where the - results will be saved. - - stru_options - The `stru_options` parameter is a dictionary that contains options for the structure from DeePTB input. - - energy_unit_option - The `energy_unit_option` parameter is a string that specifies the unit of energy for the - calculation. It can be either "Hartree" or "eV". - - geom_all - The `geom_all` parameter is the geometry of the whole structure, including the device and leads. - - H_all - The `H_all` parameter is the sisl.Hamiltonian for the entire system, including the device and leads. - - H_lead_L - The `H_lead_L` parameter is sisl.Hamiltonian for the left lead. - - H_lead_R - The `H_lead_R` parameter is sisl.Hamiltonian for the right lead. - - allbonds_all - The `allbonds_all` parameter is a tensor that contains all of the bond information for the entire system. - - allbonds_lead_L - The `allbonds_lead_L` parameter is a tensor that contains all of the bond information for the left lead. - - allbonds_lead_R - The `allbonds_lead_R` parameter is a tensor that contains all of the bond information for the right lead. - - hamil_block_all - The `hamil_block_all` parameter is a tensor that contains the Hamiltonian matrix elements for each specific bond in allbonds_all. - - hamil_block_lead_L - The `hamil_block_lead_L` parameter is a tensor that contains the Hamiltonian matrix elements for each specific bond in allbonds_lead_L. - - hamil_block_lead_R - The `hamil_block_lead_L` parameter is a tensor that contains the Hamiltonian matrix elements for each specific bond in allbonds_lead_R. - - overlap_block_all - The `overlap_block_all` parameter is a tensor that contains the overlap matrix elements for each specific basis in the entire system. - - overlap_block_lead_L - The `overlap_block_lead_L` parameter is a tensor that contains the overlap matrix elements for each specific basis in the left lead. - - overlap_block_lead_R - The `overlap_block_lead_R` parameter is a tensor that contains the overlap matrix elements for each specific basis in the right lead. - """ def __init__(self, model: torch.nn.Module, @@ -98,7 +48,38 @@ def __init__(self, results_path: Optional[str]=None, unit: str='eV', nel_atom: Optional[dict]=None, - **kwargs): + **kwargs): + ''' + This function initializes properties and calculations for TBtrans input files. + + Parameters + ---------- + model : torch.nn.Module + The `model` parameter in the `__init__` method is expected to be an instance of + `torch.nn.Module`. This parameter is used to store a neural network model that has been loaded + in the `run.py` script. + AtomicData_options : dict + The `AtomicData_options` parameter is a dictionary containing options for atomic data. + These options are used to provide necessary atomic information for the model. + structure : Union[AtomicData, ase.Atoms, str] + The `structure` parameter in the `__init__` method is used to specify the structure of the + system. + stru_options : dict + The `stru_options` parameter in the `__init__` method is a dictionary containing options for + the structure. This dictionary includes pbc, kmesh and the regions division of the structure. + basis_dict : dict + The `basis_dict` parameter in the `__init__` method is used in the `orbitals_get` + method. It is a dictionary that contains the basis functions used in the calculations. + results_path : Optional[str] + The `results_path` parameter is a string that specifies the path where the results of the + calculations will be saved. + unit : str, optional + The `unit` parameter in the `__init__` function is used to specify the energy unit for TBtrans + calculation. It can take two possible values: eV or Hartree. The default value is eV. + nel_atom : Optional[dict] + The `nel_atom` parameter in the `__init__` function is an optional dictionary that represents + the number of electrons per atom. + ''' self.model = model #apiHrk has been loaded in run.py self.AtomicData_options = AtomicData_options @@ -147,41 +128,7 @@ def __init__(self, else: raise RuntimeError("energy_unit_option should be 'Hartree' or 'eV'") - # def __init__(self, apiHrk, run_opt, jdata): - # self.apiHrk = apiHrk #apiHrk has been loaded in run.py - # self.jdata = jdata #jdata has been loaded in run.py, jdata is written in negf.json - - # self.results_path = run_opt['results_path'] - # if not self.results_path.endswith('/'):self.results_path += '/' - # self.stru_options = j_must_have(jdata, "stru_options") - # self.energy_unit_option = 'eV' # enenrgy unit for TBtrans calculation - - - # self.geom_all,self.geom_lead_L,self.geom_lead_R,self.all_tbtrans_stru,self.lead_L_tbtrans_stru,self.lead_R_tbtrans_stru\ - # = self.read_rewrite_structure(run_opt['structure'],self.stru_options,self.results_path) - - - # self.orbitals_get(self.geom_all,self.geom_lead_L,self.geom_lead_R,apiHrk=apiHrk) - - # self.H_all = sisl.Hamiltonian(self.geom_all) - # self.H_lead_L = sisl.Hamiltonian(self.geom_lead_L) - # self.H_lead_R = sisl.Hamiltonian(self.geom_lead_R) - - - # #important properties for later use - - # ##allbonds matrx, hamiltonian matrix, overlap matrix for the whole structure - # self.allbonds_all = None - # self.hamil_block_all = None - # self.overlap_block_all = None - # ##allbonds matrx, hamiltonian matrix, overlap matrix for lead_L - # self.allbonds_lead_L = None - # self.hamil_block_lead_L = None - # self.overlap_block_lead_L = None - # ##allbonds matrx, hamiltonian matrix, overlap matrix for lead_R - # self.allbonds_lead_R = None - # self.hamil_block_lead_R = None - # self.overlap_block_lead_R = None + def hamil_get_write(self,write_nc:bool=True): @@ -285,13 +232,6 @@ def read_rewrite_structure(self,structure_file:str,struct_options:dict,results_p geom_lead_R = geom_lead_R.sort(axis=(2,1,0));geom_lead_L=geom_lead_L.sort(axis=(2,1,0)) geom_all=geom_all.sort(axis=(2,1,0)) - ##redefine the Lattice vector of Lead L/R - # lead_L_cor = geom_lead_L.axyz() - # Natom_PL = int(len(lead_L_cor)/2) - # first_PL_leadL = lead_L_cor[Natom_PL:];second_PL_leadL =lead_L_cor[:Natom_PL] - # PL_leadL_zspace = first_PL_leadL[0][2]-second_PL_leadL[-1][2] # the distance between Principal layers - # geom_lead_L.lattice.cell[2,2]=first_PL_leadL[-1][2]-second_PL_leadL[0][2]+PL_leadL_zspace - # assert geom_lead_L.lattice.cell[2,2]>0 lead_L_cor = geom_lead_L.axyz() #Return the atomic coordinates in the supercell of a given atom. cell = np.array(geom_lead_L.lattice.cell)[:2] @@ -318,28 +258,7 @@ def read_rewrite_structure(self,structure_file:str,struct_options:dict,results_p cell = np.concatenate([cell, R_vec.reshape(1,-1)]) # PL_leadR_zspace = second_PL_leadR[0][2]-first_PL_leadR[-1][2] geom_lead_R.lattice.cell = cell - # print(cell) - # assert geom_lead_R.lattice.cell[2,2]>0 - - # set supercell - # PBC requirement in TBtrans - ## lead calculation have periodicity in all directions,which is different from dptb-negf - ## all(lead + central part) have periodicity in x,y,z directions: interaction between supercells - ### not sure that geom_all need pbc in z direction - - # pbc = struct_options['pbc'] - # if pbc[0]==True: nsc_x = 3 - # else: nsc_x = 1 - - # if pbc[1]==True: nsc_y = 3 - # else: nsc_y = 1 - - # geom_lead_L.set_nsc(a=nsc_x,b=nsc_y,c=3) #Set the number of super-cells in the `Lattice` object - # geom_lead_R.set_nsc(a=nsc_x,b=nsc_y,c=3) - # geom_all.set_nsc(a=nsc_x,b=nsc_y,c=3) - - # output sorted geometry into xyz Structure file all_tbtrans_stru=results_path+'structure_tbtrans.xyz' sorted_structure = sisl.io.xyzSile(all_tbtrans_stru,'w') geom_all.write(sorted_structure) @@ -472,109 +391,11 @@ def _orbitals_name_get(self,element_orbital_class:str): raise RuntimeError("At this stage dptb-negf only supports s, p, d orbitals") # print(orbital_name_list) # raise RuntimeError('stop here') - return orbital_name_list - - # def _shell_electrons(self,element_symbol): - # '''The function `_shell_electrons` calculates the number of shell electrons for a given element symbol. - - # In this code, shell electron number is trivial for subgroup element. It would be improved soon. - - # Parameters - # ---------- - # element_symbol - # The element symbol is a string representing the symbol of an element on the periodic table. For - # example, "H" for hydrogen, "O" for oxygen, or "Fe" for iron. - - # Returns - # ------- - # the number of shell electrons for the given element symbol. - - # ''' - # atomic_number = PeriodicTable().Z_int(element_symbol) - # assert atomic_number > 1 and atomic_number <=118 - - # if atomic_number>18: - # print('In this code, shell electron number is trivial for subgroup element ') - # rare_element_index = [2,10,18,36,54,86] - - # for index in range(len(rare_element_index)-1): - # if atomic_number > rare_element_index[index] and atomic_number <= rare_element_index[index+1]: - # core_ele_num = atomic_number-rare_element_index[index] - - # print(element_symbol+' shell elec: '+str(core_ele_num)) - # return core_ele_num - - - - # def _load_dptb_model(self,checkfile:str,config:str,structure_tbtrans_file:str,run_sk:bool,use_correction:Optional[str]): - - # def _load_model(self,apiHrk,structure_tbtrans_file:str): - # '''The `_load_model` function loads model from deeptb and returns the Hamiltonian elements. - - # Parameters - # ---------- - # apiHrk - # apiHrk has been loaded in the run.py file. It is used as an API for - # performing certain operations or accessing certain functionalities when loading dptb model. - # structure_tbtrans_file : str - # The parameter `structure_tbtrans_file` is a string that represents the file path to the structure - # file in the TBTrans format. - - # Returns - # ------- - # The function `_load_model` returns three variables: `allbonds`, `hamil_block`, and - # `overlap_block`. - - # ''' - # if all((use_correction, run_sk)): - # raise RuntimeError("--use-correction and --train_sk should not be set at the same time") - - # ## read Hamiltonian elements - # if run_sk: - # apihost = NNSKHost(checkpoint=checkfile, config=config) - # apihost.register_plugin(InitSKModel()) - # apihost.build() - # ## define nnHrk for Hamiltonian model. - # apiHrk = NN2HRK(apihost=apihost, mode='nnsk') - # else: - # apihost = DPTBHost(dptbmodel=checkfile,use_correction=use_correction) - # apihost.register_plugin(InitDPTBModel()) - # apihost.build() - # apiHrk = NN2HRK(apihost=apihost, mode='dptb') - - - # self.allbonds_all,self.hamil_block_all,self.overlap_block_all\ - # =self._load_model(self.apiHrk,self.all_tbtrans_stru) - # self.allbonds_lead_L,self.hamil_block_lead_L,self.overlap_block_lead_L\ - # =self._load_model(self.apiHrk,self.lead_L_tbtrans_stru) - # self.allbonds_lead_R,self.hamil_block_lead_R,self.overlap_block_lead_R\ - # =self._load_model(self.apiHrk,self.lead_R_tbtrans_stru) - - # structure_tbtrans_file_list = [self.all_tbtrans_stru,self.lead_L_tbtrans_stru,self.lead_R_tbtrans_stru] - - ## create BaseStruct - # structure_base =BaseStruct( - # atom=ase.io.read(structure_tbtrans_file), - # format='ase', - # cutoff=apiHrk.apihost.model_config['bond_cutoff'], - # proj_atom_anglr_m=apiHrk.apihost.model_config['proj_atom_anglr_m'], - # proj_atom_neles=apiHrk.apihost.model_config['proj_atom_neles'], - # onsitemode=apiHrk.apihost.model_config['onsitemode'], - # time_symm=apiHrk.apihost.model_config['time_symm'] - # ) - - # apiHrk.update_struct(structure_base) - # allbonds,hamil_block,overlap_block = apiHrk.get_HR() - - # return allbonds,hamil_block,overlap_block - - + return orbital_name_list def _load_model(self,model,AtomicData_options,structure_tbtrans_file:str): - '''The `load_dptb_model` function loads a DPTB or NNSK model and returns the Hamiltonian elements. - Here run_sk is a boolean flag that determines whether to run the model using the NNSK or DPTB. - + '''The `load_dptb_model` function loads model and returns the Hamiltonian elements. Parameters ---------- checkfile : str @@ -611,15 +432,14 @@ def _load_model(self,model,AtomicData_options,structure_tbtrans_file:str): data = model.idp(data) data = model(data) - HR_dict = feature_to_block(data,model.idp) - allbonds = [] + HR_dict = feature_to_block(data,model.idp) # get HR + allbonds = [] # a list contain bond info in the form like (type1,atom1_index,type2,atom2_index,displacement) hamil_block = [] overlap_block = [] for key,value in HR_dict.items(): bond = key.split('_') bond = torch.as_tensor([int(bond[i]) for i in range(len(bond))]) - iatom,jatom = model.idp.untransform(data[AtomicDataDict.ATOM_TYPE_KEY][bond[0]-1]),\ model.idp.untransform(data[AtomicDataDict.ATOM_TYPE_KEY][bond[1]-1]) allbonds.append(torch.cat([iatom,torch.tensor([bond[0]-1]),jatom,torch.tensor([bond[1]-1]),bond[2:]])) @@ -658,18 +478,9 @@ def hamiltonian_get(self,allbonds:torch.tensor,hamil_block:torch.tensor,overlap_ calculation. It can be either "Hartree" or "eV". ''' - - - - # print(len(allbonds)) - # H_device.H[1000,1000]=1 - x_max = abs(allbonds[:,-3].numpy()).max() y_max = abs(allbonds[:,-2].numpy()).max() z_max = abs(allbonds[:,-1].numpy()).max() - # print('x_max: ',x_max) - # print('y_max: ',y_max) - # print('z_max: ',z_max) Hamil_sisl.set_nsc(a=2*abs(x_max)+1,b=2*abs(y_max)+1,c=2*abs(z_max)+1) # set the number of super-cells in Hamiltonian object in sisl, which is based on allbonds results @@ -693,12 +504,6 @@ def hamiltonian_get(self,allbonds:torch.tensor,hamil_block:torch.tensor,overlap_ x = allbonds[i,-3].numpy().tolist() y = allbonds[i,-2].numpy().tolist() z = allbonds[i,-1].numpy().tolist() - # consistent with supercell setting:Set the number of super-cells in the `Lattice` object in sisl - # if abs(x) > 1 or abs(y) > 1 or abs(z) > 1: - - # print("Unexpected supercell index: ",[x,y,z]) - # print("Attention: the supercell setting may be too small to satisfy the nearest cell interaction, \ - # error in Lead self-energy calculation may occur.") for orb_a in range(orb_first_a,orb_last_a): for orb_b in range(orb_first_b,orb_last_b): From e48fa82e30bf827a66efe165d72ed342cf5d7e73 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sun, 21 Apr 2024 17:39:17 +0800 Subject: [PATCH 123/209] rename some variables --- dptb/negf/split_btd.py | 2 +- dptb/postprocess/NEGF.py | 22 +++++++++---------- dptb/v1/v1test/_test_negf_recursive_gf_cal.py | 6 ++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/dptb/negf/split_btd.py b/dptb/negf/split_btd.py index 57d7fbaa..ec10ec69 100644 --- a/dptb/negf/split_btd.py +++ b/dptb/negf/split_btd.py @@ -525,7 +525,7 @@ def compute_edge(mat): """ # First get some statistics - if isinstance(mat, scipy.sparse.lil.lil_matrix): + if isinstance(mat, scipy.sparse.lil_matrix): row, col = mat.nonzero() else: row, col = np.where(mat != 0.0) # Output rows and columns of all non-zero elements. diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index bec65b81..ce41911c 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -49,7 +49,7 @@ def __init__(self, # self.apiH = apiHrk - + self.model = model self.results_path = results_path # self.jdata = jdata @@ -223,8 +223,8 @@ def compute(self): def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): - profiler = Profiler() - profiler.start() + + # profiler.start() # create real-space grid grid = self.get_grid(self.poisson_options["grid"],self.deviceprop.structure) @@ -307,10 +307,10 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): if iter_count > max_iter: log.info(msg="Warning! Poisson-NEGF iteration exceeds the upper limit of iterations {}".format(int(max_iter))) - profiler.stop() - with open('profile_report.html', 'w') as report_file: - report_file.write(profiler.output_html()) - break + # profiler.stop() + # with open('profile_report.html', 'w') as report_file: + # report_file.write(profiler.output_html()) + # break self.poisson_out = {} self.poisson_out['potential'] = torch.tensor(interface_poisson.phi) @@ -325,10 +325,10 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): self.negf_compute(scf_require=False,Vbias=self.potential_at_orb) # output the profile report in html format - if iter_count <= max_iter: - profiler.stop() - with open('profile_report.html', 'w') as report_file: - report_file.write(profiler.output_html()) + # if iter_count <= max_iter: + # profiler.stop() + # with open('profile_report.html', 'w') as report_file: + # report_file.write(profiler.output_html()) def negf_compute(self,scf_require=False,Vbias=None): diff --git a/dptb/v1/v1test/_test_negf_recursive_gf_cal.py b/dptb/v1/v1test/_test_negf_recursive_gf_cal.py index 83b7fdc9..17874dc7 100644 --- a/dptb/v1/v1test/_test_negf_recursive_gf_cal.py +++ b/dptb/v1/v1test/_test_negf_recursive_gf_cal.py @@ -108,8 +108,8 @@ def test_negf_RGF(root_directory): V = 0. if not hasattr(device, "hd") or not hasattr(device, "sd"): - device.hd, device.sd, _, _, _, _ = hamiltonian.get_hs_device(kpoint,V, block_tridiagonal) - s_in = [torch.zeros(i.shape).cdouble() for i in device.hd] + device.hd_k, device.sd, _, _, _, _ = hamiltonian.get_hs_device(kpoint,V, block_tridiagonal) + s_in = [torch.zeros(i.shape).cdouble() for i in device.hd_k] tags = ["g_trans","grd", "grl", "gru", "gr_left", \ "gnd", "gnl", "gnu", "gin_left", \ @@ -168,7 +168,7 @@ def test_negf_RGF(root_directory): assert abs(s_in[-1]-s_in_1_standard).max()<1e-5 - ans = recursive_gf(e, hl=[], hd=device.hd, hu=[], + ans = recursive_gf(e, hl=[], hd=device.hd_k, hu=[], sd=device.sd, su=[], sl=[], left_se=seL, right_se=seR, seP=None, s_in=s_in, s_out=None, eta=eta_device, chemiPot=device.mu) From 5490bed5de748c278f3cd2c40af7c57859a0ff23 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 22 Apr 2024 13:38:10 +0800 Subject: [PATCH 124/209] add BTD --- dptb/entrypoints/run.py | 8 +- dptb/negf/device_property.py | 27 ++- dptb/negf/negf_hamiltonian_init.py | 175 +++++++++++------- dptb/negf/recursive_green_cal.py | 40 ++-- dptb/postprocess/NEGF.py | 7 +- .../test_negf_run/negf_graphene_new.json | 2 +- 6 files changed, 165 insertions(+), 94 deletions(-) diff --git a/dptb/entrypoints/run.py b/dptb/entrypoints/run.py index b63489e9..1b7c9f07 100644 --- a/dptb/entrypoints/run.py +++ b/dptb/entrypoints/run.py @@ -11,7 +11,7 @@ from dptb.utils.tools import j_must_have from dptb.postprocess.NEGF import NEGF from dptb.postprocess.tbtrans_init import TBTransInputSet,sisl_installed - +from pyinstrument import Profiler log = logging.getLogger(__name__) @@ -87,6 +87,9 @@ def run( log.info(msg='band calculation successfully completed.') if task=='negf': + + profiler = Profiler() + profiler.start() negf = NEGF( model=model, AtomicData_options=jdata['AtomicData_options'], @@ -97,6 +100,9 @@ def run( negf.compute() log.info(msg='negf calculation successfully completed.') + profiler.stop() + with open(results_path+'/profile_report.html', 'w') as report_file: + report_file.write(profiler.output_html()) if task == 'tbtrans_negf': if not(sisl_installed): diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index 7261e263..5d154f3f 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -175,11 +175,22 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr # if not hasattr(self, "hd") or not hasattr(self, "sd"): #maybe the reason why different kpoint has different green function - if not hasattr(self, "hd") or not hasattr(self, "sd"): - self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(self.kpoint, self.V, block_tridiagonal) - elif self.newK_flag or self.newV_flag: # check whether kpoints or Vbias change or not - self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) - + # if [not hasattr(self, "hd") or not hasattr(self, "sd")]: + # if not self.block_tridiagonal: + # self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(self.kpoint, self.V, block_tridiagonal) + # else: + # self.hd, self.sd, self.hl, self.su, self.sl, self.hu = self.hamiltonian.get_hs_device(self.kpoint, self.V, block_tridiagonal) + # elif [self.newK_flag or self.newV_flag]: # check whether kpoints or Vbias change or not + # if not self.block_tridiagonal: + # self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) + # else: + # self.hd, self.sd, self.hl, self.su, self.sl, self.hu = self.hamiltonian.get_hs_device(self.kpoint, self.V, block_tridiagonal) + + # hd in format:(block_index,orb,orb) + if (hasattr(self, "hd") and hasattr(self, "sd")) or (self.newK_flag or self.newV_flag): + self.hd, self.sd, self.hl, self.su, self.sl, self.hu = self.hamiltonian.get_hs_device(self.kpoint, self.V, block_tridiagonal) + + s_in = [torch.zeros(i.shape).cdouble() for i in self.hd] # for i, e in tqdm(enumerate(ee), desc="Compute green functions: "): @@ -191,6 +202,8 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr seL = self.lead_L.se seR = self.lead_R.se + # seinL = -i \Sigma_L^< = \Gamma_L f_L + # Fluctuation-Dissipation theorem seinL = 1j*(seL-seL.conj().T) * self.lead_L.fermi_dirac(energy+self.mu).reshape(-1) seinR = 1j*(seR-seR.conj().T) * self.lead_R.fermi_dirac(energy+self.mu).reshape(-1) s01, s02 = s_in[0].shape @@ -205,8 +218,8 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr s_in[0][:idx0,:idy0] = s_in[0][:idx0,:idy0] + seinL[:idx0,:idy0] s_in[-1][-idx1:,-idy1:] = s_in[-1][-idx1:,-idy1:] + seinR[-idx1:,-idy1:] - ans = recursive_gf(energy, hl=[], hd=self.hd, hu=[], - sd=self.sd, su=[], sl=[], + ans = recursive_gf(energy, hl=self.hl, hd=self.hd, hu=self.hu, + sd=self.sd, su=self.su, sl=self.sl, left_se=seL, right_se=seR, seP=None, s_in=s_in, s_out=None, eta=eta_device, chemiPot=self.mu) s_in[0][:idx0,:idy0] = s_in[0][:idx0,:idy0] - seinL[:idx0,:idy0] diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 3e6e349c..20de3af1 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -90,12 +90,6 @@ def __init__(self, else: raise ValueError('structure must be ase.Atoms or str') - # sort the atoms lexicographically - if block_tridiagonal: - self.structase.positions = self.structase.positions[sort_lexico(self.structase.positions)] - log.info(msg="The structure is sorted lexicographically in this version!") - - self.unit = unit self.stru_options = stru_options self.pbc_negf = pbc_negf @@ -129,6 +123,12 @@ def __init__(self, if kk.startswith("lead"): self.lead_ids[kk] = [int(x) for x in self.stru_options.get(kk)["id"].split("-")] + # sort the atoms in device region lexicographically + if block_tridiagonal: + self.structase.positions[self.device_id[0]:self.device_id[1]] =\ + self.structase.positions[self.device_id[0]:self.device_id[1]][sort_lexico(self.structase.positions[self.device_id[0]:self.device_id[1]])] + log.info(msg="The structure is sorted lexicographically in this version!") + if self.unit == "Hartree": self.h_factor = 13.605662285137 * 2 elif self.unit == "eV": @@ -205,21 +205,11 @@ def initialize(self, kpoints, block_tridiagnal=False): HD, SD = HK[:,d_start:d_end, d_start:d_end], S[:, d_start:d_end, d_start:d_end] Hall, Sall = HK, S - if not block_tridiagnal: - HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) - HS_device.update({"Hall":Hall.cdouble()*self.h_factor, "Sall":Sall.cdouble()}) - else: - hd, hu, hl, sd, su, sl = self.get_block_tridiagonal(HD*self.h_factor, SD) - HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl}) - - torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) - - structure_device = self.structase[device_id[0]:device_id[1]] structure_device.pbc = self.pbc_negf # structure_device = self.apiH.structure.projected_struct[self.device_id[0]:self.device_id[1]] - structure_leads = {} + structure_leads = {};coupling_width = {} for kk in self.stru_options: if kk.startswith("lead"): HS_leads = {} @@ -237,7 +227,11 @@ def initialize(self, kpoints, block_tridiagnal=False): # lead hamiltonian in the first principal layer(the layer close to the device) HL, SL = HK[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # device and lead's hopping - HDL, SDL = HK[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] + HDL, SDL = HK[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] + nonzero_indice = torch.nonzero(HDL) + coupling_width[kk] = max(torch.max(nonzero_indice[:,1]).item()-torch.min(nonzero_indice[:,1]).item() +1,\ + torch.max(nonzero_indice[:,2]).item()-torch.min(nonzero_indice[:,2]).item() +1) + log.info(msg="The coupling width of {} is {}.".format(kk,coupling_width[kk])) cell = np.array(stru_lead.cell)[:2] @@ -282,10 +276,12 @@ def initialize(self, kpoints, block_tridiagnal=False): nL = int(HK_lead.shape[1] / 2) HLL, SLL = HK_lead[:, :nL, nL:], S_lead[:, :nL, nL:] # H_{L_first2L_second} hL, sL = HK_lead[:,:nL,:nL], S[:,:nL,:nL] # lead hamiltonian in one principal layer - err_l = (HK_lead[:, :nL, :nL] - HL).abs().max() - if err_l >= 1e-4: + err_l = (hL - HL).abs().max() + + if err_l >= 1e-2: # check the lead hamiltonian get from device and lead calculation matches each other # a standard check to see the lead environment is bulk-like or not + print('err_l',err_l) log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") raise RuntimeError elif 1e-7 <= err_l <= 1e-4: @@ -301,43 +297,75 @@ def initialize(self, kpoints, block_tridiagnal=False): }) torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) + + + if not block_tridiagnal: + # change HD format to ( k_index,block_index=0, orb, orb) + HD = torch.unsqueeze(HD,dim=1) + SD = torch.unsqueeze(SD,dim=1) + HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) + HS_device.update({"Hall":Hall.cdouble()*self.h_factor, "Sall":Sall.cdouble()}) + else: + leftmost_size = coupling_width['lead_L'] + rightmost_size = coupling_width['lead_R'] + hd, hu, hl, sd, su, sl = self.get_block_tridiagonal(HD*self.h_factor,SD.cdouble(),self.structase,\ + leftmost_size,rightmost_size) + HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl}) + + torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) + torch.set_default_dtype(torch.float32) return structure_device, structure_leads - def get_block_tridiagonal(self,HK,SK): + def get_block_tridiagonal(self,HK,SK,structase:ase.Atoms,leftmost_size:int,rightmost_size:int): + + # return hd in format: (k_index,block_index, orb, orb) hd,hu,hl,sd,su,sl = [],[],[],[],[],[] - leftmost_orb_num = 1 - rightmost_orb_num = 1 - subblocks = split_into_subblocks(HK[:],leftmost_orb_num,rightmost_orb_num) + if leftmost_size is None: + leftmost_atoms_index = np.where(structase.positions[:,2]==min(structase.positions[:,2]))[0] + leftmost_size = sum([self.h2k.atom_norbs[leftmost_atoms_index[i]] for i in range(len(leftmost_atoms_index))]) + if rightmost_size is None: + rightmost_atoms_index = np.where(structase.positions[:,2]==max(structase.positions[:,2]))[0] + rightmost_size = sum([self.h2k.atom_norbs[rightmost_atoms_index[i]] for i in range(len(rightmost_atoms_index))]) + + subblocks = split_into_subblocks_optimized(HK[0],leftmost_size,rightmost_size) + if subblocks[0] < leftmost_size or subblocks[-1] < rightmost_size: + subblocks = split_into_subblocks(HK[0],leftmost_size,rightmost_size) + log.info(msg="The optimized block tridiagonalization is not successful, \ + the original block tridiagonalization is used.") subblocks = [0]+subblocks - edge1,edge2 = compute_edge(HK[:]) - - for id in range(len(subblocks)-1): - # if id== 0: - # iu = id+1; il = None - # elif id == len(subblocks)-1: - # iu = None; il = id-1 - # else: - # iu = id+1; il = id-1 - if id < len(subblocks)-2: - iu = id+1 - hd.append(HK[:,subblocks[id]:subblocks[id+1],subblocks[id]:subblocks[id+1]]) - sd.append(SK[:,subblocks[id]:subblocks[id+1],subblocks[id]:subblocks[id+1]]) - hu.append(HK[:,subblocks[id]:subblocks[id+1],subblocks[id+1]:subblocks[id+2]]) - su.append(SK[:,subblocks[id]:subblocks[id+1],subblocks[id+1]:subblocks[id+2]]) - hl.append(HK[:,subblocks[id+1]:subblocks[id+2],subblocks[id]:subblocks[id+1]]) - sl.append(SK[:,subblocks[id+1]:subblocks[id+2],subblocks[id]:subblocks[id+1]]) - - - - - + + for ik in range(HK.shape[0]): + hd_k,hu_k,hl_k,sd_k,su_k,sl_k = [],[],[],[],[],[] + counted_block = 0 + for id in range(len(subblocks)-1): + counted_block+=subblocks[id] + d_slice = slice(counted_block,counted_block+subblocks[id+1]) + hd_k.append(HK[ik,d_slice,d_slice]) + sd_k.append(SK[ik,d_slice,d_slice]) + if id < len(subblocks)-2: + u_slice = slice(counted_block+subblocks[id+1],counted_block+subblocks[id+1]+subblocks[id+2]) + hu_k.append(HK[ik,d_slice,u_slice]) + su_k.append(SK[ik,d_slice,u_slice]) + if id > 0: + l_slice = slice(counted_block-subblocks[id],counted_block) + hl_k.append(HK[ik,d_slice,l_slice]) + sl_k.append(SK[ik,d_slice,l_slice]) + hd.append(hd_k);hu.append(hu_k);hl.append(hl_k) + sd.append(sd_k);su.append(su_k);sl.append(sl_k) - + + num_diag = sum([i**2 for i in subblocks]) + num_updiag = sum([subblocks[i]*subblocks[i+1] for i in range(len(subblocks)-1)]) + num_lowdiag = num_updiag + num_total = num_diag+num_updiag+num_lowdiag + log.info(msg="The Hamiltonian is block tridiagonalized into {} subblocks.".format(len(hd[0]))) + log.info(msg=" the number of elements in subblocks: {}".format(num_total)) + log.info(msg=" occupation of subblocks: {} %".format(num_total/(HK[0].shape[0]**2)*100)) return hd, hu, hl, sd, su, sl @@ -361,28 +389,43 @@ def get_hs_device(self, kpoint, V, block_tridiagonal=False): f = torch.load(os.path.join(self.results_path, "HS_device.pth")) kpoints = f["kpoints"] - ix = None + ik = None for i, k in enumerate(kpoints): if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: - ix = i + ik = i break - assert ix is not None + assert ik is not None - if not block_tridiagonal: - HD, SD = f["HD"][ix], f["SD"][ix] - else: - hd, sd, hl, su, sl, hu = f["hd"][ix], f["sd"][ix], f["hl"][ix], f["su"][ix], f["sl"][ix], f["hu"][ix] + if block_tridiagonal: - return hd, sd, hl, su, sl, hu - else: - # print('HD shape:', HD.shape) - # print('SD shape:', SD.shape) - # print('V shape:', V.shape) - log.info(msg='Device Hamiltonian shape: {0}x{0}'.format(HD.shape[0], HD.shape[1])) + # hd format: ( k_index,block_index, orb, orb) + hd_k, sd_k, hl_k, su_k, sl_k, hu_k = f["hd"][ik], f["sd"][ik], f["hl"][ik], f["su"][ik], f["sl"][ik], f["hu"][ik] + # hd, sd, hl, su, sl, hu = torch.nested.nested_tensor(f["hd"][ik]), torch.nested.nested_tensor(f["sd"][ik]),\ + # torch.nested.nested_tensor(f["hl"][ik]), torch.nested.nested_tensor(f["su"][ik]), \ + # torch.nested.nested_tensor(f["sl"][ik]), torch.nested.nested_tensor(f["hu"][ik]) + if V.shape == torch.Size([]): + allorb = sum([hd_k[i].shape[0] for i in range(len(hd_k))]) + V = V.repeat(allorb).unsqueeze(0) + V = V.cdouble() + counted = 0 + for i in range(len(hd_k)): # TODO: this part may have probelms when V!=0 + l_slice = slice(counted, counted+hd_k[i].shape[0]) + # hd_k[i] = hd_k[i] - V[l_slice]*sd_k[i] + hd_k[i] = hd_k[i] - V[:,l_slice]@sd_k[i] + if i 0: + # hl_k[i-1] = hl_k[i-1] - V[l_slice]*sl_k[i-1] + hl_k[i-1] = hl_k[i-1] - V[:,l_slice]@sl_k[i-1] + counted += hd_k[i].shape[0] - return [HD - V*SD], [SD], [], [], [], [] + return hd_k , sd_k, hl_k , su_k, sl_k, hu_k + else: + HD_k, SD_k = f["HD"][ik], f["SD"][ik] + return HD_k - V*SD_k, SD_k, [], [], [], [] def get_hs_lead(self, kpoint, tab, v): """get the lead Hamiltonian and overlap matrix at a specific kpoint @@ -404,16 +447,16 @@ def get_hs_lead(self, kpoint, tab, v): f = torch.load(os.path.join(self.results_path, "HS_{0}.pth".format(tab))) kpoints = f["kpoints"] - ix = None + ik = None for i, k in enumerate(kpoints): if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: - ix = i + ik = i break - assert ix is not None + assert ik is not None - hL, hLL, hDL, sL, sLL, sDL = f["HL"][ix], f["HLL"][ix], f["HDL"][ix], \ - f["SL"][ix], f["SLL"][ix], f["SDL"][ix] + hL, hLL, hDL, sL, sLL, sDL = f["HL"][ik], f["HLL"][ik], f["HDL"][ik], \ + f["SL"][ik], f["SLL"][ik], f["SDL"][ik] return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL diff --git a/dptb/negf/recursive_green_cal.py b/dptb/negf/recursive_green_cal.py index 6fe2b8bc..e11f8593 100644 --- a/dptb/negf/recursive_green_cal.py +++ b/dptb/negf/recursive_green_cal.py @@ -92,18 +92,21 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list, sd, su, sl, s_i g_trans = gr_left[q] @ mat_u_list[q] @ g_trans # ------------------------------------------------------------------- - # ------ compute the electron correlation function if needed -------- + # ------ compute the electron correlation function ( Lesser Green Function ) if needed -------- # ------------------------------------------------------------------- if isinstance(s_in, list): - + gin_left = [None for _ in range(num_of_matrices)] + # Keldysh formula: G^< = G^r * Sigma^< * G^a ====> (-i * G^<) = G^r * (-i * Sigma^<) * G^a gin_left[0] = gr_left[0] @ s_in[0] @ gr_left[0].conj().T for q in range(num_of_matrices - 1): - sla2 = mat_l_list[q] @ gin_left[q] @ mat_u_list[q].conj().T + # sla2: coupling with the left layer + # s_in[q + 1]: coupling directly with the q+1 layer from lead + sla2 = mat_l_list[q] @ gin_left[q] @ mat_u_list[q] prom = s_in[q + 1] + sla2 - gin_left[q + 1] = gr_left[q + 1] @ prom @ gr_left[q + 1].conj().T + gin_left[q + 1] = gr_left[q + 1] @ prom @ gr_left[q + 1].conj().T # --------------------------------------------------------------- @@ -113,12 +116,12 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list, sd, su, sl, s_i for q in range(num_of_matrices - 2, -1, -1): # Recursive algorithm gnl[q] = grd[q + 1] @ mat_l_list[q] @ gin_left[q] + \ - gnd[q + 1] @ mat_l_list[q].conj().T @ gr_left[q].conj().T + gnd[q + 1] @ mat_l_list[q] @ gr_left[q].conj().T # (B10) gnd[q] = gin_left[q] + \ - gr_left[q] @ mat_u_list[q] @ gnd[q + 1] @ mat_l_list[q].conj().T @ \ + gr_left[q] @ mat_u_list[q] @ gnd[q + 1] @ mat_l_list[q] @ \ gr_left[q].conj().T + \ - ((gin_left[q] @ mat_u_list[q].conj().T @ grl[q].conj().T) + (gru[q] @ - mat_l_list[q] @ gin_left[q])) + ((gin_left[q] @ mat_u_list[q] @ gru[q].conj().T) + (gru[q] @ + mat_l_list[q] @ gin_left[q])) # (B11) gnu[q] = gnl[q].conj().T @@ -128,12 +131,12 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list, sd, su, sl, s_i if isinstance(s_out, list): gip_left = [None for _ in range(num_of_matrices)] - gip_left[0] = gr_left[0] @ s_out[0] @ gr_left[0].conj().T + gip_left[0] = gr_left[0] @ s_out[0] @ gr_left[0].conj() for q in range(num_of_matrices - 1): - sla2 = mat_l_list[q] @ gip_left[q] @ mat_u_list[q].conj().T + sla2 = mat_l_list[q] @ gip_left[q] @ mat_u_list[q].conj() prom = s_out[q + 1] + sla2 - gip_left[q + 1] = gr_left[q + 1] @ prom @ gr_left[q + 1].conj().T + gip_left[q + 1] = gr_left[q + 1] @ prom @ gr_left[q + 1].conj() # --------------------------------------------------------------- @@ -143,11 +146,11 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list, sd, su, sl, s_i for q in range(num_of_matrices - 2, -1, -1): # Recursive algorithm gpl[q] = grd[q + 1] @ mat_l_list[q] @ gip_left[q] + \ - gpd[q + 1] @ mat_l_list[q].conj().T @ gr_left[q].conj().T + gpd[q + 1] @ mat_l_list[q].conj() @ gr_left[q].conj() gpd[q] = gip_left[q] + \ - gr_left[q] @ mat_u_list[q] @ gpd[q + 1] @ mat_l_list[q].conj().T @ \ - gr_left[q].conj().T + \ - ((gip_left[q]@ mat_u_list[q].conj().T @ grl[q].conj().T) + (gru[q] @ + gr_left[q] @ mat_u_list[q] @ gpd[q + 1] @ mat_l_list[q].conj() @ \ + gr_left[q].conj()+ \ + ((gip_left[q]@ mat_u_list[q].conj() @ grl[q].conj()) + (gru[q] @ mat_l_list[q] @ gip_left[q])) gpu[0] = gpl[0].conj().T @@ -211,7 +214,7 @@ def recursive_gf(energy, hl, hd, hu, sd, su, sl, left_se, right_se, seP=None, ch List of upper-diagonal blocks mat_l_list : list of numpy.ndarray (dtype=numpy.float) List of lower-diagonal blocks - s_in : Sigma_in contains self-energy about electron phonon scattering + s_in : Coupling Matrix Gamma from leads to the device (Default value = 0) s_out : (Default value = 0) @@ -251,6 +254,11 @@ def recursive_gf(energy, hl, hd, hu, sd, su, sl, left_se, right_se, seP=None, ch shift_energy = energy + chemiPot # shift_energy = torch.scalar_tensor(shift_energy, dtype=torch.complex128) + # if isinstance(hd,torch.Tensor): # hd, hl, hu are torch.nested.nested_tensor + # temp_mat_d_list = [hd[i] * 1. for i in range(hd.size(0))] + # temp_mat_l_list = [hl[i] * 1. for i in range(hl.size(0))] + # temp_mat_u_list = [hu[i] * 1. for i in range(hu.size(0))] + # else: temp_mat_d_list = [hd[i] * 1. for i in range(len(hd))] temp_mat_l_list = [hl[i] * 1. for i in range(len(hl))] temp_mat_u_list = [hu[i] * 1. for i in range(len(hu))] diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index ce41911c..3310a441 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -55,6 +55,7 @@ def __init__(self, # self.jdata = jdata self.cdtype = torch.complex128 self.torch_device = torch_device + self.overlap = overlap # get the parameters self.ele_T = ele_T @@ -480,14 +481,14 @@ def negf_compute(self,scf_require=False,Vbias=None): getattr(self.deviceprop, ll).self_energy( energy=e, kpoint=k, - eta_lead=self.jdata["eta_lead"], - method=self.jdata["sgf_solver"] + eta_lead=self.eta_lead, + method=self.sgf_solver ) self.deviceprop.cal_green_function( energy=e, kpoint=k, - eta_device=self.jdata["eta_device"], + eta_device=self.eta_device, block_tridiagonal=self.block_tridiagonal ) diff --git a/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json b/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json index 2faea8da..5ce5d1c2 100644 --- a/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json +++ b/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json @@ -3,7 +3,7 @@ { "task": "negf", "scf": false, - "block_tridiagonal": false, + "block_tridiagonal": true, "ele_T": 500, "unit": "eV", "scf_options":{ From abff5e57737305db231f511a6b7764e31a3a4afc Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 22 Apr 2024 13:39:05 +0800 Subject: [PATCH 125/209] fix gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 10428c4f..bbfe44d1 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ dptb/tests/data/test_all/checkpoint/best_nnsk_b4.000_c4.000_w0.300.json dptb/tests/data/test_all/fancy_ones/checkpoint/best_nnsk_b4.000_c4.000_w0.300.json dptb/tests/data/test_negf/test_negf_run/out_negf/run_config.json dptb/data/try_test.ipynb +dptb/negf/check.ipynb dptb/tests/data/test_negf/show.ipynb dptb/tests/data/test_tbtrans/show.ipynb run_config.json @@ -174,3 +175,4 @@ _version.py /.idea/ + From 9bbe6d76f1ae343e6bfa1f44648a1040c4e1b3c2 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 22 Apr 2024 21:49:56 +0800 Subject: [PATCH 126/209] fix pyinstrument import probelm in pytest --- dptb/entrypoints/run.py | 19 +++++++++++++------ dptb/postprocess/NEGF.py | 3 ++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/dptb/entrypoints/run.py b/dptb/entrypoints/run.py index f9590121..ec871fa9 100644 --- a/dptb/entrypoints/run.py +++ b/dptb/entrypoints/run.py @@ -11,7 +11,7 @@ from dptb.utils.tools import j_must_have from dptb.postprocess.NEGF import NEGF from dptb.postprocess.tbtrans_init import TBTransInputSet,sisl_installed -from pyinstrument import Profiler + from dptb.postprocess.write_ham import write_ham import torch import h5py @@ -91,8 +91,14 @@ def run( elif task=='negf': - profiler = Profiler() - profiler.start() + try: + from pyinstrument import Profiler + profiler = Profiler() + profiler.start() + profiler_start = True + except ImportWarning: + log.warning(msg="pyinstrument is not installed, no profiling will be done.") + profiler_start = False negf = NEGF( model=model, AtomicData_options=jdata['AtomicData_options'], @@ -103,9 +109,10 @@ def run( negf.compute() log.info(msg='negf calculation successfully completed.') - profiler.stop() - with open(results_path+'/profile_report.html', 'w') as report_file: - report_file.write(profiler.output_html()) + if profiler_start: + profiler.stop() + with open(results_path+'/profile_report.html', 'w') as report_file: + report_file.write(profiler.output_html()) elif task == 'tbtrans_negf': if not(sisl_installed): diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 3310a441..60585194 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -21,7 +21,7 @@ import logging from dptb.negf.poisson_init import Grid,Interface3D,Gate,Dielectric from typing import Optional, Union -from pyinstrument import Profiler +# from pyinstrument import Profiler from dptb.data import AtomicData, AtomicDataDict log = logging.getLogger(__name__) @@ -105,6 +105,7 @@ def __init__(self, stru_options=self.stru_options, unit = self.unit, results_path=self.results_path, + overlap = self.overlap, torch_device = self.torch_device) with torch.no_grad(): struct_device, struct_leads = self.negf_hamiltonian.initialize(kpoints=self.kpoints, From 031c44175767fda7020000686eb68d88b20d2d64 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 23 Apr 2024 09:52:29 +0800 Subject: [PATCH 127/209] fix pyinstrument import in pytest --- dptb/entrypoints/run.py | 27 +++++++++++++++------------ dptb/negf/negf_hamiltonian_init.py | 6 +++++- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/dptb/entrypoints/run.py b/dptb/entrypoints/run.py index ec871fa9..03ff23f3 100644 --- a/dptb/entrypoints/run.py +++ b/dptb/entrypoints/run.py @@ -15,6 +15,7 @@ from dptb.postprocess.write_ham import write_ham import torch import h5py +import pytest log = logging.getLogger(__name__) @@ -90,15 +91,16 @@ def run( log.info(msg='band calculation successfully completed.') elif task=='negf': + + # try: + # from pyinstrument import Profiler + # except ImportWarning: + # log.warning(msg="pyinstrument is not installed, no profiling will be done.") + # Profiler = None + # if Profiler is not None: + # profiler = Profiler() + # profiler.start() - try: - from pyinstrument import Profiler - profiler = Profiler() - profiler.start() - profiler_start = True - except ImportWarning: - log.warning(msg="pyinstrument is not installed, no profiling will be done.") - profiler_start = False negf = NEGF( model=model, AtomicData_options=jdata['AtomicData_options'], @@ -109,10 +111,11 @@ def run( negf.compute() log.info(msg='negf calculation successfully completed.') - if profiler_start: - profiler.stop() - with open(results_path+'/profile_report.html', 'w') as report_file: - report_file.write(profiler.output_html()) + + # if Profiler is not None: + # profiler.stop() + # with open(results_path+'/profile_report.html', 'w') as report_file: + # report_file.write(profiler.output_html()) elif task == 'tbtrans_negf': if not(sisl_installed): diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 20de3af1..e767044b 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -190,6 +190,8 @@ def initialize(self, kpoints, block_tridiagnal=False): self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] self.alldata[AtomicDataDict.EDGE_INDEX_KEY] = self.alldata[AtomicDataDict.EDGE_INDEX_KEY][:,mask] self.alldata[AtomicDataDict.EDGE_FEATURES_KEY] = self.alldata[AtomicDataDict.EDGE_FEATURES_KEY][mask] + if self.overlap: + self.alldata[AtomicDataDict.EDGE_OVERLAP_KEY] = self.alldata[AtomicDataDict.EDGE_OVERLAP_KEY][mask] self.alldata = self.h2k(self.alldata) HK = self.alldata[AtomicDataDict.HAMILTONIAN_KEY] @@ -263,6 +265,8 @@ def initialize(self, kpoints, block_tridiagnal=False): lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] lead_data[AtomicDataDict.EDGE_INDEX_KEY] = lead_data[AtomicDataDict.EDGE_INDEX_KEY][:,mask] lead_data[AtomicDataDict.EDGE_FEATURES_KEY] = lead_data[AtomicDataDict.EDGE_FEATURES_KEY][mask] + if self.overlap: + lead_data[AtomicDataDict.EDGE_OVERLAP_KEY] = lead_data[AtomicDataDict.EDGE_OVERLAP_KEY][mask] lead_data = self.h2k(lead_data) HK_lead = lead_data[AtomicDataDict.HAMILTONIAN_KEY] @@ -278,7 +282,7 @@ def initialize(self, kpoints, block_tridiagnal=False): hL, sL = HK_lead[:,:nL,:nL], S[:,:nL,:nL] # lead hamiltonian in one principal layer err_l = (hL - HL).abs().max() - if err_l >= 1e-2: + if err_l >= 1e-4: # check the lead hamiltonian get from device and lead calculation matches each other # a standard check to see the lead environment is bulk-like or not print('err_l',err_l) From cdfbba0d0f2f3ea96fef77b35b29382bfe285163 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 23 Apr 2024 11:27:35 +0800 Subject: [PATCH 128/209] write code in negf_hamiltonian_init in compact form --- dptb/entrypoints/run.py | 2 +- dptb/negf/negf_hamiltonian_init.py | 114 +++++++++++++++++------------ 2 files changed, 67 insertions(+), 49 deletions(-) diff --git a/dptb/entrypoints/run.py b/dptb/entrypoints/run.py index 03ff23f3..fe7c5b8c 100644 --- a/dptb/entrypoints/run.py +++ b/dptb/entrypoints/run.py @@ -15,7 +15,7 @@ from dptb.postprocess.write_ham import write_ham import torch import h5py -import pytest + log = logging.getLogger(__name__) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index e767044b..ef4744ce 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -184,28 +184,29 @@ def initialize(self, kpoints, block_tridiagnal=False): self.alldata[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_device["kpoints"], dtype=self.model.dtype, device=self.torch_device) self.alldata = self.model(self.alldata) # remove the edges corresponding to z-direction pbc for HR2HK - for ip,p in enumerate(self.pbc_negf): - if not p: - mask = self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip] == 0 - self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] - self.alldata[AtomicDataDict.EDGE_INDEX_KEY] = self.alldata[AtomicDataDict.EDGE_INDEX_KEY][:,mask] - self.alldata[AtomicDataDict.EDGE_FEATURES_KEY] = self.alldata[AtomicDataDict.EDGE_FEATURES_KEY][mask] - if self.overlap: - self.alldata[AtomicDataDict.EDGE_OVERLAP_KEY] = self.alldata[AtomicDataDict.EDGE_OVERLAP_KEY][mask] + # for ip,p in enumerate(self.pbc_negf): + # if not p: + # mask = self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip] == 0 + # self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] + # self.alldata[AtomicDataDict.EDGE_INDEX_KEY] = self.alldata[AtomicDataDict.EDGE_INDEX_KEY][:,mask] + # self.alldata[AtomicDataDict.EDGE_FEATURES_KEY] = self.alldata[AtomicDataDict.EDGE_FEATURES_KEY][mask] + # if self.overlap: + # self.alldata[AtomicDataDict.EDGE_OVERLAP_KEY] = self.alldata[AtomicDataDict.EDGE_OVERLAP_KEY][mask] + self.remove_bonds_nonpbc(self.alldata,self.pbc_negf) self.alldata = self.h2k(self.alldata) HK = self.alldata[AtomicDataDict.HAMILTONIAN_KEY] if self.overlap: self.alldata = self.s2k(self.alldata) - S = self.alldata[AtomicDataDict.OVERLAP_KEY] + SK = self.alldata[AtomicDataDict.OVERLAP_KEY] else: - S = torch.eye(HK.shape[1], dtype=self.model.dtype, device=self.torch_device).unsqueeze(0).repeat(HK.shape[0], 1, 1) + SK = torch.eye(HK.shape[1], dtype=self.model.dtype, device=self.torch_device).unsqueeze(0).repeat(HK.shape[0], 1, 1) # H, S = self.apiH.get_HK(kpoints=kpoints) d_start = int(np.sum(self.h2k.atom_norbs[:device_id[0]])) d_end = int(np.sum(self.h2k.atom_norbs)-np.sum(self.h2k.atom_norbs[device_id[1]:])) - HD, SD = HK[:,d_start:d_end, d_start:d_end], S[:, d_start:d_end, d_start:d_end] - Hall, Sall = HK, S + HD, SD = HK[:,d_start:d_end, d_start:d_end], SK[:, d_start:d_end, d_start:d_end] + Hall, Sall = HK, SK structure_device = self.structase[device_id[0]:device_id[1]] structure_device.pbc = self.pbc_negf @@ -216,42 +217,28 @@ def initialize(self, kpoints, block_tridiagnal=False): if kk.startswith("lead"): HS_leads = {} HS_leads["kpoints"] = kpoints - stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] + # update lead id n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]].sum() n_proj_atom_lead = np.array([1]*len(self.structase))[self.lead_ids[kk][0]:self.lead_ids[kk][1]].sum() lead_id = [0,0] lead_id[0] = n_proj_atom_pre lead_id[1] = n_proj_atom_pre + n_proj_atom_lead + l_start = int(np.sum(self.h2k.atom_norbs[:lead_id[0]])) l_end = int(l_start + np.sum(self.h2k.atom_norbs[lead_id[0]:lead_id[1]]) / 2) # lead hamiltonian in the first principal layer(the layer close to the device) - HL, SL = HK[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] + HL, SL = HK[:,l_start:l_end, l_start:l_end], SK[:, l_start:l_end, l_start:l_end] # device and lead's hopping - HDL, SDL = HK[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] + HDL, SDL = HK[:,d_start:d_end, l_start:l_end], SK[:,d_start:d_end, l_start:l_end] nonzero_indice = torch.nonzero(HDL) coupling_width[kk] = max(torch.max(nonzero_indice[:,1]).item()-torch.min(nonzero_indice[:,1]).item() +1,\ torch.max(nonzero_indice[:,2]).item()-torch.min(nonzero_indice[:,2]).item() +1) log.info(msg="The coupling width of {} is {}.".format(kk,coupling_width[kk])) - - cell = np.array(stru_lead.cell)[:2] - natom = lead_id[1] - lead_id[0] - R_vec = stru_lead[int(natom/2):].positions - stru_lead[:int(natom/2)].positions - assert np.abs(R_vec[0] - R_vec[-1]).sum() < 1e-5 - R_vec = R_vec.mean(axis=0) * 2 - cell = np.concatenate([cell, R_vec.reshape(1,-1)]) - - # get lead structure in ase format - pbc_lead = self.pbc_negf.copy() - pbc_lead[2] = True - stru_lead = Atoms(str(stru_lead.symbols), - positions=stru_lead.positions, - cell=cell, - pbc=pbc_lead) - stru_lead.set_chemical_symbols(stru_lead.get_chemical_symbols()) - structure_leads[kk] = stru_lead + structure_leads[kk] = self.get_lead_structure(kk,n_proj_atom_lead) + # get lead_data lead_data = AtomicData.from_ase(structure_leads[kk], **self.AtomicData_options) lead_data = AtomicData.to_AtomicDataDict(lead_data.to(self.torch_device)) @@ -259,15 +246,15 @@ def initialize(self, kpoints, block_tridiagnal=False): lead_data[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_leads["kpoints"], dtype=self.model.dtype, device=self.torch_device) lead_data = self.model(lead_data) # remove the edges corresponding to z-direction pbc for HR2HK - for ip,p in enumerate(self.pbc_negf): - if not p: - mask = abs(lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip])<1e-7 - lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] - lead_data[AtomicDataDict.EDGE_INDEX_KEY] = lead_data[AtomicDataDict.EDGE_INDEX_KEY][:,mask] - lead_data[AtomicDataDict.EDGE_FEATURES_KEY] = lead_data[AtomicDataDict.EDGE_FEATURES_KEY][mask] - if self.overlap: - lead_data[AtomicDataDict.EDGE_OVERLAP_KEY] = lead_data[AtomicDataDict.EDGE_OVERLAP_KEY][mask] - + # for ip,p in enumerate(self.pbc_negf): + # if not p: + # mask = abs(lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip])<1e-7 + # lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] + # lead_data[AtomicDataDict.EDGE_INDEX_KEY] = lead_data[AtomicDataDict.EDGE_INDEX_KEY][:,mask] + # lead_data[AtomicDataDict.EDGE_FEATURES_KEY] = lead_data[AtomicDataDict.EDGE_FEATURES_KEY][mask] + # if self.overlap: + # lead_data[AtomicDataDict.EDGE_OVERLAP_KEY] = lead_data[AtomicDataDict.EDGE_OVERLAP_KEY][mask] + self.remove_bonds_nonpbc(lead_data,self.pbc_negf) lead_data = self.h2k(lead_data) HK_lead = lead_data[AtomicDataDict.HAMILTONIAN_KEY] if self.overlap: @@ -279,17 +266,19 @@ def initialize(self, kpoints, block_tridiagnal=False): nL = int(HK_lead.shape[1] / 2) HLL, SLL = HK_lead[:, :nL, nL:], S_lead[:, :nL, nL:] # H_{L_first2L_second} - hL, sL = HK_lead[:,:nL,:nL], S[:,:nL,:nL] # lead hamiltonian in one principal layer - err_l = (hL - HL).abs().max() + hL, sL = HK_lead[:,:nL,:nL], SK[:,:nL,:nL] # lead hamiltonian in one principal layer + err_l_HK = (hL - HL).abs().max() + err_l_SK = (sL - SL).abs().max() - if err_l >= 1e-4: + if max(err_l_HK,err_l_SK) >= 1e-2: # check the lead hamiltonian get from device and lead calculation matches each other # a standard check to see the lead environment is bulk-like or not - print('err_l',err_l) + log.info(msg="The lead's hamiltonian or overlap attained from device and lead calculation does not match. \ + The error is {:.7f}.".format(max(err_l_HK,err_l_SK))) log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") raise RuntimeError - elif 1e-7 <= err_l <= 1e-4: - log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(err_l)) + elif 1e-7 <= max(err_l_HK,err_l_SK) <= 1e-4: + log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(max(err_l_HK,err_l_SK))) HS_leads.update({ "HL":HL.cdouble()*self.h_factor, @@ -320,7 +309,36 @@ def initialize(self, kpoints, block_tridiagnal=False): torch.set_default_dtype(torch.float32) return structure_device, structure_leads - + + def remove_bonds_nonpbc(self,data,pbc): + + for ip,p in enumerate(pbc): + if not p: + mask = abs(data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip])<1e-7 + data[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] + data[AtomicDataDict.EDGE_INDEX_KEY] = data[AtomicDataDict.EDGE_INDEX_KEY][:,mask] + data[AtomicDataDict.EDGE_FEATURES_KEY] = data[AtomicDataDict.EDGE_FEATURES_KEY][mask] + if self.overlap: + data[AtomicDataDict.EDGE_OVERLAP_KEY] = data[AtomicDataDict.EDGE_OVERLAP_KEY][mask] + + def get_lead_structure(self,kk,natom): + stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] + cell = np.array(stru_lead.cell)[:2] + + R_vec = stru_lead[int(natom/2):].positions - stru_lead[:int(natom/2)].positions + assert np.abs(R_vec[0] - R_vec[-1]).sum() < 1e-5 + R_vec = R_vec.mean(axis=0) * 2 + cell = np.concatenate([cell, R_vec.reshape(1,-1)]) + + # get lead structure in ase format + pbc_lead = self.pbc_negf.copy() + pbc_lead[2] = True + stru_lead = Atoms(str(stru_lead.symbols), + positions=stru_lead.positions, + cell=cell, + pbc=pbc_lead) + stru_lead.set_chemical_symbols(stru_lead.get_chemical_symbols()) + return stru_lead def get_block_tridiagonal(self,HK,SK,structase:ase.Atoms,leftmost_size:int,rightmost_size:int): From 7854c93a914d6940b92aab82f38eb72aa336bb3e Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 23 Apr 2024 13:43:04 +0800 Subject: [PATCH 129/209] fix overlap Bool input probelm --- dptb/negf/negf_hamiltonian_init.py | 67 ++++++++++++++++-------------- dptb/postprocess/NEGF.py | 18 +------- 2 files changed, 37 insertions(+), 48 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index ef4744ce..11dfab3d 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -67,7 +67,6 @@ def __init__(self, stru_options:dict, unit: str, results_path:Optional[str]=None, - overlap: bool=False, torch_device: Union[str, torch.device]=torch.device('cpu') ) -> None: @@ -95,7 +94,6 @@ def __init__(self, self.pbc_negf = pbc_negf assert len(self.pbc_negf) == 3 self.results_path = results_path - self.overlap = overlap self.h2k = HR2HK( idp=model.idp, @@ -106,16 +104,16 @@ def __init__(self, device=self.torch_device, ) - if overlap: - self.s2k = HR2HK( - idp=model.idp, - overlap=True, - edge_field=AtomicDataDict.EDGE_OVERLAP_KEY, - node_field=AtomicDataDict.NODE_OVERLAP_KEY, - out_field=AtomicDataDict.OVERLAP_KEY, - dtype=model.dtype, - device=self.torch_device, - ) + # if overlap: + # self.s2k = HR2HK( + # idp=model.idp, + # overlap=True, + # edge_field=AtomicDataDict.EDGE_OVERLAP_KEY, + # node_field=AtomicDataDict.NODE_OVERLAP_KEY, + # out_field=AtomicDataDict.OVERLAP_KEY, + # dtype=model.dtype, + # device=self.torch_device, + # ) self.device_id = [int(x) for x in self.stru_options['device']["id"].split("-")] self.lead_ids = {} @@ -183,19 +181,28 @@ def initialize(self, kpoints, block_tridiagnal=False): self.alldata = self.model.idp(alldata) self.alldata[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_device["kpoints"], dtype=self.model.dtype, device=self.torch_device) self.alldata = self.model(self.alldata) - # remove the edges corresponding to z-direction pbc for HR2HK - # for ip,p in enumerate(self.pbc_negf): - # if not p: - # mask = self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip] == 0 - # self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = self.alldata[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] - # self.alldata[AtomicDataDict.EDGE_INDEX_KEY] = self.alldata[AtomicDataDict.EDGE_INDEX_KEY][:,mask] - # self.alldata[AtomicDataDict.EDGE_FEATURES_KEY] = self.alldata[AtomicDataDict.EDGE_FEATURES_KEY][mask] - # if self.overlap: - # self.alldata[AtomicDataDict.EDGE_OVERLAP_KEY] = self.alldata[AtomicDataDict.EDGE_OVERLAP_KEY][mask] - self.remove_bonds_nonpbc(self.alldata,self.pbc_negf) - + + if self.alldata.get(AtomicDataDict.EDGE_OVERLAP_KEY,None) is not None: + self.overlap = True + self.s2k = HR2HK( + idp=self.model.idp, + overlap=True, + edge_field=AtomicDataDict.EDGE_OVERLAP_KEY, + node_field=AtomicDataDict.NODE_OVERLAP_KEY, + out_field=AtomicDataDict.OVERLAP_KEY, + dtype=self.model.dtype, + device=self.torch_device, + ) + else: + self.overlap = False + + + + self.remove_bonds_nonpbc(self.alldata,self.pbc_negf) self.alldata = self.h2k(self.alldata) HK = self.alldata[AtomicDataDict.HAMILTONIAN_KEY] + + if self.overlap: self.alldata = self.s2k(self.alldata) SK = self.alldata[AtomicDataDict.OVERLAP_KEY] @@ -210,8 +217,7 @@ def initialize(self, kpoints, block_tridiagnal=False): structure_device = self.structase[device_id[0]:device_id[1]] structure_device.pbc = self.pbc_negf - # structure_device = self.apiH.structure.projected_struct[self.device_id[0]:self.device_id[1]] - + structure_leads = {};coupling_width = {} for kk in self.stru_options: if kk.startswith("lead"): @@ -244,7 +250,7 @@ def initialize(self, kpoints, block_tridiagnal=False): lead_data = AtomicData.to_AtomicDataDict(lead_data.to(self.torch_device)) lead_data = self.model.idp(lead_data) lead_data[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_leads["kpoints"], dtype=self.model.dtype, device=self.torch_device) - lead_data = self.model(lead_data) + lead_data = self.model(lead_data) # remove the edges corresponding to z-direction pbc for HR2HK # for ip,p in enumerate(self.pbc_negf): # if not p: @@ -266,11 +272,11 @@ def initialize(self, kpoints, block_tridiagnal=False): nL = int(HK_lead.shape[1] / 2) HLL, SLL = HK_lead[:, :nL, nL:], S_lead[:, :nL, nL:] # H_{L_first2L_second} - hL, sL = HK_lead[:,:nL,:nL], SK[:,:nL,:nL] # lead hamiltonian in one principal layer + hL, sL = HK_lead[:,:nL,:nL], S_lead[:,:nL,:nL] # lead hamiltonian in one principal layer err_l_HK = (hL - HL).abs().max() err_l_SK = (sL - SL).abs().max() - if max(err_l_HK,err_l_SK) >= 1e-2: + if max(err_l_HK,err_l_SK) >= 1e-4: # check the lead hamiltonian get from device and lead calculation matches each other # a standard check to see the lead environment is bulk-like or not log.info(msg="The lead's hamiltonian or overlap attained from device and lead calculation does not match. \ @@ -424,9 +430,7 @@ def get_hs_device(self, kpoint, V, block_tridiagonal=False): if block_tridiagonal: # hd format: ( k_index,block_index, orb, orb) hd_k, sd_k, hl_k, su_k, sl_k, hu_k = f["hd"][ik], f["sd"][ik], f["hl"][ik], f["su"][ik], f["sl"][ik], f["hu"][ik] - # hd, sd, hl, su, sl, hu = torch.nested.nested_tensor(f["hd"][ik]), torch.nested.nested_tensor(f["sd"][ik]),\ - # torch.nested.nested_tensor(f["hl"][ik]), torch.nested.nested_tensor(f["su"][ik]), \ - # torch.nested.nested_tensor(f["sl"][ik]), torch.nested.nested_tensor(f["hu"][ik]) + if V.shape == torch.Size([]): allorb = sum([hd_k[i].shape[0] for i in range(len(hd_k))]) V = V.repeat(allorb).unsqueeze(0) @@ -434,7 +438,6 @@ def get_hs_device(self, kpoint, V, block_tridiagonal=False): counted = 0 for i in range(len(hd_k)): # TODO: this part may have probelms when V!=0 l_slice = slice(counted, counted+hd_k[i].shape[0]) - # hd_k[i] = hd_k[i] - V[l_slice]*sd_k[i] hd_k[i] = hd_k[i] - V[:,l_slice]@sd_k[i] if i Date: Tue, 23 Apr 2024 14:36:56 +0800 Subject: [PATCH 130/209] fix ldos and dos bug for BTD form --- dptb/negf/device_property.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index 5d154f3f..2e5b9aa8 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -337,9 +337,20 @@ def _cal_dos_(self): ''' dos = 0 for jj in range(len(self.grd)): - temp = self.grd[jj] @ self.sd[jj] # taking each diagonal block with all energy e together + if not self.block_tridiagonal: + temp = self.grd[jj] @ self.sd[jj] # taking each diagonal block with all energy e together + else: + # print(self.grl[jj-1].shape) + # print(self.su[jj-1].shape) + # print(self.gru[jj-1].shape) + # print(self.sl[jj-1].shape) + if jj == 0: + temp = self.grd[jj] @ self.sd[jj] + self.gru[jj] @ self.sl[jj] + elif jj == len(self.grd)-1: + temp = self.grd[jj] @ self.sd[jj] + self.grl[jj-1] @ self.su[jj-1] + else: + temp = self.grd[jj] @ self.sd[jj] + self.grl[jj-1] @ self.su[jj-1] + self.gru[jj] @ self.sl[jj] dos -= temp.imag.diag().sum(-1) / pi - return dos * 2 def _cal_ldos_(self): @@ -353,7 +364,15 @@ def _cal_ldos_(self): ldos = [] # sd = self.hamiltonian.get_hs_device(kpoint=self.kpoint, V=self.V, block_tridiagonal=self.block_tridiagonal)[1] for jj in range(len(self.grd)): - temp = self.grd[jj] @ self.sd[jj] # taking each diagonal block with all energy e together + if not self.block_tridiagonal: + temp = self.grd[jj] @ self.sd[jj] # taking each diagonal block with all energy e together + else: + if jj == 0: + temp = self.grd[jj] @ self.sd[jj] + self.gru[jj] @ self.sl[jj] + elif jj == len(self.grd)-1: + temp = self.grd[jj] @ self.sd[jj] + self.grl[jj-1] @ self.su[jj-1] + else: + temp = self.grd[jj] @ self.sd[jj] + self.grl[jj-1] @ self.su[jj-1] + self.gru[jj] @ self.sl[jj] ldos.append(-temp.imag.diag() / pi) # shape(Nd(diagonal elements)) ldos = torch.cat(ldos, dim=0).contiguous() From 4fff7e43f1434cfdedfe29f63e2cb7b859a882ed Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Apr 2024 16:32:43 +0800 Subject: [PATCH 131/209] add list for pbc setting in AtomicData_options --- dptb/utils/argcheck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 8c3eb493..570e4528 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -1379,7 +1379,7 @@ def AtomicData_options_sub(): Argument("r_max", [float, int, dict], optional=False, doc=doc_r_max, default=4.0), Argument("er_max", [float, int, dict], optional=True, doc=doc_er_max, default=None), Argument("oer_max", [float, int, dict], optional=True, doc=doc_oer_max,default=None), - Argument("pbc", bool, optional=False, doc=doc_pbc, default=True), + Argument("pbc", [bool,list], optional=False, doc=doc_pbc, default=True), ] return Argument("AtomicData_options", dict, optional=False, sub_fields=args, sub_variants=[], doc="") From bb89909271945694f3cd126a277a38416c950449 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Apr 2024 16:34:04 +0800 Subject: [PATCH 132/209] fix ldos and dos when length of up and down blocklist equals 0 --- dptb/negf/device_property.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index 2e5b9aa8..460d2ac5 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -337,13 +337,9 @@ def _cal_dos_(self): ''' dos = 0 for jj in range(len(self.grd)): - if not self.block_tridiagonal: + if not self.block_tridiagonal or len(self.gru) == 0: temp = self.grd[jj] @ self.sd[jj] # taking each diagonal block with all energy e together else: - # print(self.grl[jj-1].shape) - # print(self.su[jj-1].shape) - # print(self.gru[jj-1].shape) - # print(self.sl[jj-1].shape) if jj == 0: temp = self.grd[jj] @ self.sd[jj] + self.gru[jj] @ self.sl[jj] elif jj == len(self.grd)-1: @@ -364,7 +360,7 @@ def _cal_ldos_(self): ldos = [] # sd = self.hamiltonian.get_hs_device(kpoint=self.kpoint, V=self.V, block_tridiagonal=self.block_tridiagonal)[1] for jj in range(len(self.grd)): - if not self.block_tridiagonal: + if not self.block_tridiagonal or len(self.gru) == 0: temp = self.grd[jj] @ self.sd[jj] # taking each diagonal block with all energy e together else: if jj == 0: From 2659506f7003337d5083de077b5e5965e6d92bb4 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Apr 2024 16:35:11 +0800 Subject: [PATCH 133/209] remove unnecessary n_proj_atom_pre variables --- dptb/negf/negf_hamiltonian_init.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 11dfab3d..4b3ad041 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -160,18 +160,13 @@ def initialize(self, kpoints, block_tridiagnal=False): HS_device = {} HS_device["kpoints"] = kpoints - # self.apiH.update_struct(self.structase, mode="device", stru_options=j_must_have(self.stru_options, "device"), pbc=self.stru_options["pbc"]) - # change parameters to match the structure projection - n_proj_atom_pre = np.array([1]*len(self.structase))[:self.device_id[0]].sum() - n_proj_atom_device = np.array([1]*len(self.structase))[self.device_id[0]:self.device_id[1]].sum() - device_id = [0,0] - device_id[0] = n_proj_atom_pre - device_id[1] = n_proj_atom_pre + n_proj_atom_device - self.device_id = device_id - # projatoms = self.apiH.structure.projatoms - # self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] - # self.apiH.get_HR() + # n_proj_atom_pre = np.array([1]*len(self.structase))[:self.device_id[0]].sum() + # n_proj_atom_device = np.array([1]*len(self.structase))[self.device_id[0]:self.device_id[1]].sum() + # device_id = [0,0] + # device_id[0] = n_proj_atom_pre + # device_id[1] = n_proj_atom_pre + n_proj_atom_device + # self.device_id = device_id self.structase.set_pbc(self.pbc_negf) alldata = AtomicData.from_ase(self.structase, **self.AtomicData_options) @@ -210,12 +205,12 @@ def initialize(self, kpoints, block_tridiagnal=False): SK = torch.eye(HK.shape[1], dtype=self.model.dtype, device=self.torch_device).unsqueeze(0).repeat(HK.shape[0], 1, 1) # H, S = self.apiH.get_HK(kpoints=kpoints) - d_start = int(np.sum(self.h2k.atom_norbs[:device_id[0]])) - d_end = int(np.sum(self.h2k.atom_norbs)-np.sum(self.h2k.atom_norbs[device_id[1]:])) + d_start = int(np.sum(self.h2k.atom_norbs[:self.device_id[0]])) + d_end = int(np.sum(self.h2k.atom_norbs)-np.sum(self.h2k.atom_norbs[self.device_id[1]:])) HD, SD = HK[:,d_start:d_end, d_start:d_end], SK[:, d_start:d_end, d_start:d_end] Hall, Sall = HK, SK - structure_device = self.structase[device_id[0]:device_id[1]] + structure_device = self.structase[self.device_id[0]:self.device_id[1]] structure_device.pbc = self.pbc_negf structure_leads = {};coupling_width = {} @@ -260,6 +255,7 @@ def initialize(self, kpoints, block_tridiagnal=False): # lead_data[AtomicDataDict.EDGE_FEATURES_KEY] = lead_data[AtomicDataDict.EDGE_FEATURES_KEY][mask] # if self.overlap: # lead_data[AtomicDataDict.EDGE_OVERLAP_KEY] = lead_data[AtomicDataDict.EDGE_OVERLAP_KEY][mask] + self.remove_bonds_nonpbc(lead_data,self.pbc_negf) lead_data = self.h2k(lead_data) HK_lead = lead_data[AtomicDataDict.HAMILTONIAN_KEY] From 2049d18b00487b86adf657a0bf91111cad6a77a7 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Apr 2024 16:36:06 +0800 Subject: [PATCH 134/209] fix energy setting problem in selfenergy calculation --- dptb/negf/recursive_green_cal.py | 6 +++--- dptb/negf/surface_green.py | 20 +++++++++++++++++++- dptb/postprocess/NEGF.py | 4 +--- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/dptb/negf/recursive_green_cal.py b/dptb/negf/recursive_green_cal.py index e11f8593..5339f891 100644 --- a/dptb/negf/recursive_green_cal.py +++ b/dptb/negf/recursive_green_cal.py @@ -160,11 +160,11 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list, sd, su, sl, s_i # ------------------------------------------------------------------- for jj, item in enumerate(mat_d_list): - mat_d_list[jj] = mat_d_list[jj] + (energy - 1j * eta) * sd[jj] + mat_d_list[jj] = mat_d_list[jj] + (energy + 1j * eta) * sd[jj] for jj, item in enumerate(mat_l_list): - mat_l_list[jj] = mat_l_list[jj] + (energy - 1j * eta) * sl[jj] + mat_l_list[jj] = mat_l_list[jj] + (energy + 1j * eta) * sl[jj] for jj, item in enumerate(mat_u_list): - mat_u_list[jj] = mat_u_list[jj] + (energy - 1j * eta) * su[jj] + mat_u_list[jj] = mat_u_list[jj] + (energy + 1j * eta) * su[jj] # ------------------------------------------------------------------- # ---- choose a proper output depending on the list of arguments ---- diff --git a/dptb/negf/surface_green.py b/dptb/negf/surface_green.py index ecd1d826..4530e236 100644 --- a/dptb/negf/surface_green.py +++ b/dptb/negf/surface_green.py @@ -201,7 +201,8 @@ def selfEnergy(hL, hLL, sL, sLL, ee, hDL=None, sDL=None, etaLead=1e-8, Bulk=Fals else: a, b = hDL.shape SGF = SurfaceGreen.apply(hL, hLL, sL, sLL, eeshifted + 1j * etaLead, method) - Sig = (ee*sDL-hDL) @ SGF[:b,:b] @ (ee*sDL.conj().T-hDL.conj().T) + #SGF = iterative_simple(eeshifted + 1j * etaLead, hL, hLL, sL, sLL, iter_max=1000) + Sig = (eeshifted*sDL-hDL) @ SGF[:b,:b] @ (eeshifted*sDL.conj().T-hDL.conj().T) return Sig, SGF # R(nuo, nuo) @@ -289,3 +290,20 @@ def iterative_gf(ee, gs, h00, h01, s00, s01, iter=1): gs = tLA.pinv(gs) return gs + +def iterative_simple(ee, h00, h01, s00, s01, iter_max=1000): + gs = torch.linalg.inv(ee*s00 - h00) + diff_gs = 1 + iter = 0 + while diff_gs > 1e-8: + iter +=1 + gs = ee*s00 - h00 - (ee * s01 - h01) @ gs @ (ee * s01.conj().T - h01.conj().T) + # gs = tLA.pinv(gs) + gs = torch.linalg.inv(gs) + diff_gs = \ + torch.max(torch.abs(gs - torch.inverse(ee * s00 - h00 - torch.mm(h01 - ee * s01, gs).mm(h01.conj().T - ee * s01.conj().T)))) + if iter > iter_max: + log.warning("iterative_simple not converged after 1000 iteration.") + break + + return gs \ No newline at end of file diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 4794daeb..b8f8c295 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -43,7 +43,6 @@ def __init__(self, out_tc: bool=False,out_dos: bool=False,out_density: bool=False,out_potential: bool=False, out_current: bool=False,out_current_nscf: bool=False,out_ldos: bool=False,out_lcurrent: bool=False, results_path: Optional[str]=None, - overlap=False, torch_device: Union[str, torch.device]=torch.device('cpu'), **kwargs): @@ -55,7 +54,7 @@ def __init__(self, # self.jdata = jdata self.cdtype = torch.complex128 self.torch_device = torch_device - self.overlap = overlap + # get the parameters self.ele_T = ele_T @@ -105,7 +104,6 @@ def __init__(self, stru_options=self.stru_options, unit = self.unit, results_path=self.results_path, - overlap = self.overlap, torch_device = self.torch_device) with torch.no_grad(): struct_device, struct_leads = self.negf_hamiltonian.initialize(kpoints=self.kpoints, From e205662236d62e5fb82f0bf0f2dfd50e654000b8 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Apr 2024 17:46:22 +0800 Subject: [PATCH 135/209] add test files for negf with overlap --- .../test_negf/test_negf_run/nnsk_C_newS.json | 45 ++++++++++++++ dptb/tests/test_negf_run.py | 61 ++++++++++++++++++- 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 dptb/tests/data/test_negf/test_negf_run/nnsk_C_newS.json diff --git a/dptb/tests/data/test_negf/test_negf_run/nnsk_C_newS.json b/dptb/tests/data/test_negf/test_negf_run/nnsk_C_newS.json new file mode 100644 index 00000000..a536c019 --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_run/nnsk_C_newS.json @@ -0,0 +1,45 @@ +{ "version":2, +"model_params": { + "onsite": {}, + "hopping": { + "C-C-2s-2s-0": [ + 0.4, + 1.0 + ] + }, + "overlap": { + "C-C-2s-2s-0": [ + 0.05, + 1.0 + ] + } +}, +"unit": "eV", +"model_options": { + "nnsk": { + "onsite": { + "method": "none" + }, + "hopping": { + "method": "powerlaw", + "rs": 1.6, + "w": 0.3 + }, + "soc": {}, + "freeze": false, + "push": false, + "std": 0.01 + } +}, +"common_options": { + "basis": { + "C": [ + "2s" + ] + }, + "dtype": "float64", + "device": "cpu", + "overlap": true +} +} + diff --git a/dptb/tests/test_negf_run.py b/dptb/tests/test_negf_run.py index 0351e674..7ae647a8 100644 --- a/dptb/tests/test_negf_run.py +++ b/dptb/tests/test_negf_run.py @@ -28,7 +28,7 @@ def test_negf_run_chain(root_directory): -def test_negf_run(root_directory): +def test_negf_run_orth(root_directory): INPUT_file = root_directory +"/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json" output = root_directory +"/dptb/tests/data/test_negf/test_negf_run/out_negf_graphene" checkfile = root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C_new.json' @@ -88,3 +88,62 @@ def test_negf_run(root_directory): T_avg_standard = torch.tensor(T_avg_standard) assert abs(T_avg-T_avg_standard).max()<1e-4 #compare with calculated transmission at efermi +def test_negf_run_S(root_directory): + INPUT_file = root_directory +"/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json" + output = root_directory +"/dptb/tests/data/test_negf/test_negf_run/out_negf_graphene" + checkfile = root_directory +'/dptb/tests/data/test_negf/test_negf_run/nnsk_C_newS.json' + structure = root_directory +"/dptb/tests/data/test_negf/test_negf_run/graphene.xyz" + + run(INPUT=INPUT_file,init_model=checkfile,output=output,run_sk=True,structure=structure,\ + log_level=5,log_path=output+"/test.log",use_correction=False) + + negf_results = torch.load(output+"/results/negf.out.pth") + + k_standard = np.array([[0. , 0. , 0.], [0. , 0.33333333, 0.]]) + k = negf_results['k'] + assert(abs(k-k_standard).max()<1e-5) #compare with calculated kpoints + + wk_standard = np.array([0.3333333333333333, 0.6666666666666666]) + wk = np.array(negf_results['wk']) + assert abs(wk-wk_standard).max()<1e-5 #compare with calculated weight + + + T_k0 = negf_results['T_k'][str(negf_results['k'][0])] + T_k0_standard = [5.3533e-16, 1.7583e-15, 6.6429e-15, 3.0157e-14, 1.7671e-13, 1.5268e-12, + 2.6431e-11, 2.9100e-09, 9.9979e-01, 9.9988e-01, 9.9991e-01, 9.9992e-01, + 9.9993e-01, 9.9993e-01, 9.9993e-01, 9.9993e-01, 9.9993e-01, 9.9993e-01, + 9.9991e-01, 9.9986e-01, 1.1692e-08, 3.8933e-10, 7.0993e-11, 2.7496e-11, + 1.8139e-11, 1.9213e-11, 3.3478e-11, 1.0907e-10, 1.0088e-09, 6.0637e-07, + 9.9988e-01, 9.9990e-01, 9.9991e-01, 9.9990e-01, 9.9990e-01, 9.9988e-01, + 9.9985e-01, 9.9969e-01, 3.1857e-10, 1.5012e-12, 4.5775e-14, 2.9746e-15, + 2.9804e-16, 3.9799e-17, 6.5382e-18, 1.2576e-18, 2.7403e-19, 6.6083e-20, + 1.7337e-20, 4.8842e-21] + T_k0_standard = torch.tensor(T_k0_standard) + assert abs(T_k0-T_k0_standard).max()<1e-4 + + T_k1 = negf_results['T_k'][str(negf_results['k'][1])] + T_k1_standard = [4.9559e-17, 1.3964e-16, 4.3406e-16, 1.5227e-15, 6.2301e-15, 3.1271e-14, + 2.0969e-13, 2.2174e-12, 5.6095e-11, 2.8496e-08, 9.9983e-01, 9.9989e-01, + 9.9991e-01, 9.9992e-01, 1.9996e+00, 1.9998e+00, 1.9998e+00, 1.9998e+00, + 1.9998e+00, 1.9998e+00, 1.9998e+00, 9.9992e-01, 9.9992e-01, 9.9992e-01, + 9.9992e-01, 9.9992e-01, 9.9992e-01, 9.9991e-01, 1.9995e+00, 1.9998e+00, + 1.9998e+00, 1.9998e+00, 1.9997e+00, 1.9996e+00, 9.9988e-01, 9.9984e-01, + 9.9968e-01, 2.5744e-10, 1.2387e-12, 3.7395e-14, 2.3958e-15, 2.3651e-16, + 3.1124e-17, 5.0410e-18, 9.5653e-19, 2.0574e-19, 4.9005e-20, 1.2706e-20, + 3.5398e-21, 1.0487e-21] + T_k1_standard = torch.tensor(T_k1_standard) + assert abs(T_k1-T_k1_standard).max()<1e-4 + + + T_avg = negf_results['T_avg'] + T_avg_standard = [2.1148e-16, 6.7919e-16, 2.5037e-15, 1.1067e-14, 6.3055e-14, 5.2978e-13, + 8.9502e-12, 9.7148e-10, 3.3326e-01, 3.3329e-01, 9.9985e-01, 9.9990e-01, + 9.9991e-01, 9.9992e-01, 1.6664e+00, 1.6665e+00, 1.6665e+00, 1.6665e+00, + 1.6665e+00, 1.6665e+00, 1.3332e+00, 6.6661e-01, 6.6661e-01, 6.6661e-01, + 6.6661e-01, 6.6661e-01, 6.6661e-01, 6.6661e-01, 1.3330e+00, 1.3332e+00, + 1.6665e+00, 1.6665e+00, 1.6665e+00, 1.6664e+00, 9.9988e-01, 9.9985e-01, + 9.9974e-01, 3.3323e-01, 1.0702e-10, 5.2534e-13, 1.6856e-14, 1.1492e-15, + 1.2010e-16, 1.6627e-17, 2.8171e-18, 5.5635e-19, 1.2401e-19, 3.0499e-20, + 8.1390e-21, 2.3272e-21] + T_avg_standard = torch.tensor(T_avg_standard) + assert abs(T_avg-T_avg_standard).max()<1e-4 #compare with calculated transmission at efermi \ No newline at end of file From c1e09a4e77f70307cba983f4875cb90b9bc3fe7c Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Apr 2024 17:53:46 +0800 Subject: [PATCH 136/209] add test_negf_hamiltonian_init file --- .../test_negf_hamiltonian_init/band.json | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 dptb/tests/data/test_negf/test_negf_hamiltonian/test_negf_hamiltonian_init/band.json diff --git a/dptb/tests/data/test_negf/test_negf_hamiltonian/test_negf_hamiltonian_init/band.json b/dptb/tests/data/test_negf/test_negf_hamiltonian/test_negf_hamiltonian_init/band.json new file mode 100644 index 00000000..e5e2aa5b --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_hamiltonian/test_negf_hamiltonian_init/band.json @@ -0,0 +1,56 @@ +{ + "structure": "/personal/DeepTB/dptb_Zjj/DeePTB/dptb/tests/data/test_negf/test_negf_run/chain.vasp", + "task_options": { + "task": "band", + "kline_type": "abacus", + "kpath": [ + [ + 0, + 0, + 0, + 50 + ], + [ + 0, + 0, + 1, + 1 + ] + ], + "nkpoints": 51, + "klabels": [ + "G", + "M" + ], + "E_fermi": -13.638589859008789, + "emin": -1.5, + "emax": 1.5, + "nel_atom": {"C":1}, + "ref_band": null + }, + "AtomicData_options": { + "r_max":2.0 + }, + "common_options": { + "basis":{"C":["2s"]}, + "device":"cpu", + "dtype": "float32", + "overlap":false + }, + "model_options":{ + "nnsk":{ + "onsite":{ + "method":"none" + }, + "hopping":{ + "method":"powerlaw", + "rs":1.6, + "w":0.3 + }, + "soc":{}, + "freeze":false, + "push":false, + "std":0.01 + } + } +} \ No newline at end of file From 126816034de5bdf670a2b14ae706225fdc944c03 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Apr 2024 17:54:19 +0800 Subject: [PATCH 137/209] remove unnecssary band.json --- .../test_negf_hamiltonian_init/band.json | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 dptb/tests/data/test_negf/test_negf_hamiltonian/test_negf_hamiltonian_init/band.json diff --git a/dptb/tests/data/test_negf/test_negf_hamiltonian/test_negf_hamiltonian_init/band.json b/dptb/tests/data/test_negf/test_negf_hamiltonian/test_negf_hamiltonian_init/band.json deleted file mode 100644 index e5e2aa5b..00000000 --- a/dptb/tests/data/test_negf/test_negf_hamiltonian/test_negf_hamiltonian_init/band.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "structure": "/personal/DeepTB/dptb_Zjj/DeePTB/dptb/tests/data/test_negf/test_negf_run/chain.vasp", - "task_options": { - "task": "band", - "kline_type": "abacus", - "kpath": [ - [ - 0, - 0, - 0, - 50 - ], - [ - 0, - 0, - 1, - 1 - ] - ], - "nkpoints": 51, - "klabels": [ - "G", - "M" - ], - "E_fermi": -13.638589859008789, - "emin": -1.5, - "emax": 1.5, - "nel_atom": {"C":1}, - "ref_band": null - }, - "AtomicData_options": { - "r_max":2.0 - }, - "common_options": { - "basis":{"C":["2s"]}, - "device":"cpu", - "dtype": "float32", - "overlap":false - }, - "model_options":{ - "nnsk":{ - "onsite":{ - "method":"none" - }, - "hopping":{ - "method":"powerlaw", - "rs":1.6, - "w":0.3 - }, - "soc":{}, - "freeze":false, - "push":false, - "std":0.01 - } - } -} \ No newline at end of file From a5a7998275a8bf62f39acd7d906f8ec4b14392d8 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 24 Apr 2024 17:59:03 +0800 Subject: [PATCH 138/209] add .gitkeep in directory --- .../test_negf_hamiltonian/test_negf_hamiltonian_init/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 dptb/tests/data/test_negf/test_negf_hamiltonian/test_negf_hamiltonian_init/.gitkeep diff --git a/dptb/tests/data/test_negf/test_negf_hamiltonian/test_negf_hamiltonian_init/.gitkeep b/dptb/tests/data/test_negf/test_negf_hamiltonian/test_negf_hamiltonian_init/.gitkeep new file mode 100644 index 00000000..e69de29b From 8664b19386c796904c98f149eb4b38fea1ab938a Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 26 Apr 2024 11:08:10 +0800 Subject: [PATCH 139/209] add poisson options in argcheck --- dptb/utils/argcheck.py | 66 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 95f092bc..0a436cc1 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -1021,18 +1021,76 @@ def fmm(): Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err) ] -def pyamg(): +def pyamg(): doc_err = "" - + doc_tolerance="" + doc_grid="" + doc_gate="" + doc_dielectric="" + doc_max_iter="" + doc_mix_rate="" return [ - Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err) + Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err), + Argument("tolerance", [int, float], optional=True, default=1e-7, doc=doc_tolerance), + Argument("max_iter", int, optional=True, default=100, doc=doc_max_iter), + Argument("mix_rate", int, optional=True, default=0.25, doc=doc_mix_rate), + Argument("grid", dict, optional=False, sub_fields=grid(), doc=doc_grid), + Argument("gate_top", dict, optional=False, sub_fields=gate(), doc=doc_gate), + Argument("gate_bottom", dict, optional=False, sub_fields=gate(), doc=doc_gate), + Argument("dielectric_region", dict, optional=False, sub_fields=dielectric(), doc=doc_dielectric) ] def scipy(): doc_err = "" + doc_tolerance="" + doc_grid="" + doc_gate="" + doc_dielectric="" + doc_max_iter="" + doc_mix_rate="" + return [ + Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err), + Argument("tolerance", [int, float], optional=True, default=1e-7, doc=doc_tolerance), + Argument("max_iter", int, optional=True, default=100, doc=doc_max_iter), + Argument("mix_rate", int, optional=True, default=0.25, doc=doc_mix_rate), + Argument("grid", dict, optional=False, sub_fields=grid(), doc=doc_grid), + Argument("gate_top", dict, optional=False, sub_fields=gate(), doc=doc_gate), + Argument("gate_bottom", dict, optional=False, sub_fields=gate(), doc=doc_gate), + Argument("dielectric_region", dict, optional=False, sub_fields=dielectric(), doc=doc_dielectric) + ] +def grid(): + doc_xrange="" + doc_yrange="" + doc_zrange="" return [ - Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err) + Argument("x_range", str, optional=False, doc=doc_xrange), + Argument("y_range", str, optional=False, doc=doc_yrange), + Argument("z_range", str, optional=False, doc=doc_zrange), + ] + +def gate(): + doc_xrange="" + doc_yrange="" + doc_zrange="" + doc_voltage="" + return [ + Argument("x_range", str, optional=False, doc=doc_xrange), + Argument("y_range", str, optional=False, doc=doc_yrange), + Argument("z_range", str, optional=False, doc=doc_zrange), + Argument("voltage", [int, float], optional=False, doc=doc_voltage) + ] + +def dielectric(): + doc_xrange="" + doc_yrange="" + doc_zrange="" + doc_permittivity="" + return [ + Argument("x_range", str, optional=False, doc=doc_xrange), + Argument("y_range", str, optional=False, doc=doc_yrange), + Argument("z_range", str, optional=False, doc=doc_zrange), + Argument("relative permittivity", [int, float], optional=False, doc=doc_permittivity) ] def run_options(): From 533df922ecf9c196fe94ec58d5b44efc015daad5 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sun, 28 Apr 2024 20:27:43 +0800 Subject: [PATCH 140/209] small fix --- dptb/negf/density.py | 4 ++-- dptb/postprocess/NEGF.py | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index fa05d6e5..785f2be4 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -240,7 +240,7 @@ def __init__(self, n_gauss): self.wlg = None self.e_grid_Fiori = None - def density_integrate_Fiori(self,e_grid,kpoint,Vbias,integrate_way,deviceprop, + def density_integrate_Fiori(self,e_grid,kpoint,Vbias,block_tridiagonal,integrate_way,deviceprop, device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): if integrate_way == "gauss": @@ -266,7 +266,7 @@ def density_integrate_Fiori(self,e_grid,kpoint,Vbias,integrate_way,deviceprop, for eidx, e in enumerate(integrate_range): deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) - deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device,Vbias = Vbias) + deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=block_tridiagonal, eta_device=eta_device,Vbias = Vbias) tx, ty = deviceprop.g_trans.shape lx, ly = deviceprop.lead_L.se.shape diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index b8f8c295..8d79a50a 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -260,12 +260,8 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): while max_diff_phi > err: # update Hamiltonian by modifying onsite energy with potential atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) - # print("atom_gridpoint_index",atom_gridpoint_index) - # np.save(self.results_path+"/atom_gridpoint_index.npy",atom_gridpoint_index) self.potential_at_atom = interface_poisson.phi[atom_gridpoint_index] - # print([torch.full((norb,), p) for p, norb in zip(self.potential_at_atom, self.device_atom_norbs)]) self.potential_at_orb = torch.cat([torch.full((norb,), p) for p, norb in zip(self.potential_at_atom, self.device_atom_norbs)]) - # torch.save(self.potential_at_orb, self.results_path+"/potential_at_orb.pth") self.negf_compute(scf_require=True,Vbias=self.potential_at_orb) @@ -361,6 +357,7 @@ def negf_compute(self,scf_require=False,Vbias=None): e_grid = self.uni_grid, kpoint=k, Vbias=Vbias, + block_tridiagonal=self.block_tridiagonal, integrate_way = self.density_options["integrate_way"], deviceprop=self.deviceprop, device_atom_norbs=self.device_atom_norbs, From 232b7ddb1bb12036bdae8ab4137f94bdb746f836 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 29 Apr 2024 16:39:27 +0800 Subject: [PATCH 141/209] fix Vbias setting caused in sorting when using BTD --- dptb/negf/density.py | 119 +++++++++++++++++++++++------ dptb/negf/negf_hamiltonian_init.py | 22 +++--- dptb/postprocess/NEGF.py | 30 +++++--- 3 files changed, 127 insertions(+), 44 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index 785f2be4..0cb7ddbc 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -240,7 +240,7 @@ def __init__(self, n_gauss): self.wlg = None self.e_grid_Fiori = None - def density_integrate_Fiori(self,e_grid,kpoint,Vbias,block_tridiagonal,integrate_way,deviceprop, + def density_integrate_Fiori(self,e_grid,kpoint,Vbias,block_tridiagonal,subblocks,integrate_way,deviceprop, device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): if integrate_way == "gauss": @@ -278,28 +278,103 @@ def density_integrate_Fiori(self,e_grid,kpoint,Vbias,block_tridiagonal,integrate gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] - - A_L = torch.mm(torch.mm(deviceprop.g_trans,gammaL),deviceprop.g_trans.conj().T) - A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) + + if not block_tridiagonal: + # A_L = torch.mm(torch.mm(deviceprop.grd[0],gammaL),deviceprop.grd[0].conj().T) + # A_R = torch.mm(torch.mm(deviceprop.grd[0],gammaR),deviceprop.grd[0].conj().T) + gpd = [1j*(deviceprop.grd[i]-deviceprop.grd[i].conj().T)-deviceprop.gnd[i] for i in range(len(deviceprop.gnd))] + else: + gpd = [1j*(deviceprop.grd[i]-deviceprop.grd[i].conj().T)-deviceprop.gnd[i] for i in range(len(deviceprop.gnd))] + # gpl = 1j*(deviceprop.grl-deviceprop.gru.conj().T)-deviceprop.gnl + # gpu = 1j*(deviceprop.gru-deviceprop.grl.conj().T)-deviceprop.gnu # Vbias = -1 * potential_at_orb - for Ei_index, Ei_at_atom in enumerate(-1*potential_at_atom): - pre_orbs = sum(device_atom_norbs[:Ei_index]) + for atom_index, Ei_at_atom in enumerate(-1*potential_at_atom): + # electron density if e >= Ei_at_atom: - for j in range(device_atom_norbs[Ei_index]): - free_charge[str(kpoint)][Ei_index] +=\ - pre_factor[eidx]*2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ - +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)) - + if not block_tridiagonal: + pre_orbs = sum(device_atom_norbs[:atom_index]) + last_orbs = pre_orbs + device_atom_norbs[atom_index] + # for j in range(device_atom_norbs[atom_index]): + # free_charge[str(kpoint)][atom_index] +=\ + # pre_factor[eidx]*2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ + # +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)) + free_charge[str(kpoint)][atom_index] +=\ + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) + else: + block_indexs,orb_start,orb_end = self.get_subblock_index(subblocks,atom_index,device_atom_norbs) + if len(block_indexs) == 1: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[block_indexs[0]][orb_start:orb_end,orb_start:orb_end]) + else: + for bindex in block_indexs: + if bindex == block_indexs[0]: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[bindex][orb_start:,orb_start:]) + elif bindex == block_indexs[-1]: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[bindex][:orb_end,:orb_end]) + else: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[bindex]) # hole density else: - for j in range(device_atom_norbs[Ei_index]): - free_charge[str(kpoint)][Ei_index] +=\ - pre_factor[eidx]*2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ - +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))) - + if not block_tridiagonal: + pre_orbs = sum(device_atom_norbs[:atom_index]) + last_orbs = pre_orbs + device_atom_norbs[atom_index] + free_charge[str(kpoint)][atom_index] += pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) + + else: + block_indexs,orb_start,orb_end = self.get_subblock_index(subblocks,atom_index,device_atom_norbs) + if len(block_indexs) == 1: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[block_indexs[0]][orb_start:orb_end,orb_start:orb_end]) + else: + for bindex in block_indexs: + if bindex == block_indexs[0]: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[bindex][orb_start:,orb_start:]) + elif bindex == block_indexs[-1]: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[bindex][:orb_end,:orb_end]) + else: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[bindex]) + + def get_subblock_index(self,subblocks,atom_index,device_atom_norbs): + # print('atom_index:',atom_index) + # print('subblocks:',subblocks) + subblocks_cumsum = [0]+list(np.cumsum(subblocks)) + # print('subblocks_cumsum:',subblocks_cumsum) + pre_orbs = sum(device_atom_norbs[:atom_index]) + last_orbs = pre_orbs + device_atom_norbs[atom_index] + + # print('pre_orbs:',pre_orbs) + # print('last_orbs:',last_orbs) + + block_index = [] + for i in range(len(subblocks_cumsum)-1): + if pre_orbs >= subblocks_cumsum[i] and last_orbs <= subblocks_cumsum[i+1]: + block_index.append(i) + orb_start = pre_orbs - subblocks_cumsum[i] + orb_end = last_orbs - subblocks_cumsum[i] + # print('1') + break + elif pre_orbs >= subblocks_cumsum[i] and pre_orbs < subblocks_cumsum[i+1] and last_orbs > subblocks_cumsum[i+1]: + block_index.append(i) + orb_start = pre_orbs - subblocks_cumsum[i] + for j in range(i+1,len(subblocks_cumsum)-1): + block_index.append(j) + if last_orbs <= subblocks_cumsum[j+1]: + orb_end = last_orbs - subblocks_cumsum[j] + # print('2') + break + # print('block_index',block_index) + # print('orb_start',orb_start) + # print('orb_end',orb_end) + return block_index,orb_start,orb_end @@ -335,19 +410,19 @@ def density_integrate_Fiori(self,e_grid,kpoint,Vbias,block_tridiagonal,integrate # A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) # # Vbias = -1 * potential_at_orb - # for Ei_index, Ei_at_atom in enumerate(-1*potential_at_atom): - # pre_orbs = sum(device_atom_norbs[:Ei_index]) + # for atom_index, Ei_at_atom in enumerate(-1*potential_at_atom): + # pre_orbs = sum(device_atom_norbs[:atom_index]) # # electron density # if e >= Ei_at_atom: - # for j in range(device_atom_norbs[Ei_index]): - # free_charge[str(kpoint)][Ei_index] +=\ + # for j in range(device_atom_norbs[atom_index]): + # free_charge[str(kpoint)][atom_index] +=\ # self.wlg[eidx]*2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ # +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)) # # hole density # else: - # for j in range(device_atom_norbs[Ei_index]): - # free_charge[str(kpoint)][Ei_index] +=\ + # for j in range(device_atom_norbs[atom_index]): + # free_charge[str(kpoint)][atom_index] +=\ # self.wlg[eidx]*2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ # +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))) \ No newline at end of file diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 4b3ad041..6d3886d6 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -296,6 +296,7 @@ def initialize(self, kpoints, block_tridiagnal=False): if not block_tridiagnal: # change HD format to ( k_index,block_index=0, orb, orb) + subblocks = [HD.shape[1]] HD = torch.unsqueeze(HD,dim=1) SD = torch.unsqueeze(SD,dim=1) HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) @@ -303,14 +304,14 @@ def initialize(self, kpoints, block_tridiagnal=False): else: leftmost_size = coupling_width['lead_L'] rightmost_size = coupling_width['lead_R'] - hd, hu, hl, sd, su, sl = self.get_block_tridiagonal(HD*self.h_factor,SD.cdouble(),self.structase,\ + hd, hu, hl, sd, su, sl, subblocks = self.get_block_tridiagonal(HD*self.h_factor,SD.cdouble(),self.structase,\ leftmost_size,rightmost_size) HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl}) torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) torch.set_default_dtype(torch.float32) - return structure_device, structure_leads + return structure_device, structure_leads, subblocks def remove_bonds_nonpbc(self,data,pbc): @@ -391,7 +392,9 @@ def get_block_tridiagonal(self,HK,SK,structase:ase.Atoms,leftmost_size:int,right log.info(msg=" the number of elements in subblocks: {}".format(num_total)) log.info(msg=" occupation of subblocks: {} %".format(num_total/(HK[0].shape[0]**2)*100)) - return hd, hu, hl, sd, su, sl + subblocks = subblocks[1:] + + return hd, hu, hl, sd, su, sl, subblocks def get_hs_device(self, kpoint, V, block_tridiagonal=False): """ get the device Hamiltonian and overlap matrix at a specific kpoint @@ -429,23 +432,22 @@ def get_hs_device(self, kpoint, V, block_tridiagonal=False): if V.shape == torch.Size([]): allorb = sum([hd_k[i].shape[0] for i in range(len(hd_k))]) - V = V.repeat(allorb).unsqueeze(0) - V = V.cdouble() + V = V.repeat(allorb) + V = torch.diag(V).cdouble() counted = 0 for i in range(len(hd_k)): # TODO: this part may have probelms when V!=0 l_slice = slice(counted, counted+hd_k[i].shape[0]) - hd_k[i] = hd_k[i] - V[:,l_slice]@sd_k[i] + hd_k[i] = hd_k[i] - V[l_slice,l_slice]@sd_k[i] if i 0: - # hl_k[i-1] = hl_k[i-1] - V[l_slice]*sl_k[i-1] - hl_k[i-1] = hl_k[i-1] - V[:,l_slice]@sl_k[i-1] + hl_k[i-1] = hl_k[i-1] - V[l_slice,l_slice]@sl_k[i-1] counted += hd_k[i].shape[0] return hd_k , sd_k, hl_k , su_k, sl_k, hu_k else: HD_k, SD_k = f["HD"][ik], f["SD"][ik] + V = V.cdouble() return HD_k - V*SD_k, SD_k, [], [], [], [] def get_hs_lead(self, kpoint, tab, v): diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 8d79a50a..b689270b 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -106,9 +106,11 @@ def __init__(self, results_path=self.results_path, torch_device = self.torch_device) with torch.no_grad(): - struct_device, struct_leads = self.negf_hamiltonian.initialize(kpoints=self.kpoints, + struct_device, struct_leads, subblocks = self.negf_hamiltonian.initialize(kpoints=self.kpoints, block_tridiagnal=self.block_tridiagonal) - + self.subblocks = subblocks # for not block_tridiagonal case, subblocks is [HD.shape[1]] + self.left_connected = abs(struct_device.positions[:,2]-min(struct_device.positions[:,2]))<1e-6 + self.right_connected = abs(struct_device.positions[:,2]-max(struct_device.positions[:,2]))<1e-6 self.deviceprop = DeviceProperty(self.negf_hamiltonian, struct_device, results_path=self.results_path, efermi=self.e_fermi) self.deviceprop.set_leadLR( @@ -261,8 +263,9 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # update Hamiltonian by modifying onsite energy with potential atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) self.potential_at_atom = interface_poisson.phi[atom_gridpoint_index] - self.potential_at_orb = torch.cat([torch.full((norb,), p) for p, norb in zip(self.potential_at_atom, self.device_atom_norbs)]) - + self.potential_at_orb = torch.cat([torch.full((norb,), p) for p, norb\ + in zip(self.potential_at_atom, self.device_atom_norbs)]) + self.negf_compute(scf_require=True,Vbias=self.potential_at_orb) # Vbias makes sense for orthogonal basis as in NanoTCAD @@ -286,11 +289,12 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): for ik,k in enumerate(self.kpoints): free_charge_allk += np.real(self.free_charge[str(k)].numpy()) * self.wk[ik] interface_poisson.free_charge[atom_gridpoint_index] = free_charge_allk - + torch.save(free_charge_allk, self.results_path+"/free_charge_iter.pth") + interface_poisson.phi_old = interface_poisson.phi.copy() max_diff_phi = interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi_old-interface_poisson.phi) - + torch.save(interface_poisson.phi, self.results_path+"/phi_iter.pth") iter_count += 1 # Gummel type iteration log.info(msg="Poisson-NEGF iteration: {} Potential Diff Maximum: {}\n".format(iter_count,max_diff_phi)) @@ -301,7 +305,8 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): if iter_count > max_iter: - log.info(msg="Warning! Poisson-NEGF iteration exceeds the upper limit of iterations {}".format(int(max_iter))) + log.warning(msg="Warning! Poisson-NEGF iteration exceeds the upper limit of iterations {}".format(int(max_iter))) + break # profiler.stop() # with open('profile_report.html', 'w') as report_file: # report_file.write(profiler.output_html()) @@ -349,15 +354,16 @@ def negf_compute(self,scf_require=False,Vbias=None): if Vbias is not None and self.density_options["method"] == "Fiori": # set voltage as -1*potential_at_orb[0] and -1*potential_at_orb[-1] for self-energy same as in NanoTCAD if ll == 'lead_L' : - getattr(self.deviceprop, ll).voltage = Vbias[0] + getattr(self.deviceprop, ll).voltage = Vbias[self.left_connected].mean() else: - getattr(self.deviceprop, ll).voltage = Vbias[-1] + getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected].mean() self.density.density_integrate_Fiori( e_grid = self.uni_grid, kpoint=k, Vbias=Vbias, block_tridiagonal=self.block_tridiagonal, + subblocks=self.subblocks, integrate_way = self.density_options["integrate_way"], deviceprop=self.deviceprop, device_atom_norbs=self.device_atom_norbs, @@ -384,10 +390,10 @@ def negf_compute(self,scf_require=False,Vbias=None): if ll.startswith("lead"): if Vbias is not None and self.density_options["method"] == "Fiori": # set voltage as -1*potential_at_orb[0] and -1*potential_at_orb[-1] for self-energy same as in NanoTCAD - if ll == 'lead_L' : - getattr(self.deviceprop, ll).voltage = Vbias[0] + if ll == 'lead_L': + getattr(self.deviceprop, ll).voltage = Vbias[self.left_connected].mean() else: - getattr(self.deviceprop, ll).voltage = Vbias[-1] + getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected].mean() getattr(self.deviceprop, ll).self_energy( energy=e, From d0bc15e9279fbb634eee23dc931ac678afded732 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 29 Apr 2024 16:57:47 +0800 Subject: [PATCH 142/209] update uni_test --- dptb/negf/negf_hamiltonian_init.py | 2 +- dptb/tests/test_negf_density_Ozaki.py | 2 +- dptb/tests/test_negf_device_property.py | 2 +- dptb/tests/test_negf_negf_hamiltonian_init.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 6d3886d6..21160b70 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -447,7 +447,7 @@ def get_hs_device(self, kpoint, V, block_tridiagonal=False): return hd_k , sd_k, hl_k , su_k, sl_k, hu_k else: HD_k, SD_k = f["HD"][ik], f["SD"][ik] - V = V.cdouble() + V = float(V) return HD_k - V*SD_k, SD_k, [], [], [], [] def get_hs_lead(self, kpoint, tab, v): diff --git a/dptb/tests/test_negf_density_Ozaki.py b/dptb/tests/test_negf_density_Ozaki.py index 329d9df8..3e011940 100644 --- a/dptb/tests/test_negf_density_Ozaki.py +++ b/dptb/tests/test_negf_density_Ozaki.py @@ -48,7 +48,7 @@ def test_negf_density_Ozaki(root_directory): # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) with torch.no_grad(): - struct_device, struct_leads = hamiltonian.initialize(kpoints=kpoints) + struct_device, struct_leads, _ = hamiltonian.initialize(kpoints=kpoints) deviceprop = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=negf_json['task_options']['e_fermi']) diff --git a/dptb/tests/test_negf_device_property.py b/dptb/tests/test_negf_device_property.py index 5ee5d58b..85a7455e 100644 --- a/dptb/tests/test_negf_device_property.py +++ b/dptb/tests/test_negf_device_property.py @@ -44,7 +44,7 @@ def test_negf_Device(root_directory): # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) with torch.no_grad(): - struct_device, struct_leads = hamiltonian.initialize(kpoints=kpoints) + struct_device, struct_leads, _ = hamiltonian.initialize(kpoints=kpoints) deviceprop = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=negf_json['task_options']['e_fermi']) diff --git a/dptb/tests/test_negf_negf_hamiltonian_init.py b/dptb/tests/test_negf_negf_hamiltonian_init.py index 6ea4b356..32bdceb4 100644 --- a/dptb/tests/test_negf_negf_hamiltonian_init.py +++ b/dptb/tests/test_negf_negf_hamiltonian_init.py @@ -46,7 +46,7 @@ def test_negf_Hamiltonian(root_directory): # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) with torch.no_grad(): - struct_device, struct_leads = hamiltonian.initialize(kpoints=kpoints) + struct_device, struct_leads, _ = hamiltonian.initialize(kpoints=kpoints) deviceprop = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=negf_json['task_options']['e_fermi']) From fb39b19315c26178165bcac81dcf4f711a305766 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 30 Apr 2024 20:52:09 +0800 Subject: [PATCH 143/209] fix bugs and keep dptb-Poisson SCF same as that in NanoTCAD restrictly. --- dptb/negf/density.py | 49 +++++++++++++++++--------------- dptb/negf/device_property.py | 6 ++-- dptb/negf/recursive_green_cal.py | 12 ++++---- dptb/postprocess/NEGF.py | 12 +++++--- 4 files changed, 45 insertions(+), 34 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index 0cb7ddbc..67488e20 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -280,51 +280,54 @@ def density_integrate_Fiori(self,e_grid,kpoint,Vbias,block_tridiagonal,subblocks gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] if not block_tridiagonal: - # A_L = torch.mm(torch.mm(deviceprop.grd[0],gammaL),deviceprop.grd[0].conj().T) - # A_R = torch.mm(torch.mm(deviceprop.grd[0],gammaR),deviceprop.grd[0].conj().T) - gpd = [1j*(deviceprop.grd[i]-deviceprop.grd[i].conj().T)-deviceprop.gnd[i] for i in range(len(deviceprop.gnd))] + A_Rd = [torch.mm(torch.mm(deviceprop.grd[i],gammaR),deviceprop.grd[i].conj().T) for i in range(len(deviceprop.grd))] else: - gpd = [1j*(deviceprop.grd[i]-deviceprop.grd[i].conj().T)-deviceprop.gnd[i] for i in range(len(deviceprop.gnd))] - # gpl = 1j*(deviceprop.grl-deviceprop.gru.conj().T)-deviceprop.gnl - # gpu = 1j*(deviceprop.gru-deviceprop.grl.conj().T)-deviceprop.gnu + A_Rd = [torch.mm(torch.mm(deviceprop.gr_lc[i],gammaR[-x1:, -x1:]),deviceprop.gr_lc[i].conj().T) for i in range(len(deviceprop.gr_lc))] + + A_Ld = [1j*(deviceprop.grd[i]-deviceprop.grd[i].conj().T)-A_Rd[i] for i in range(len(A_Rd))] + gnd = [A_Ld[i]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ + +A_Rd[i]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi) for i in range(len(A_Ld))] + gpd = [A_Ld[i] + A_Rd[i] - gnd[i] for i in range(len(A_Ld))] + # Vbias = -1 * potential_at_orb for atom_index, Ei_at_atom in enumerate(-1*potential_at_atom): - - + pre_orbs = sum(device_atom_norbs[:atom_index]) + last_orbs = pre_orbs + device_atom_norbs[atom_index] # electron density if e >= Ei_at_atom: if not block_tridiagonal: - pre_orbs = sum(device_atom_norbs[:atom_index]) - last_orbs = pre_orbs + device_atom_norbs[atom_index] - # for j in range(device_atom_norbs[atom_index]): - # free_charge[str(kpoint)][atom_index] +=\ - # pre_factor[eidx]*2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ - # +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)) free_charge[str(kpoint)][atom_index] +=\ - pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(A_Ld[0][pre_orbs:last_orbs,pre_orbs:last_orbs]*\ + deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ + +A_Rd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)) + # free_charge[str(kpoint)][atom_index] +=\ + # pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) else: block_indexs,orb_start,orb_end = self.get_subblock_index(subblocks,atom_index,device_atom_norbs) if len(block_indexs) == 1: free_charge[str(kpoint)][atom_index] += \ - pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[block_indexs[0]][orb_start:orb_end,orb_start:orb_end]) + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(gnd[block_indexs[0]][orb_start:orb_end,orb_start:orb_end]) else: for bindex in block_indexs: if bindex == block_indexs[0]: free_charge[str(kpoint)][atom_index] += \ - pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[bindex][orb_start:,orb_start:]) + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(gnd[bindex][orb_start:,orb_start:]) elif bindex == block_indexs[-1]: free_charge[str(kpoint)][atom_index] += \ - pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[bindex][:orb_end,:orb_end]) + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(gnd[bindex][:orb_end,:orb_end]) else: free_charge[str(kpoint)][atom_index] += \ - pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[bindex]) + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(gnd[bindex]) # hole density else: - if not block_tridiagonal: - pre_orbs = sum(device_atom_norbs[:atom_index]) - last_orbs = pre_orbs + device_atom_norbs[atom_index] - free_charge[str(kpoint)][atom_index] += pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) + if not block_tridiagonal: + free_charge[str(kpoint)][atom_index] +=\ + pre_factor[eidx]*2/2/torch.pi*torch.trace(A_Ld[0][pre_orbs:last_orbs,pre_orbs:last_orbs]\ + *(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ + +A_Rd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]\ + *(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))) + # free_charge[str(kpoint)][atom_index] += pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) else: block_indexs,orb_start,orb_end = self.get_subblock_index(subblocks,atom_index,device_atom_norbs) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index 460d2ac5..8b5afbac 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -195,7 +195,7 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr # for i, e in tqdm(enumerate(ee), desc="Compute green functions: "): - tags = ["g_trans", \ + tags = ["g_trans","gr_lc", \ "grd", "grl", "gru", "gr_left", \ "gnd", "gnl", "gnu", "gin_left", \ "gpd", "gpl", "gpu", "gip_left"] @@ -460,7 +460,9 @@ def lcurrent(self): @property def g_trans(self): return self.greenfuncs["g_trans"] # [n,n] - + @property + def gr_lc(self): # last column of Gr + return self.greenfuncs["gr_lc"] @property def grd(self): return self.greenfuncs["grd"] # [[n,n]] diff --git a/dptb/negf/recursive_green_cal.py b/dptb/negf/recursive_green_cal.py index 5339f891..bce7e2b3 100644 --- a/dptb/negf/recursive_green_cal.py +++ b/dptb/negf/recursive_green_cal.py @@ -85,12 +85,14 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list, sd, su, sl, s_i gru = [None for _ in range(num_of_matrices-1)] grd = [i.clone() for i in gr_left] # Our glorious benefactor. g_trans = gr_left[len(gr_left) - 1].clone() + gr_lc = [gr_left[len(gr_left) - 1].clone()] for q in range(num_of_matrices - 2, -1, -1): # Recursive algorithm grl[q] = grd[q + 1] @ mat_l_list[q] @ gr_left[q] # (B5) We get the off-diagonal blocks for free. gru[q] = gr_left[q] @ mat_u_list[q] @ grd[q + 1] # (B6) because we need .Tthem.T for the next calc: grd[q] = gr_left[q] + gr_left[q] @ mat_u_list[q] @ grl[q] # (B4) I suppose I could also use the lower. g_trans = gr_left[q] @ mat_u_list[q] @ g_trans - + gr_lc.append(g_trans) + gr_lc.reverse() # ------------------------------------------------------------------- # ------ compute the electron correlation function ( Lesser Green Function ) if needed -------- # ------------------------------------------------------------------- @@ -171,25 +173,25 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list, sd, su, sl, s_i # ------------------------------------------------------------------- if not isinstance(s_in, list) and not isinstance(s_out, list): - return g_trans, \ + return g_trans,gr_lc, \ grd, grl, gru, gr_left, \ None, None, None, None, \ None, None, None, None elif isinstance(s_in, list) and not isinstance(s_out, list): - return g_trans, \ + return g_trans,gr_lc, \ grd, grl, gru, gr_left, \ gnd, gnl, gnu, gin_left, \ None, None, None, None elif not isinstance(s_in, list) and isinstance(s_out, list): - return g_trans, \ + return g_trans,gr_lc, \ grd, grl, gru, gr_left, \ None, None, None, None, \ gpd, gpl, gpu, gip_left else: - return g_trans, \ + return g_trans,gr_lc, \ grd, grl, gru, gr_left, \ gnd, gnl, gnu, gin_left, \ gpd, gpl, gpu, gip_left diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index b689270b..ada4e839 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -289,12 +289,12 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): for ik,k in enumerate(self.kpoints): free_charge_allk += np.real(self.free_charge[str(k)].numpy()) * self.wk[ik] interface_poisson.free_charge[atom_gridpoint_index] = free_charge_allk - torch.save(free_charge_allk, self.results_path+"/free_charge_iter.pth") + interface_poisson.phi_old = interface_poisson.phi.copy() max_diff_phi = interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi_old-interface_poisson.phi) - torch.save(interface_poisson.phi, self.results_path+"/phi_iter.pth") + iter_count += 1 # Gummel type iteration log.info(msg="Poisson-NEGF iteration: {} Potential Diff Maximum: {}\n".format(iter_count,max_diff_phi)) @@ -355,8 +355,10 @@ def negf_compute(self,scf_require=False,Vbias=None): # set voltage as -1*potential_at_orb[0] and -1*potential_at_orb[-1] for self-energy same as in NanoTCAD if ll == 'lead_L' : getattr(self.deviceprop, ll).voltage = Vbias[self.left_connected].mean() + # getattr(self.deviceprop, ll).voltage = Vbias[0] else: getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected].mean() + # getattr(self.deviceprop, ll).voltage = Vbias[-1] self.density.density_integrate_Fiori( e_grid = self.uni_grid, @@ -391,9 +393,11 @@ def negf_compute(self,scf_require=False,Vbias=None): if Vbias is not None and self.density_options["method"] == "Fiori": # set voltage as -1*potential_at_orb[0] and -1*potential_at_orb[-1] for self-energy same as in NanoTCAD if ll == 'lead_L': - getattr(self.deviceprop, ll).voltage = Vbias[self.left_connected].mean() + # getattr(self.deviceprop, ll).voltage = Vbias[self.left_connected].mean() + getattr(self.deviceprop, ll).voltage = Vbias[0] else: - getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected].mean() + # getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected].mean() + getattr(self.deviceprop, ll).voltage = Vbias[-1] getattr(self.deviceprop, ll).self_energy( energy=e, From 1f4f4f55247bff7eb30fa4535e16e01ee60f74e7 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 30 Apr 2024 23:09:53 +0800 Subject: [PATCH 144/209] update density calculation with BTD and non-BTD consistent with NanoTCAD --- dptb/negf/density.py | 9 ++------- dptb/negf/negf_hamiltonian_init.py | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index 67488e20..397e1206 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -298,9 +298,7 @@ def density_integrate_Fiori(self,e_grid,kpoint,Vbias,block_tridiagonal,subblocks if e >= Ei_at_atom: if not block_tridiagonal: free_charge[str(kpoint)][atom_index] +=\ - pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(A_Ld[0][pre_orbs:last_orbs,pre_orbs:last_orbs]*\ - deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ - +A_Rd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)) + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(gnd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) # free_charge[str(kpoint)][atom_index] +=\ # pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) else: @@ -323,10 +321,7 @@ def density_integrate_Fiori(self,e_grid,kpoint,Vbias,block_tridiagonal,subblocks else: if not block_tridiagonal: free_charge[str(kpoint)][atom_index] +=\ - pre_factor[eidx]*2/2/torch.pi*torch.trace(A_Ld[0][pre_orbs:last_orbs,pre_orbs:last_orbs]\ - *(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ - +A_Rd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]\ - *(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))) + pre_factor[eidx]*2/2/torch.pi*torch.trace(gpd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) # free_charge[str(kpoint)][atom_index] += pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) else: diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 21160b70..6d3886d6 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -447,7 +447,7 @@ def get_hs_device(self, kpoint, V, block_tridiagonal=False): return hd_k , sd_k, hl_k , su_k, sl_k, hu_k else: HD_k, SD_k = f["HD"][ik], f["SD"][ik] - V = float(V) + V = V.cdouble() return HD_k - V*SD_k, SD_k, [], [], [], [] def get_hs_lead(self, kpoint, tab, v): From b81c6c0dc4fe1f7e5ed4dae37467565e45dbe9d8 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 30 Apr 2024 23:25:29 +0800 Subject: [PATCH 145/209] update test files --- dptb/negf/negf_hamiltonian_init.py | 1 - dptb/tests/test_negf_device_property.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 6d3886d6..4c203de1 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -447,7 +447,6 @@ def get_hs_device(self, kpoint, V, block_tridiagonal=False): return hd_k , sd_k, hl_k , su_k, sl_k, hu_k else: HD_k, SD_k = f["HD"][ik], f["SD"][ik] - V = V.cdouble() return HD_k - V*SD_k, SD_k, [], [], [], [] def get_hs_lead(self, kpoint, tab, v): diff --git a/dptb/tests/test_negf_device_property.py b/dptb/tests/test_negf_device_property.py index 85a7455e..a3b8b13a 100644 --- a/dptb/tests/test_negf_device_property.py +++ b/dptb/tests/test_negf_device_property.py @@ -122,7 +122,7 @@ def test_negf_Device(root_directory): ) #check green functions' results - assert list(device.greenfuncs.keys())==['g_trans', 'grd', 'grl', 'gru', 'gr_left', 'gnd', 'gnl',\ + assert list(device.greenfuncs.keys())==['g_trans','gr_lc', 'grd', 'grl', 'gru', 'gr_left', 'gnd', 'gnl',\ 'gnu', 'gin_left', 'gpd', 'gpl', 'gpu', 'gip_left'] g_trans= torch.tensor([[ 1.0983e-11-8.2022e-01j, -8.2022e-01+4.4634e-07j,8.9264e-07+8.2022e-01j, 8.2022e-01-1.3390e-06j], [-8.2022e-01+4.4634e-07j, -3.6607e-12-8.2022e-01j,-8.2021e-01+4.4631e-07j, 8.9264e-07+8.2022e-01j], From f02fd27261ef9e18ef8e77f83ee605d1ae1cb010 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 1 May 2024 11:53:48 +0800 Subject: [PATCH 146/209] add show_blocks in BTD mode --- dptb/negf/negf_hamiltonian_init.py | 5 +- dptb/negf/split_btd.py | 631 ++++++++++++++++++++++++++++- 2 files changed, 632 insertions(+), 4 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 4c203de1..5acc68e5 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -25,7 +25,7 @@ from ase import Atoms from dptb.negf.sort_btd import sort_lexico, sort_projection, sort_capacitance -from dptb.negf.split_btd import compute_edge,compute_blocks,show_blocks,compute_blocks_optimized,split_into_subblocks,split_into_subblocks_optimized +from dptb.negf.split_btd import show_blocks,split_into_subblocks,split_into_subblocks_optimized ''' a Hamiltonian object that initializes and manipulates device and lead Hamiltonians for NEGF ''' @@ -393,7 +393,8 @@ def get_block_tridiagonal(self,HK,SK,structase:ase.Atoms,leftmost_size:int,right log.info(msg=" occupation of subblocks: {} %".format(num_total/(HK[0].shape[0]**2)*100)) subblocks = subblocks[1:] - + show_blocks(subblocks,HK[0],self.results_path) + return hd, hu, hl, sd, su, sl, subblocks def get_hs_device(self, kpoint, V, block_tridiagonal=False): diff --git a/dptb/negf/split_btd.py b/dptb/negf/split_btd.py index ec10ec69..d3cd25b8 100644 --- a/dptb/negf/split_btd.py +++ b/dptb/negf/split_btd.py @@ -625,7 +625,634 @@ def compute_blocks(left_block, right_block, edge, edge1): return [size] -def show_blocks(subblocks, input_mat): +"""This module contains a set of functions facilitating computations of +the block-tridiagonal structure of a band matrix. +""" +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle +from itertools import product +import math +import scipy + + +def accum(accmap, input, func=None, size=None, fill_value=0, dtype=None): + """An accumulation function similar to Matlab's `accumarray` function. + + Parameters + ---------- + accmap : ndarray + This is the "accumulation map". It maps input (i.e. indices into + `a`) to their destination in the output array. The first `a.ndim` + dimensions of `accmap` must be the same as `a.shape`. That is, + `accmap.shape[:a.ndim]` must equal `a.shape`. For example, if `a` + has shape (15,4), then `accmap.shape[:2]` must equal (15,4). In this + case `accmap[i,j]` gives the index into the output array where + element (i,j) of `a` is to be accumulated. If the output is, say, + a 2D, then `accmap` must have shape (15,4,2). The value in the + last dimension give indices into the output array. If the output is + 1D, then the shape of `accmap` can be either (15,4) or (15,4,1) + input : ndarray + The input data to be accumulated. + func : callable or None + The accumulation function. The function will be passed a list + of values from `a` to be accumulated. + If None, numpy.sum is assumed. (Default value = None) + size : ndarray or None + The size of the output array. If None, the size will be determined + from `accmap`. (Default value = None) + fill_value : scalar + The default value for elements of the output array. + dtype : numpy data type, or None + The data type of the output array. If None, the data type of + `a` is used. (Default value = None) + + Returns + ------- + + + """ + + # Check for bad arguments and handle the defaults. + if accmap.shape[:input.ndim] != input.shape: + raise ValueError("The initial dimensions of accmap must be the same as a.shape") + if func is None: + func = np.sum + if dtype is None: + dtype = input.dtype + if accmap.shape == input.shape: + accmap = np.expand_dims(accmap, -1) + adims = tuple(range(input.ndim)) + if size is None: + size = 1 + np.squeeze(np.apply_over_axes(np.max, accmap, axes=adims)) + size = np.atleast_1d(size) + + # Create an array of python lists of values. + vals = np.empty(size, dtype='O') + for s in product(*[range(k) for k in size]): + vals[s] = [] + for s in product(*[range(k) for k in input.shape]): + indx = tuple(accmap[s]) + val = input[s] + vals[indx].append(val) + + # Create the output array. + out = np.empty(size, dtype=dtype) + for s in product(*[range(k) for k in size]): + if vals[s] == []: + out[s] = fill_value + else: + out[s] = func(vals[s]) + + return out + + +def cut_in_blocks(h_0, blocks): + """Cut a matrix into diagonal, upper-diagonal and lower-diagonal blocks + if sizes of the diagonal blocks are specified. + + Parameters + ---------- + h_0 : ndarray + Input matrix + blocks : ndarray(dtype=int) + Sizes of diagonal blocks + + Returns + ------- + h_0_s, h_l_s, h_r_s : ndarray + List of diagonal matrices, + list of lower-diagonal matrices and + list of upper-diagonal matrices. + Note that if the size of the list h_0_s is N, + the sizes of h_l_s, h_r_s are N-1. + + Examples + -------- + >>> import numpy as np + >>> from nanonet.tb.block_tridiagonalization import cut_in_blocks + >>> a = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> a + array([[1, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> # Sum the diagonals. + >>> blocks = [2, 2] + >>> blocks + [2, 2] + >>> h0, h1, h2 = cut_in_blocks(a, blocks) + >>> h0 + [array([[1, 1], + [1, 1]]), array([[1, 1], + [1, 1]])] + >>> h1 + [array([[0, 1], + [0, 0]])] + >>> h2 + [array([[0, 0], + [1, 0]])] + """ + + j1 = 0 + + h_0_s = [] + h_l_s = [] + h_r_s = [] + + for j, block in enumerate(blocks): + h_0_s.append(h_0[j1:block + j1, j1:block + j1]) + if j < len(blocks) - 1: + h_l_s.append(h_0[block + j1:block + j1 + blocks[j + 1], j1:block + j1]) + h_r_s.append(h_0[j1:block + j1, j1 + block:j1 + block + blocks[j + 1]]) + j1 += block + + return h_0_s, h_l_s, h_r_s + + +def find_optimal_cut(edge, edge1, left, right): + """Computes the index corresponding to the optimal cut such that applying + the function compute_blocks() to the sub-blocks defined by the cut reduces + the cost function comparing to the case when the function compute_blocks() is + applied to the whole matrix. If cutting point can not be find, the algorithm returns + the result from the function compute_blocks(). + + Parameters + ---------- + edge : ndarray + sparsity pattern profile of the matrix + edge1 : ndarray + conjugated sparsity pattern profile of the matrix + left : int + size of the leftmost diagonal block + right : int + size of the rightmost diagonal block + + Returns + ------- + + + """ + + unique_indices = np.arange(left, len(edge) - right + 1) + blocks = [] + seps = [] + sizes = [] + metric = [] + size = len(edge) + + for j1, item1 in enumerate(unique_indices): + seps.append(item1) + item2 = size - item1 + + # print(item1, item2) + # print(item1) + + edge_1 = edge[:item1] + edge_2 = (edge1 - np.arange(len(edge1)))[item2:] + np.arange(item1) + + edge_3 = edge1[:item2] + edge_4 = (edge - np.arange(len(edge)))[item1:] + np.arange(item2) + + block1 = compute_blocks(left, (edge1 - np.arange(len(edge)))[item2], + edge_1, edge_2) + + block2 = compute_blocks(right, (edge - np.arange(len(edge1)))[item1], + edge_3, edge_4) + + block = block1 + block2[::-1] + blocks.append(block) + metric.append(np.sum(np.array(block) ** 3)) + sizes.append((block1[-1], block2[-1])) + + if len(metric) == 0: + return [left, right], np.nan, 0, 0 + else: + + best = np.argmin(np.array(metric)) + + blocks = blocks[best] + blocks = [item for item in blocks if item != 0] + + sep = seps[best] + + right_block, left_block = sizes[best] + + return blocks, sep, right_block, left_block + + +def compute_blocks_optimized(edge, edge1, left=1, right=1): + """Computes optimal sizes of diagonal blocks of a matrix whose + sparsity pattern is defined by the sparsity pattern profiles edge and edge1. + This function is based on the algorithm which uses defined above function + find_optimal_cut() to subdivide the problem into sub-problems in a optimal way + according to some cost function. + + Parameters + ---------- + edge : ndarray + sparsity pattern profile of the matrix + edge1 : ndarray + conjugated sparsity pattern profile of the matrix + left : int + size of the leftmost diagonal block (constrained) (Default value = 1) + right : int + size of the rightmost diagonal block (constrained) (Default value = 1) + + Returns + ------- + + + """ + + blocks, sep, right_block, left_block = find_optimal_cut(edge, edge1, left=left, right=right) + flag = False + + if not math.isnan(sep): + + # print(left, right_block, sep) + + if left + right_block < sep: + + edge_1 = edge[:sep] + # edge_1[edge_1 > sep] = sep + edge_2 = (edge1 - np.arange(len(edge1)))[-sep:] + np.arange(sep) + + blocks1 = compute_blocks_optimized(edge_1, edge_2, left=left, right=right_block) + + elif left + right_block == sep: + + blocks1 = [left, right_block] + else: + + flag = True + + # print(left_block, right, len(edge) - sep) + + if right + left_block < len(edge) - sep: + + edge_3 = (edge - np.arange(len(edge)))[sep:] + np.arange(len(edge) - sep) + edge_4 = edge1[:-sep] + # edge_4[edge_4 > len(edge) - sep] = len(edge) - sep + + blocks2 = compute_blocks_optimized(edge_3, edge_4, left=left_block, right=right) + + elif right + left_block == len(edge) - sep: + blocks2 = [left_block, right] + else: + flag = True + + if flag: + return blocks + else: + blocks = blocks1 + blocks2 + + return blocks + + +def find_nonzero_lines(mat, order): + """ + + Parameters + ---------- + mat : + + order : + + + Returns + ------- + + """ + + if scipy.sparse.issparse(mat): + lines = _find_nonzero_lines_sparse(mat, order) + else: + lines = _find_nonzero_lines(mat, order) + + if lines == max(mat.shape[0], mat.shape[1]) - 1: + lines = 1 + if lines == 0: + lines = 1 + + return lines + + +def _find_nonzero_lines(mat, order): + """ + + Parameters + ---------- + mat : + + order : + + + Returns + ------- + + """ + if order == 'top': + line = mat.shape[0] + while line > 0: + if np.count_nonzero(mat[line - 1, :]) == 0: + line -= 1 + else: + break + elif order == 'bottom': + line = -1 + while line < mat.shape[0] - 1: + if np.count_nonzero(mat[line + 1, :]) == 0: + line += 1 + else: + line = mat.shape[0] - (line + 1) + break + elif order == 'left': + line = mat.shape[1] + while line > 0: + if np.count_nonzero(mat[:, line - 1]) == 0: + line -= 1 + else: + break + elif order == 'right': + line = -1 + while line < mat.shape[1] - 1: + if np.count_nonzero(mat[:, line + 1]) == 0: + line += 1 + else: + line = mat.shape[1] - (line + 1) + break + else: + raise ValueError('Wrong value of the parameter order') + + return line + + +def _find_nonzero_lines_sparse(mat, order): + """ + + Parameters + ---------- + mat : + + order : + + + Returns + ------- + + """ + if order == 'top': + line = mat.shape[0] + while line > 0: + if np.count_nonzero(mat[line - 1, :].todense()) == 0: + line -= 1 + else: + break + elif order == 'bottom': + line = -1 + while line < mat.shape[0] - 1: + if np.count_nonzero(mat[line + 1, :].todense()) == 0: + line += 1 + else: + line = mat.shape[0] - (line + 1) + break + elif order == 'left': + line = mat.shape[1] + while line > 0: + if np.count_nonzero(mat[:, line - 1].todense()) == 0: + line -= 1 + else: + break + elif order == 'right': + line = -1 + while line < mat.shape[1] - 1: + if np.count_nonzero(mat[:, line + 1].todense()) == 0: + line += 1 + else: + line = mat.shape[1] - (line + 1) + break + else: + raise ValueError('Wrong value of the parameter order') + + return line + + +def split_into_subblocks_optimized(h_0, left=1, right=1): + """ + + Parameters + ---------- + h_0 : + param left: + right : + return: (Default value = 1) + left : + (Default value = 1) + + Returns + ------- + + """ + + if not (isinstance(left, int) and isinstance(right, int)): + h_r_h = find_nonzero_lines(right, 'bottom') + h_r_v = find_nonzero_lines(right[-h_r_h:, :], 'left') + h_l_h = find_nonzero_lines(left, 'top') + h_l_v = find_nonzero_lines(left[:h_l_h, :], 'right') + left = max(h_l_h, h_r_v) + right = max(h_r_h, h_l_v) + + if left + right > h_0.shape[0]: + return [h_0.shape[0]] + else: + edge, edge1 = compute_edge(h_0) + return compute_blocks_optimized(edge, edge1, left=left, right=right) + + +def split_into_subblocks(h_0, h_l, h_r): + """Split Hamiltonian matrix and coupling matrices into subblocks + + Parameters + ---------- + h_0 : + Hamiltonian matrix + h_l : + left inter-cell coupling matrices + h_r : + right inter-cell coupling matrices + :return h_0_s, h_l_s, h_r_s: lists of subblocks + + Returns + ------- + + """ + + if isinstance(h_l, np.ndarray) and isinstance(h_r, np.ndarray): + h_r_h = find_nonzero_lines(h_r, 'bottom') + h_r_v = find_nonzero_lines(h_r[-h_r_h:, :], 'left') + h_l_h = find_nonzero_lines(h_l, 'top') + h_l_v = find_nonzero_lines(h_l[:h_l_h, :], 'right') + left_block = max(h_l_h, h_r_v) + right_block = max(h_r_h, h_l_v) + elif isinstance(h_l, int) and isinstance(h_r, int): + left_block = h_l + right_block = h_r + else: + raise TypeError + + edge, edge1 = compute_edge(h_0) + + blocks = compute_blocks(left_block, right_block, edge, edge1) + + return blocks + + +def compute_edge(mat): + """Computes edges of the sparsity pattern of a matrix. + + Parameters + ---------- + mat : ndarray + Input matrix + + Returns + ------- + edge : ndarray + edge of the sparsity pattern + edge1 : ndarray + conjugate edge of the sparsity pattern + + Examples + -------- + >>> import numpy as np + >>> from nanonet.tb.block_tridiagonalization import compute_edge + >>> input_matrix = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> e1 + array([2, 3, 4, 4]) + >>> e2 + array([2, 3, 4, 4]) + >>> input_matrix = np.array([[1, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 0, 0, 0], + [0, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> e1 + array([1, 3, 4, 4]) + >>> e2 + array([2, 3, 3, 4]) + """ + + # First get some statistics + if isinstance(mat, scipy.sparse.lil_matrix): + row, col = mat.nonzero() + else: + row, col = np.where(mat != 0.0) # Output rows and columns of all non-zero elements. + + # Clever use of accumarray: + edge = accum(row, col, np.max) + 1 + edge[0] = max(0, edge[0]) + edge = np.maximum.accumulate(edge) + + edge1 = accum(np.max(row) - row[::-1], np.max(row) - col[::-1], np.max) + 1 + edge1[0] = max(0, edge1[0]) + edge1 = np.maximum.accumulate(edge1) + + return edge, edge1 + + +def compute_blocks(left_block, right_block, edge, edge1): + """This is an implementation of the greedy algorithm for + computing block-tridiagonal representation of a matrix. + The information regarding the input matrix is represented + by the sparsity patters edges, `edge` and `edge1`. + + Parameters + ---------- + left_block : int + a predefined size of the leftmost block + right_block : int + a predefined size of the rightmost block + edge : ndarray + edge of sparsity pattern + edge1 : ndarray + conjugate edge of sparsity pattern + + Returns + ------- + ans : list + + + Examples + -------- + >>> import numpy as np + >>> from nanonet.tb.block_tridiagonalization import compute_edge + >>> input_matrix = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> compute_blocks(1, 1, e1, e2) + [1, 1, 1, 1] + >>> input_matrix = np.array([[1, 1, 1, 0], [1, 1, 1, 0], [1, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 1, 1, 0], + [1, 1, 1, 0], + [1, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> compute_blocks(1, 1, e1, e2) + [1, 2, 1] + >>> e1, e2 = compute_edge(input_matrix) + >>> compute_blocks(2, 2, e1, e2) + [2, 2] + """ + + size = len(edge) + left_block = max(1, left_block) + right_block = max(1, right_block) + + if left_block + right_block < size: # if blocks do not overlap + + new_left_block = edge[left_block - 1] - left_block + new_right_block = edge1[right_block - 1] - right_block + # + # new_right_block = np.max(np.argwhere(np.abs(edge - (size - right_block)) - + # np.min(np.abs(edge - (size - right_block))) == 0)) + 1 + # new_right_block = size - new_right_block - right_block + + if left_block + new_left_block <= size - right_block and \ + size - right_block - new_right_block >= left_block: # spacing between blocks is sufficient + + blocks = compute_blocks(new_left_block, + new_right_block, + edge[left_block:-right_block] - left_block, + edge1[right_block:-left_block] - right_block) + + return [left_block] + blocks + [right_block] + else: + if new_left_block > new_right_block: + return [left_block] + [size - left_block] + else: + return [size - right_block] + [right_block] + + elif left_block + right_block == size: # sum of blocks equal to the matrix size + return [left_block] + [right_block] + else: # blocks overlap + return [size] + + +def show_blocks(subblocks, input_mat, results_path): """This is a script for visualizing the sparsity pattern and a block-tridiagonal structure of a matrix. @@ -689,7 +1316,7 @@ def show_blocks(subblocks, input_mat): plt.xlim(input_mat.shape[0] - 0.5, -1.0) plt.ylim(-1.0, input_mat.shape[0] - 0.5) plt.axis('off') - plt.show() + plt.savefig(results_path +'/subblocks.png', dpi=300) # if __name__ == "__main__": From 156968b0bf73c62018680f21163c6312596adec9 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 7 May 2024 20:37:28 +0800 Subject: [PATCH 147/209] fix complex warning --- dptb/postprocess/NEGF.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index ada4e839..1201f9f6 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -285,7 +285,7 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # TODO: check the sign of free_charge # TODO: check the spin degenracy # TODO: add k summation operation - free_charge_allk = torch.zeros_like(torch.tensor(self.device_atom_norbs),dtype=torch.complex128) + free_charge_allk = torch.zeros_like(torch.tensor(self.device_atom_norbs)) for ik,k in enumerate(self.kpoints): free_charge_allk += np.real(self.free_charge[str(k)].numpy()) * self.wk[ik] interface_poisson.free_charge[atom_gridpoint_index] = free_charge_allk @@ -393,11 +393,11 @@ def negf_compute(self,scf_require=False,Vbias=None): if Vbias is not None and self.density_options["method"] == "Fiori": # set voltage as -1*potential_at_orb[0] and -1*potential_at_orb[-1] for self-energy same as in NanoTCAD if ll == 'lead_L': - # getattr(self.deviceprop, ll).voltage = Vbias[self.left_connected].mean() - getattr(self.deviceprop, ll).voltage = Vbias[0] + getattr(self.deviceprop, ll).voltage = self.potential_at_atom[self.left_connected].mean() + else: - # getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected].mean() - getattr(self.deviceprop, ll).voltage = Vbias[-1] + getattr(self.deviceprop, ll).voltage = self.potential_at_atom[self.right_connected].mean() + getattr(self.deviceprop, ll).self_energy( energy=e, From b8d374518d68243d24981aa02f4c5d0d28605a42 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 10 May 2024 21:02:19 +0800 Subject: [PATCH 148/209] add DFTB+ band plot tool --- dptb/utils/band.py | 106 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/dptb/utils/band.py b/dptb/utils/band.py index e0cca38e..a6845a8c 100644 --- a/dptb/utils/band.py +++ b/dptb/utils/band.py @@ -115,4 +115,108 @@ def band_plot(args:argparse.Namespace): f=open('showband.py','w') for iraw in plotcode: print(iraw[0],file=f) - \ No newline at end of file + + + + +def DFTBP_band_plot(structase, pathstr:str, high_sym_kpoints_dict:dict, number_in_line:list,band_file:str): + """The function `DFTBP_band_plot` will plot band structure from DFTB+ output file band_tot.dat. + Note that the band_tot.dat file is generated by DFTB+ code with cmd: dp_bands band.out band. The detailed + operation is in https://dftbplus-recipes.readthedocs.io/en/latest/basics/bandstruct.html. + + It takes in a structure, a string of high symmetry points, a dictionary of high symmetry points, + the numbers of k-points in each line and the band file. It plots the band structure. + + + Parameters: + ----------- + structase: ase structure object + pathstr: str + a string that defines the path in reciprocal space. + high_sym_kpoints: dict + a dictionary of high symmetry points + number_in_line: int + the numbers of k-points in each line + + Returns: + -------- + klist: np.array, float, shape [N,3] + a list of k-points + xlist: np.array, float, shape [N] + a list of x-values + xlist_label: list[float] + a list of high symmetry k-points + klabels: list[str] + + Examples: + --------- + >>> struct = read('struct.vasp') + >>> pathstr = ["Z-G","G-X","X-P"] + >>> high_sym_kpoints_dict = { + >>> "G": [0, 0, 0], + >>> "X": [0, 0, 0.5], + >>> "P": [0.25, 0.25, 0.25], + >>> "Z": [0.5, 0.5, -0.5]} + >>> number_in_line = [21,45,10] + >>> DFTBP_band_plot(struct, pathstr, high_sym_kpoints_dict, number_in_line,band_file='band_tot.dat') + """ + + kpath = [] + klist = [] + + for i in range(len(pathstr)): + kline = (pathstr[i].split('-')) + kpath.append([high_sym_kpoints_dict[kline[0]],high_sym_kpoints_dict[kline[1]]]) + kline_list = np.linspace(high_sym_kpoints_dict[kline[0]], high_sym_kpoints_dict[kline[1]], number_in_line[i]) + klist.append(kline_list) + if i == 0: + klabels = [(pathstr[i].split('-')[0])] + else: + if pathstr[i].split('-')[0] == pathstr[i-1].split('-')[1]: + klabels.append(pathstr[i].split('-')[0]) + else: + klabels.append(pathstr[i-1].split('-')[1] + '|' + pathstr[i].split('-')[0]) + if i == len(pathstr)-1: + klabels.append(pathstr[i].split('-')[1]) + + kpath = np.asarray(kpath) + klist = np.concatenate(klist) + + + rev_latt = np.mat(structase.cell).I.T + #rev_latt = 2*np.pi*np.mat(ase_struct.cell).I + kdiff = kpath[:,1] - kpath[:,0] + kdiff_cart = np.asarray(kdiff * rev_latt) + kdist = np.linalg.norm(kdiff_cart,axis=1) + + xlist_label = [0] + for i in range(len(kdist)): + if i == 0: + xlist = np.linspace(0,kdist[i],number_in_line[i]) + else: + xlist = np.concatenate([xlist, xlist[-1] + np.linspace(0,kdist[i],number_in_line[i])]) + xlist_label.append(xlist[-1]) + + + eigs=[] + with open(band_file) as f: + for line in f.readlines(): + eig=[] + line_ik = line.split() + for ie in range(1,len(line_ik)): + eig.append(float(line_ik[ie])) + eigs.append(eig) + + eigs = np.array(eigs) + + for i in range(eigs.shape[1]): + plt.plot(xlist,eigs[:,i],'b') + for i in xlist_label: + plt.axvline(i, color='k', linestyle='--') + plt.xticks(xlist_label,klabels,fontsize=12) + + plt.ylabel('Energy (eV)') + plt.show() + return + + \ No newline at end of file From 062b1f184b7999e835c162a02e191d326c65b05d Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 14 May 2024 10:02:57 +0800 Subject: [PATCH 149/209] fix Fiori import error when integrate way is direct --- dptb/postprocess/NEGF.py | 6 +++++- dptb/utils/band.py | 27 ++++++++++++++------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 1201f9f6..0dd11e78 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -140,7 +140,11 @@ def __init__(self, if self.density_options["method"] == "Ozaki": self.density = Ozaki(R=self.density_options["R"], M_cut=self.density_options["M_cut"], n_gauss=self.density_options["n_gauss"]) elif self.density_options["method"] == "Fiori": - self.density = Fiori(n_gauss=self.density_options["n_gauss"]) + if self.density_options["integrate_way"] == "gauss": + assert self.density_options["n_gauss"] is not None, "n_gauss should be set for Fiori method using gauss integration" + self.density = Fiori(n_gauss=self.density_options["n_gauss"]) + else: + self.density = Fiori() #calculate the density by integrating the energy window in direct way else: raise ValueError diff --git a/dptb/utils/band.py b/dptb/utils/band.py index a6845a8c..19c10fbd 100644 --- a/dptb/utils/band.py +++ b/dptb/utils/band.py @@ -5,8 +5,8 @@ import numpy as np import matplotlib.pyplot as plt -from dptb.Parameters import Paras -from dptb.nnet import Model +# from dptb.Parameters import Paras +# from dptb.nnet import Model def band_plot(args:argparse.Namespace): @@ -119,7 +119,9 @@ def band_plot(args:argparse.Namespace): -def DFTBP_band_plot(structase, pathstr:str, high_sym_kpoints_dict:dict, number_in_line:list,band_file:str): +def DFTBP_band_plot(structase, pathstr:str, high_sym_kpoints_dict:dict, + number_in_line:list,band_file:str, + ylim=None): """The function `DFTBP_band_plot` will plot band structure from DFTB+ output file band_tot.dat. Note that the band_tot.dat file is generated by DFTB+ code with cmd: dp_bands band.out band. The detailed operation is in https://dftbplus-recipes.readthedocs.io/en/latest/basics/bandstruct.html. @@ -137,20 +139,18 @@ def DFTBP_band_plot(structase, pathstr:str, high_sym_kpoints_dict:dict, number_i a dictionary of high symmetry points number_in_line: int the numbers of k-points in each line + band_file: str + the band file generated by DFTB+ code + ylim: list[float] + the ylimit of the band structure plot Returns: -------- - klist: np.array, float, shape [N,3] - a list of k-points - xlist: np.array, float, shape [N] - a list of x-values - xlist_label: list[float] - a list of high symmetry k-points - klabels: list[str] - + None + Examples: --------- - >>> struct = read('struct.vasp') + >>> struct = read('struct.vasp') or read('struct.gen') >>> pathstr = ["Z-G","G-X","X-P"] >>> high_sym_kpoints_dict = { >>> "G": [0, 0, 0], @@ -214,7 +214,8 @@ def DFTBP_band_plot(structase, pathstr:str, high_sym_kpoints_dict:dict, number_i for i in xlist_label: plt.axvline(i, color='k', linestyle='--') plt.xticks(xlist_label,klabels,fontsize=12) - + if ylim: + plt.ylim(ylim[0],ylim[1]) plt.ylabel('Energy (eV)') plt.show() return From 2232c197b64bdcdb3081e348ffef24c471182c33 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 28 May 2024 11:31:11 +0800 Subject: [PATCH 150/209] fix kpoints as nested_tensor form --- dptb/negf/negf_hamiltonian_init.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 5acc68e5..6074b83a 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -174,7 +174,8 @@ def initialize(self, kpoints, block_tridiagnal=False): alldata = AtomicData.to_AtomicDataDict(alldata.to(self.torch_device)) self.alldata = self.model.idp(alldata) - self.alldata[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_device["kpoints"], dtype=self.model.dtype, device=self.torch_device) + self.alldata[AtomicDataDict.KPOINT_KEY] = \ + torch.nested.as_nested_tensor([torch.as_tensor(HS_device["kpoints"], dtype=self.model.dtype, device=self.torch_device)]) self.alldata = self.model(self.alldata) if self.alldata.get(AtomicDataDict.EDGE_OVERLAP_KEY,None) is not None: @@ -244,7 +245,8 @@ def initialize(self, kpoints, block_tridiagnal=False): lead_data = AtomicData.from_ase(structure_leads[kk], **self.AtomicData_options) lead_data = AtomicData.to_AtomicDataDict(lead_data.to(self.torch_device)) lead_data = self.model.idp(lead_data) - lead_data[AtomicDataDict.KPOINT_KEY] = torch.as_tensor(HS_leads["kpoints"], dtype=self.model.dtype, device=self.torch_device) + lead_data[AtomicDataDict.KPOINT_KEY] = \ + torch.nested.as_nested_tensor([torch.as_tensor(HS_leads["kpoints"], dtype=self.model.dtype, device=self.torch_device)]) lead_data = self.model(lead_data) # remove the edges corresponding to z-direction pbc for HR2HK # for ip,p in enumerate(self.pbc_negf): From 954debb7550c3207f0dc5591ecb77ce3deade883 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 28 May 2024 13:57:44 +0800 Subject: [PATCH 151/209] fix Fiori n_gauss import bug --- dptb/negf/density.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index 397e1206..7416d12c 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -233,7 +233,7 @@ def get_density_onsite(self, deviceprop, DM): class Fiori(Density): - def __init__(self, n_gauss): + def __init__(self, n_gauss=None): super(Fiori, self).__init__() self.n_gauss = n_gauss self.xs = None @@ -244,6 +244,7 @@ def density_integrate_Fiori(self,e_grid,kpoint,Vbias,block_tridiagonal,subblocks device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): if integrate_way == "gauss": + assert self.n_gauss is not None, "n_gauss must be set in the Fiori class" if self.xs is None: self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() From d909a4ed7753c165e908df2c7caef260ee75ef18 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 13 Jun 2024 20:21:16 +0800 Subject: [PATCH 152/209] recover NEGF related files --- dptb/negf/bloch.py | 26 + dptb/negf/density.py | 209 ++++- dptb/negf/device_property.py | 105 ++- dptb/negf/lead_property.py | 21 +- dptb/negf/negf_hamiltonian_init.py | 658 ++++++++++++-- dptb/negf/poisson_init.py | 376 ++++++++ dptb/negf/recursive_green_cal.py | 59 +- dptb/negf/sort_btd.py | 106 +++ dptb/negf/split_btd.py | 1325 ++++++++++++++++++++++++++++ dptb/negf/surface_green.py | 23 +- dptb/negf/try_bloch.ipynb | 650 ++++++++++++++ dptb/postprocess/NEGF.py | 554 ++++++++---- 12 files changed, 3809 insertions(+), 303 deletions(-) create mode 100644 dptb/negf/bloch.py create mode 100644 dptb/negf/poisson_init.py create mode 100644 dptb/negf/sort_btd.py create mode 100644 dptb/negf/split_btd.py create mode 100644 dptb/negf/try_bloch.ipynb diff --git a/dptb/negf/bloch.py b/dptb/negf/bloch.py new file mode 100644 index 00000000..541a3abb --- /dev/null +++ b/dptb/negf/bloch.py @@ -0,0 +1,26 @@ + +import numpy as np + + +class Bloch(object): + def __init__(self,bloch_factor) -> None: + + if isinstance(bloch_factor,list): + bloch_factor = np.array(bloch_factor) + + assert bloch_factor.shape[0] == 3, "kpoint should be a 3D vector" + self.bloch_factor = bloch_factor + + + def unfold_points(self,k): + + + # Create expansion points + B = self.bloch_factor + unfold = np.empty([B[2], B[1], B[0], 3]) + # Use B-casting rules (much simpler) + unfold[:, :, :, 0] = (np.arange(B[0]).reshape(1, 1, -1) + k[0]) / B[0] + unfold[:, :, :, 1] = (np.arange(B[1]).reshape(1, -1, 1) + k[1]) / B[1] + unfold[:, :, :, 2] = (np.arange(B[2]).reshape(-1, 1, 1) + k[2]) / B[2] + # Back-transform shape + return unfold.reshape(-1, 3) \ No newline at end of file diff --git a/dptb/negf/density.py b/dptb/negf/density.py index ea8b29d5..7416d12c 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -140,7 +140,7 @@ def __init__(self, R, M_cut, n_gauss): self.R = R self.n_gauss = n_gauss - def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.): + def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.,Vbias=None): '''calculates the equilibrium and non-equilibrium density matrices for a given k-point. Parameters @@ -166,13 +166,15 @@ def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.): poles = 1j* self.poles * kBT + deviceprop.lead_L.mu - deviceprop.mu # left lead expression for rho_eq deviceprop.lead_L.self_energy(kpoint=kpoint, energy=1j*self.R-deviceprop.mu) deviceprop.lead_R.self_energy(kpoint=kpoint, energy=1j*self.R-deviceprop.mu) - deviceprop.cal_green_function(energy=1j*self.R-deviceprop.mu, kpoint=kpoint, block_tridiagonal=False) + deviceprop.cal_green_function(energy=1j*self.R-deviceprop.mu, kpoint=kpoint, block_tridiagonal=False, + Vbias = Vbias) g0 = deviceprop.grd[0] DM_eq = 1.0j * self.R * g0 for i, e in enumerate(poles): deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) - deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device) + deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device,\ + Vbias = Vbias) term = ((-4 * 1j * kBT) * deviceprop.grd[0] * self.residues[i]).imag DM_eq -= term @@ -187,7 +189,7 @@ def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.): for i, e in enumerate(xs): deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) - deviceprop.cal_green_function(e=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device) + deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device) gr_gamma_ga = torch.mm(torch.mm(deviceprop.grd[0], deviceprop.lead_R.gamma), deviceprop.grd[0].conj().T).real gr_gamma_ga = gr_gamma_ga * (deviceprop.lead_R.fermi_dirac(e+deviceprop.mu) - deviceprop.lead_L.fermi_dirac(e+deviceprop.mu)) DM_neq = DM_neq + wlg[i] * gr_gamma_ga @@ -225,4 +227,201 @@ def get_density_onsite(self, deviceprop, DM): onsite_density = torch.cat([torch.from_numpy(deviceprop.structure.positions), onsite_density.unsqueeze(-1)], dim=-1) - return onsite_density \ No newline at end of file + return onsite_density + + + +class Fiori(Density): + + def __init__(self, n_gauss=None): + super(Fiori, self).__init__() + self.n_gauss = n_gauss + self.xs = None + self.wlg = None + self.e_grid_Fiori = None + + def density_integrate_Fiori(self,e_grid,kpoint,Vbias,block_tridiagonal,subblocks,integrate_way,deviceprop, + device_atom_norbs,potential_at_atom,free_charge, + eta_lead=1e-5, eta_device=1e-5): + if integrate_way == "gauss": + assert self.n_gauss is not None, "n_gauss must be set in the Fiori class" + if self.xs is None: + self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) + # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() + self.e_grid_Fiori = e_grid + elif self.e_grid_Fiori[0] != e_grid[0] or self.e_grid_Fiori[-1] != e_grid[-1]: + self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) + # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() + self.e_grid_Fiori = e_grid + integrate_range = self.xs + pre_factor = self.wlg + elif integrate_way == "direct": + dE = e_grid[1] - e_grid[0] + integrate_range = e_grid + pre_factor = dE * torch.ones(len(e_grid)) + else: + raise ValueError("integrate_way only supports gauss and direct in this version") + + + + for eidx, e in enumerate(integrate_range): + deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) + deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) + deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=block_tridiagonal, eta_device=eta_device,Vbias = Vbias) + + tx, ty = deviceprop.g_trans.shape + lx, ly = deviceprop.lead_L.se.shape + rx, ry = deviceprop.lead_R.se.shape + x0 = min(lx, tx) + x1 = min(rx, ty) + + gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) + gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] + gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) + gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] + + if not block_tridiagonal: + A_Rd = [torch.mm(torch.mm(deviceprop.grd[i],gammaR),deviceprop.grd[i].conj().T) for i in range(len(deviceprop.grd))] + else: + A_Rd = [torch.mm(torch.mm(deviceprop.gr_lc[i],gammaR[-x1:, -x1:]),deviceprop.gr_lc[i].conj().T) for i in range(len(deviceprop.gr_lc))] + + A_Ld = [1j*(deviceprop.grd[i]-deviceprop.grd[i].conj().T)-A_Rd[i] for i in range(len(A_Rd))] + gnd = [A_Ld[i]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ + +A_Rd[i]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi) for i in range(len(A_Ld))] + gpd = [A_Ld[i] + A_Rd[i] - gnd[i] for i in range(len(A_Ld))] + + + # Vbias = -1 * potential_at_orb + for atom_index, Ei_at_atom in enumerate(-1*potential_at_atom): + pre_orbs = sum(device_atom_norbs[:atom_index]) + last_orbs = pre_orbs + device_atom_norbs[atom_index] + # electron density + if e >= Ei_at_atom: + if not block_tridiagonal: + free_charge[str(kpoint)][atom_index] +=\ + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(gnd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) + # free_charge[str(kpoint)][atom_index] +=\ + # pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(deviceprop.gnd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) + else: + block_indexs,orb_start,orb_end = self.get_subblock_index(subblocks,atom_index,device_atom_norbs) + if len(block_indexs) == 1: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(gnd[block_indexs[0]][orb_start:orb_end,orb_start:orb_end]) + else: + for bindex in block_indexs: + if bindex == block_indexs[0]: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(gnd[bindex][orb_start:,orb_start:]) + elif bindex == block_indexs[-1]: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(gnd[bindex][:orb_end,:orb_end]) + else: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*(-1)/2/torch.pi*torch.trace(gnd[bindex]) + # hole density + else: + if not block_tridiagonal: + free_charge[str(kpoint)][atom_index] +=\ + pre_factor[eidx]*2/2/torch.pi*torch.trace(gpd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) + # free_charge[str(kpoint)][atom_index] += pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[0][pre_orbs:last_orbs,pre_orbs:last_orbs]) + + else: + block_indexs,orb_start,orb_end = self.get_subblock_index(subblocks,atom_index,device_atom_norbs) + if len(block_indexs) == 1: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[block_indexs[0]][orb_start:orb_end,orb_start:orb_end]) + else: + for bindex in block_indexs: + if bindex == block_indexs[0]: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[bindex][orb_start:,orb_start:]) + elif bindex == block_indexs[-1]: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[bindex][:orb_end,:orb_end]) + else: + free_charge[str(kpoint)][atom_index] += \ + pre_factor[eidx]*2*1/2/torch.pi*torch.trace(gpd[bindex]) + + def get_subblock_index(self,subblocks,atom_index,device_atom_norbs): + # print('atom_index:',atom_index) + # print('subblocks:',subblocks) + subblocks_cumsum = [0]+list(np.cumsum(subblocks)) + # print('subblocks_cumsum:',subblocks_cumsum) + pre_orbs = sum(device_atom_norbs[:atom_index]) + last_orbs = pre_orbs + device_atom_norbs[atom_index] + + # print('pre_orbs:',pre_orbs) + # print('last_orbs:',last_orbs) + + block_index = [] + for i in range(len(subblocks_cumsum)-1): + if pre_orbs >= subblocks_cumsum[i] and last_orbs <= subblocks_cumsum[i+1]: + block_index.append(i) + orb_start = pre_orbs - subblocks_cumsum[i] + orb_end = last_orbs - subblocks_cumsum[i] + # print('1') + break + elif pre_orbs >= subblocks_cumsum[i] and pre_orbs < subblocks_cumsum[i+1] and last_orbs > subblocks_cumsum[i+1]: + block_index.append(i) + orb_start = pre_orbs - subblocks_cumsum[i] + for j in range(i+1,len(subblocks_cumsum)-1): + block_index.append(j) + if last_orbs <= subblocks_cumsum[j+1]: + orb_end = last_orbs - subblocks_cumsum[j] + # print('2') + break + # print('block_index',block_index) + # print('orb_start',orb_start) + # print('orb_end',orb_end) + return block_index,orb_start,orb_end + + + + # def density_integrate_Fiori_gauss(self,e_grid,kpoint,Vbias,deviceprop,device_atom_norbs,potential_at_atom,free_charge, eta_lead=1e-5, eta_device=1e-5): + + # if self.xs is None: + # self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) + # # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() + # self.e_grid_Fiori = e_grid + # elif self.e_grid_Fiori[0] != e_grid[0] or self.e_grid_Fiori[-1] != e_grid[-1]: + # self.xs, self.wlg = gauss_xw(xl=torch.scalar_tensor(e_grid[0]), xu=torch.scalar_tensor(e_grid[-1]), n=self.n_gauss) + # # self.xs = self.xs.numpy();self.wlg = self.wlg.numpy() + # self.e_grid_Fiori = e_grid + + # for eidx, e in enumerate(self.xs): + + # deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) + # deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) + # deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device,Vbias = Vbias) + + # tx, ty = deviceprop.g_trans.shape + # lx, ly = deviceprop.lead_L.se.shape + # rx, ry = deviceprop.lead_R.se.shape + # x0 = min(lx, tx) + # x1 = min(rx, ty) + + # gammaL = torch.zeros(size=(tx, tx), dtype=torch.complex128) + # gammaL[:x0, :x0] += deviceprop.lead_L.gamma[:x0, :x0] + # gammaR = torch.zeros(size=(ty, ty), dtype=torch.complex128) + # gammaR[-x1:, -x1:] += deviceprop.lead_R.gamma[-x1:, -x1:] + + # A_L = torch.mm(torch.mm(deviceprop.g_trans,gammaL),deviceprop.g_trans.conj().T) + # A_R = torch.mm(torch.mm(deviceprop.g_trans,gammaR),deviceprop.g_trans.conj().T) + + # # Vbias = -1 * potential_at_orb + # for atom_index, Ei_at_atom in enumerate(-1*potential_at_atom): + # pre_orbs = sum(device_atom_norbs[:atom_index]) + + # # electron density + # if e >= Ei_at_atom: + # for j in range(device_atom_norbs[atom_index]): + # free_charge[str(kpoint)][atom_index] +=\ + # self.wlg[eidx]*2*(-1)/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ + # +A_R[pre_orbs+j,pre_orbs+j]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi)) + + # # hole density + # else: + # for j in range(device_atom_norbs[atom_index]): + # free_charge[str(kpoint)][atom_index] +=\ + # self.wlg[eidx]*2*1/2/torch.pi*(A_L[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi)) \ + # +A_R[pre_orbs+j,pre_orbs+j]*(1-deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi))) \ No newline at end of file diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index 51225125..8b5afbac 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -87,8 +87,9 @@ def __init__(self, hamiltonian, structure, results_path, e_T=300, efermi=0.) -> self.e_T = e_T self.efermi = efermi self.mu = self.efermi - self.kpoint = None - self.V = None + self.kpoint = None # kpoint for cal_green_function + self.newK_flag = None # whether the kpoint is new or not in cal_green_function + self.newV_flag = None # whether the voltage is new or not in cal_green_function def set_leadLR(self, lead_L, lead_R): '''initialize the left and right lead in Device object @@ -105,9 +106,10 @@ def set_leadLR(self, lead_L, lead_R): ''' self.lead_L = lead_L self.lead_R = lead_R - self.mu = self.efermi - 0.5*(self.lead_L.voltage + self.lead_R.voltage) + # self.mu = self.efermi - 0.5*(self.lead_L.voltage + self.lead_R.voltage) # temporarily for NanoTCAD - def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=True): + + def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=True, Vbias=None): ''' computes the Green's function for a given energy and k-point in device. the tags used here to identify different Green's functions follows the NEGF theory @@ -134,41 +136,74 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr energy = torch.tensor(energy, dtype=torch.complex128) self.block_tridiagonal = block_tridiagonal - # self.kpoint = kpoint + if self.kpoint is None or abs(self.kpoint - torch.tensor(kpoint)).sum() > 1e-5: + self.kpoint = torch.tensor(kpoint) + self.newK_flag = True + else: + self.newK_flag = False + # if V is not None: # HD_ = self.attachPotential(HD, SD, V) # else: # HD_ = HD - - if os.path.exists(os.path.join(self.results_path, "POTENTIAL.pth")): - self.V = torch.load(os.path.join(self.results_path, "POTENTIAL.pth")) - elif abs(self.mu - self.efermi) > 1e-7: - self.V = self.efermi - self.mu + if hasattr(self, "V"): + self.oldV = self.V else: - self.V = 0. - - if not hasattr(self, "hd") or not hasattr(self, "sd") or self.kpoint is None: - self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) - self.kpoint = torch.tensor(kpoint) - elif not torch.allclose(self.kpoint, torch.tensor(kpoint), atol=1e-5): - self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) - self.kpoint = torch.tensor(kpoint) - + self.oldV = None + + if Vbias is None: + if os.path.exists(os.path.join(self.results_path, "POTENTIAL.pth")): + self.V = torch.load(os.path.join(self.results_path, "POTENTIAL.pth")) + elif abs(self.mu - self.efermi) > 1e-7: + self.V = torch.tensor(self.efermi - self.mu) + else: + self.V = torch.tensor(0.) + else: + self.V = Vbias + + assert torch.is_tensor(self.V) + if not self.oldV is None: + if torch.abs(self.V - self.oldV).sum() > 1e-5: + self.newV_flag = True + else: + self.newV_flag = False + else: + self.newV_flag = True # for the first time to run cal_green_function in Poisson-NEGF SCF + + # if not hasattr(self, "hd") or not hasattr(self, "sd"): + #maybe the reason why different kpoint has different green function + + # if [not hasattr(self, "hd") or not hasattr(self, "sd")]: + # if not self.block_tridiagonal: + # self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(self.kpoint, self.V, block_tridiagonal) + # else: + # self.hd, self.sd, self.hl, self.su, self.sl, self.hu = self.hamiltonian.get_hs_device(self.kpoint, self.V, block_tridiagonal) + # elif [self.newK_flag or self.newV_flag]: # check whether kpoints or Vbias change or not + # if not self.block_tridiagonal: + # self.hd, self.sd, _, _, _, _ = self.hamiltonian.get_hs_device(kpoint, self.V, block_tridiagonal) + # else: + # self.hd, self.sd, self.hl, self.su, self.sl, self.hu = self.hamiltonian.get_hs_device(self.kpoint, self.V, block_tridiagonal) + + # hd in format:(block_index,orb,orb) + if (hasattr(self, "hd") and hasattr(self, "sd")) or (self.newK_flag or self.newV_flag): + self.hd, self.sd, self.hl, self.su, self.sl, self.hu = self.hamiltonian.get_hs_device(self.kpoint, self.V, block_tridiagonal) s_in = [torch.zeros(i.shape).cdouble() for i in self.hd] # for i, e in tqdm(enumerate(ee), desc="Compute green functions: "): - tags = ["g_trans", \ + tags = ["g_trans","gr_lc", \ "grd", "grl", "gru", "gr_left", \ "gnd", "gnl", "gnu", "gin_left", \ "gpd", "gpl", "gpu", "gip_left"] seL = self.lead_L.se seR = self.lead_R.se + # seinL = -i \Sigma_L^< = \Gamma_L f_L + # Fluctuation-Dissipation theorem seinL = 1j*(seL-seL.conj().T) * self.lead_L.fermi_dirac(energy+self.mu).reshape(-1) seinR = 1j*(seR-seR.conj().T) * self.lead_R.fermi_dirac(energy+self.mu).reshape(-1) s01, s02 = s_in[0].shape @@ -183,8 +218,8 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr s_in[0][:idx0,:idy0] = s_in[0][:idx0,:idy0] + seinL[:idx0,:idy0] s_in[-1][-idx1:,-idy1:] = s_in[-1][-idx1:,-idy1:] + seinR[-idx1:,-idy1:] - ans = recursive_gf(energy, hl=[], hd=self.hd, hu=[], - sd=self.sd, su=[], sl=[], + ans = recursive_gf(energy, hl=self.hl, hd=self.hd, hu=self.hu, + sd=self.sd, su=self.su, sl=self.sl, left_se=seL, right_se=seR, seP=None, s_in=s_in, s_out=None, eta=eta_device, chemiPot=self.mu) s_in[0][:idx0,:idy0] = s_in[0][:idx0,:idy0] - seinL[:idx0,:idy0] @@ -302,9 +337,16 @@ def _cal_dos_(self): ''' dos = 0 for jj in range(len(self.grd)): - temp = self.grd[jj] @ self.sd[jj] # taking each diagonal block with all energy e together + if not self.block_tridiagonal or len(self.gru) == 0: + temp = self.grd[jj] @ self.sd[jj] # taking each diagonal block with all energy e together + else: + if jj == 0: + temp = self.grd[jj] @ self.sd[jj] + self.gru[jj] @ self.sl[jj] + elif jj == len(self.grd)-1: + temp = self.grd[jj] @ self.sd[jj] + self.grl[jj-1] @ self.su[jj-1] + else: + temp = self.grd[jj] @ self.sd[jj] + self.grl[jj-1] @ self.su[jj-1] + self.gru[jj] @ self.sl[jj] dos -= temp.imag.diag().sum(-1) / pi - return dos * 2 def _cal_ldos_(self): @@ -318,7 +360,15 @@ def _cal_ldos_(self): ldos = [] # sd = self.hamiltonian.get_hs_device(kpoint=self.kpoint, V=self.V, block_tridiagonal=self.block_tridiagonal)[1] for jj in range(len(self.grd)): - temp = self.grd[jj] @ self.sd[jj] # taking each diagonal block with all energy e together + if not self.block_tridiagonal or len(self.gru) == 0: + temp = self.grd[jj] @ self.sd[jj] # taking each diagonal block with all energy e together + else: + if jj == 0: + temp = self.grd[jj] @ self.sd[jj] + self.gru[jj] @ self.sl[jj] + elif jj == len(self.grd)-1: + temp = self.grd[jj] @ self.sd[jj] + self.grl[jj-1] @ self.su[jj-1] + else: + temp = self.grd[jj] @ self.sd[jj] + self.grl[jj-1] @ self.su[jj-1] + self.gru[jj] @ self.sl[jj] ldos.append(-temp.imag.diag() / pi) # shape(Nd(diagonal elements)) ldos = torch.cat(ldos, dim=0).contiguous() @@ -327,6 +377,7 @@ def _cal_ldos_(self): accmap = np.cumsum(norbs) ldos = torch.stack([ldos[accmap[i]:accmap[i+1]].sum() for i in range(len(accmap)-1)]) + # return ldos*2 return ldos*2 def _cal_local_current_(self): @@ -409,7 +460,9 @@ def lcurrent(self): @property def g_trans(self): return self.greenfuncs["g_trans"] # [n,n] - + @property + def gr_lc(self): # last column of Gr + return self.greenfuncs["gr_lc"] @property def grd(self): return self.greenfuncs["grd"] # [[n,n]] diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 00c2ec7c..3cfe306c 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -74,6 +74,7 @@ def __init__(self, tab, hamiltonian, structure, results_path, voltage, e_T=300, self.efermi = efermi self.mu = self.efermi - self.voltage self.kpoint = None + self.voltage_old = None def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-Sancho"): '''calculate and loads the self energy and surface green function at the given kpoint and energy. @@ -94,15 +95,13 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S # according to given kpoint and e_mesh, calculating or loading the self energy and surface green function to self. if not isinstance(energy, torch.Tensor): - energy = torch.tensor(energy) + energy = torch.tensor(energy) # Energy relative to Ef - - - if not hasattr(self, "HL") or self.kpoint is None: - self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) - self.kpoint = torch.tensor(kpoint) - elif not torch.allclose(self.kpoint, torch.tensor(kpoint), atol=1e-5): + # if not hasattr(self, "HL"): + #TODO: check here whether it is necessary to calculate the self energy every time + if not hasattr(self, "HL") or abs(self.voltage_old-self.voltage)>1e-6 or max(abs(self.kpoint-torch.tensor(kpoint)))>1e-6: self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) + self.voltage_old = self.voltage self.kpoint = torch.tensor(kpoint) @@ -113,8 +112,8 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S sL=self.SL, sLL=self.SLL, hDL=self.HDL, - sDL=self.SDL, - chemiPot=self.mu, + sDL=self.SDL, #TODO: check chemiPot settiing is correct or not + chemiPot=self.efermi, # temmporarily change to self.efermi for the case in which applying lead bias to corresponding to Nanotcad etaLead=eta_lead, method=method ) @@ -132,10 +131,10 @@ def sigmaLR2Gamma(self, se): Returns ------- Gamma - The Gamma function, $\Gamma = -1j(se-se^\dagger)$ + The Gamma function, $\Gamma = 1j(se-se^\dagger)$. ''' - return -1j * (se - se.conj().T) + return 1j * (se - se.conj().T) def fermi_dirac(self, x) -> torch.Tensor: return 1 / (1 + torch.exp((x - self.mu)/ self.kBT)) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index a6f6ffe5..6074b83a 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -6,7 +6,7 @@ from dptb.negf.negf_utils import quad, gauss_xw,update_kmap,leggauss from dptb.negf.ozaki_res_cal import ozaki_residues from dptb.negf.areshkin_pole_sum import pole_maker -from ase.io import read +from ase.io import read,write from dptb.negf.poisson import Density2Potential, getImg from dptb.negf.scf_method import SCFMethod import logging @@ -16,6 +16,16 @@ import numpy as np from dptb.utils.make_kpoints import kmesh_sampling +import ase +from dptb.data import AtomicData, AtomicDataDict +from typing import Optional, Union +from dptb.nn.energy import Eigenvalues +from dptb.nn.hamiltonian import E3Hamiltonian +from dptb.nn.hr2hk import HR2HK +from ase import Atoms + +from dptb.negf.sort_btd import sort_lexico, sort_projection, sort_capacitance +from dptb.negf.split_btd import show_blocks,split_into_subblocks,split_into_subblocks_optimized ''' a Hamiltonian object that initializes and manipulates device and lead Hamiltonians for NEGF ''' @@ -48,19 +58,75 @@ class NEGFHamiltonianInit(object): ''' - def __init__(self, apiH, structase, stru_options, results_path) -> None: - self.apiH = apiH - self.unit = apiH.unit - self.structase = structase + def __init__(self, + model: torch.nn.Module, + AtomicData_options: dict, + structure: ase.Atoms, + block_tridiagonal: bool, + pbc_negf: List[bool], + stru_options:dict, + unit: str, + results_path:Optional[str]=None, + torch_device: Union[str, torch.device]=torch.device('cpu') + ) -> None: + + # TODO: add dtype and device setting to the model + torch.set_default_dtype(torch.float64) + + if isinstance(torch_device, str): + torch_device = torch.device(torch_device) + self.torch_device = torch_device + self.model = model + self.AtomicData_options = AtomicData_options + self.model.eval() + + # get bondlist with pbc in all directions for complete chemical environment + # around atoms in the two ends of device when predicting HR + if isinstance(structure,str): + self.structase = read(structure) + elif isinstance(structure,ase.Atoms): + self.structase = structure + else: + raise ValueError('structure must be ase.Atoms or str') + + self.unit = unit self.stru_options = stru_options + self.pbc_negf = pbc_negf + assert len(self.pbc_negf) == 3 self.results_path = results_path + + self.h2k = HR2HK( + idp=model.idp, + edge_field=AtomicDataDict.EDGE_FEATURES_KEY, + node_field=AtomicDataDict.NODE_FEATURES_KEY, + out_field=AtomicDataDict.HAMILTONIAN_KEY, + dtype= model.dtype, + device=self.torch_device, + ) + + # if overlap: + # self.s2k = HR2HK( + # idp=model.idp, + # overlap=True, + # edge_field=AtomicDataDict.EDGE_OVERLAP_KEY, + # node_field=AtomicDataDict.NODE_OVERLAP_KEY, + # out_field=AtomicDataDict.OVERLAP_KEY, + # dtype=model.dtype, + # device=self.torch_device, + # ) - self.device_id = [int(x) for x in self.stru_options.get("device")["id"].split("-")] + self.device_id = [int(x) for x in self.stru_options['device']["id"].split("-")] self.lead_ids = {} for kk in self.stru_options: if kk.startswith("lead"): self.lead_ids[kk] = [int(x) for x in self.stru_options.get(kk)["id"].split("-")] + # sort the atoms in device region lexicographically + if block_tridiagonal: + self.structase.positions[self.device_id[0]:self.device_id[1]] =\ + self.structase.positions[self.device_id[0]:self.device_id[1]][sort_lexico(self.structase.positions[self.device_id[0]:self.device_id[1]])] + log.info(msg="The structure is sorted lexicographically in this version!") + if self.unit == "Hartree": self.h_factor = 13.605662285137 * 2 elif self.unit == "eV": @@ -92,82 +158,247 @@ def initialize(self, kpoints, block_tridiagnal=False): assert len(np.array(kpoints).shape) == 2 HS_device = {} - HS_leads = {} HS_device["kpoints"] = kpoints - self.apiH.update_struct(self.structase, mode="device", stru_options=j_must_have(self.stru_options, "device"), pbc=self.stru_options["pbc"]) # change parameters to match the structure projection - n_proj_atom_pre = np.array([1]*len(self.structase))[:self.device_id[0]][self.apiH.structure.projatoms[:self.device_id[0]]].sum() - n_proj_atom_device = np.array([1]*len(self.structase))[self.device_id[0]:self.device_id[1]][self.apiH.structure.projatoms[self.device_id[0]:self.device_id[1]]].sum() - proj_device_id = [0,0] - proj_device_id[0] = n_proj_atom_pre - proj_device_id[1] = n_proj_atom_pre + n_proj_atom_device - projatoms = self.apiH.structure.projatoms - - self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] - self.apiH.get_HR() - H, S = self.apiH.get_HK(kpoints=kpoints) - d_start = int(np.sum(self.atom_norbs[:proj_device_id[0]])) - d_end = int(np.sum(self.atom_norbs)-np.sum(self.atom_norbs[proj_device_id[1]:])) - HD, SD = H[:,d_start:d_end, d_start:d_end], S[:, d_start:d_end, d_start:d_end] + # n_proj_atom_pre = np.array([1]*len(self.structase))[:self.device_id[0]].sum() + # n_proj_atom_device = np.array([1]*len(self.structase))[self.device_id[0]:self.device_id[1]].sum() + # device_id = [0,0] + # device_id[0] = n_proj_atom_pre + # device_id[1] = n_proj_atom_pre + n_proj_atom_device + # self.device_id = device_id + + self.structase.set_pbc(self.pbc_negf) + alldata = AtomicData.from_ase(self.structase, **self.AtomicData_options) + alldata[AtomicDataDict.PBC_KEY][2] = True # force pbc in z-axis to get reasonable chemical environment in two ends + + alldata = AtomicData.to_AtomicDataDict(alldata.to(self.torch_device)) + self.alldata = self.model.idp(alldata) + self.alldata[AtomicDataDict.KPOINT_KEY] = \ + torch.nested.as_nested_tensor([torch.as_tensor(HS_device["kpoints"], dtype=self.model.dtype, device=self.torch_device)]) + self.alldata = self.model(self.alldata) - if not block_tridiagnal: - HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) + if self.alldata.get(AtomicDataDict.EDGE_OVERLAP_KEY,None) is not None: + self.overlap = True + self.s2k = HR2HK( + idp=self.model.idp, + overlap=True, + edge_field=AtomicDataDict.EDGE_OVERLAP_KEY, + node_field=AtomicDataDict.NODE_OVERLAP_KEY, + out_field=AtomicDataDict.OVERLAP_KEY, + dtype=self.model.dtype, + device=self.torch_device, + ) + else: + self.overlap = False + + + + self.remove_bonds_nonpbc(self.alldata,self.pbc_negf) + self.alldata = self.h2k(self.alldata) + HK = self.alldata[AtomicDataDict.HAMILTONIAN_KEY] + + + if self.overlap: + self.alldata = self.s2k(self.alldata) + SK = self.alldata[AtomicDataDict.OVERLAP_KEY] else: - hd, hu, hl, sd, su, sl = self.get_block_tridiagonal(HD*self.h_factor, SD) - HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl}) - - torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) - structure_device = self.apiH.structure.projected_struct[self.device_id[0]:self.device_id[1]] + SK = torch.eye(HK.shape[1], dtype=self.model.dtype, device=self.torch_device).unsqueeze(0).repeat(HK.shape[0], 1, 1) + + # H, S = self.apiH.get_HK(kpoints=kpoints) + d_start = int(np.sum(self.h2k.atom_norbs[:self.device_id[0]])) + d_end = int(np.sum(self.h2k.atom_norbs)-np.sum(self.h2k.atom_norbs[self.device_id[1]:])) + HD, SD = HK[:,d_start:d_end, d_start:d_end], SK[:, d_start:d_end, d_start:d_end] + Hall, Sall = HK, SK - structure_leads = {} + structure_device = self.structase[self.device_id[0]:self.device_id[1]] + structure_device.pbc = self.pbc_negf + + structure_leads = {};coupling_width = {} for kk in self.stru_options: if kk.startswith("lead"): HS_leads = {} - stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] - self.apiH.update_struct(stru_lead, mode="lead", stru_options=self.stru_options.get(kk), pbc=self.stru_options["pbc"]) + HS_leads["kpoints"] = kpoints + # update lead id - n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]][projatoms[:self.lead_ids[kk][0]]].sum() - n_proj_atom_lead = np.array([1]*len(self.structase))[self.lead_ids[kk][0]:self.lead_ids[kk][1]][projatoms[self.lead_ids[kk][0]:self.lead_ids[kk][1]]].sum() - proj_lead_id = [0,0] - proj_lead_id[0] = n_proj_atom_pre - proj_lead_id[1] = n_proj_atom_pre + n_proj_atom_lead - - l_start = int(np.sum(self.atom_norbs[:proj_lead_id[0]])) - l_end = int(l_start + np.sum(self.atom_norbs[proj_lead_id[0]:proj_lead_id[1]]) / 2) - HL, SL = H[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian - HDL, SDL = H[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] # device and lead's hopping - HS_leads.update({ - "HL":HL.cdouble()*self.h_factor, - "SL":SL.cdouble(), - "HDL":HDL.cdouble()*self.h_factor, - "SDL":SDL.cdouble()} - ) + n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]].sum() + n_proj_atom_lead = np.array([1]*len(self.structase))[self.lead_ids[kk][0]:self.lead_ids[kk][1]].sum() + lead_id = [0,0] + lead_id[0] = n_proj_atom_pre + lead_id[1] = n_proj_atom_pre + n_proj_atom_lead + + l_start = int(np.sum(self.h2k.atom_norbs[:lead_id[0]])) + l_end = int(l_start + np.sum(self.h2k.atom_norbs[lead_id[0]:lead_id[1]]) / 2) + # lead hamiltonian in the first principal layer(the layer close to the device) + HL, SL = HK[:,l_start:l_end, l_start:l_end], SK[:, l_start:l_end, l_start:l_end] + # device and lead's hopping + HDL, SDL = HK[:,d_start:d_end, l_start:l_end], SK[:,d_start:d_end, l_start:l_end] + nonzero_indice = torch.nonzero(HDL) + coupling_width[kk] = max(torch.max(nonzero_indice[:,1]).item()-torch.min(nonzero_indice[:,1]).item() +1,\ + torch.max(nonzero_indice[:,2]).item()-torch.min(nonzero_indice[:,2]).item() +1) + log.info(msg="The coupling width of {} is {}.".format(kk,coupling_width[kk])) + + structure_leads[kk] = self.get_lead_structure(kk,n_proj_atom_lead) + + # get lead_data + lead_data = AtomicData.from_ase(structure_leads[kk], **self.AtomicData_options) + lead_data = AtomicData.to_AtomicDataDict(lead_data.to(self.torch_device)) + lead_data = self.model.idp(lead_data) + lead_data[AtomicDataDict.KPOINT_KEY] = \ + torch.nested.as_nested_tensor([torch.as_tensor(HS_leads["kpoints"], dtype=self.model.dtype, device=self.torch_device)]) + lead_data = self.model(lead_data) + # remove the edges corresponding to z-direction pbc for HR2HK + # for ip,p in enumerate(self.pbc_negf): + # if not p: + # mask = abs(lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip])<1e-7 + # lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] + # lead_data[AtomicDataDict.EDGE_INDEX_KEY] = lead_data[AtomicDataDict.EDGE_INDEX_KEY][:,mask] + # lead_data[AtomicDataDict.EDGE_FEATURES_KEY] = lead_data[AtomicDataDict.EDGE_FEATURES_KEY][mask] + # if self.overlap: + # lead_data[AtomicDataDict.EDGE_OVERLAP_KEY] = lead_data[AtomicDataDict.EDGE_OVERLAP_KEY][mask] - structure_leads[kk] = self.apiH.structure.struct - self.apiH.get_HR() - h, s = self.apiH.get_HK(kpoints=kpoints) - nL = int(h.shape[1] / 2) - HLL, SLL = h[:, :nL, nL:], s[:, :nL, nL:] # H_{L_first2L_second} - err_l = (h[:, :nL, :nL] - HL).abs().max() - if err_l >= 1e-4: # check the lead hamiltonian get from device and lead calculation matches each other + self.remove_bonds_nonpbc(lead_data,self.pbc_negf) + lead_data = self.h2k(lead_data) + HK_lead = lead_data[AtomicDataDict.HAMILTONIAN_KEY] + if self.overlap: + lead_data = self.s2k(lead_data) + S_lead = lead_data[AtomicDataDict.OVERLAP_KEY] + else: + S_lead = torch.eye(HK_lead.shape[1], dtype=self.model.dtype, device=self.torch_device).unsqueeze(0).repeat(HK_lead.shape[0], 1, 1) + + + nL = int(HK_lead.shape[1] / 2) + HLL, SLL = HK_lead[:, :nL, nL:], S_lead[:, :nL, nL:] # H_{L_first2L_second} + hL, sL = HK_lead[:,:nL,:nL], S_lead[:,:nL,:nL] # lead hamiltonian in one principal layer + err_l_HK = (hL - HL).abs().max() + err_l_SK = (sL - SL).abs().max() + + if max(err_l_HK,err_l_SK) >= 1e-4: + # check the lead hamiltonian get from device and lead calculation matches each other + # a standard check to see the lead environment is bulk-like or not + log.info(msg="The lead's hamiltonian or overlap attained from device and lead calculation does not match. \ + The error is {:.7f}.".format(max(err_l_HK,err_l_SK))) log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") raise RuntimeError - elif 1e-7 <= err_l <= 1e-4: - log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(err_l)) + elif 1e-7 <= max(err_l_HK,err_l_SK) <= 1e-4: + log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(max(err_l_HK,err_l_SK))) HS_leads.update({ + "HL":HL.cdouble()*self.h_factor, + "SL":SL.cdouble(), + "HDL":HDL.cdouble()*self.h_factor, + "SDL":SDL.cdouble(), "HLL":HLL.cdouble()*self.h_factor, - "SLL":SLL.cdouble()} - ) - - HS_leads["kpoints"] = kpoints - + "SLL":SLL.cdouble() + }) + torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) + - return structure_device, structure_leads - + if not block_tridiagnal: + # change HD format to ( k_index,block_index=0, orb, orb) + subblocks = [HD.shape[1]] + HD = torch.unsqueeze(HD,dim=1) + SD = torch.unsqueeze(SD,dim=1) + HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) + HS_device.update({"Hall":Hall.cdouble()*self.h_factor, "Sall":Sall.cdouble()}) + else: + leftmost_size = coupling_width['lead_L'] + rightmost_size = coupling_width['lead_R'] + hd, hu, hl, sd, su, sl, subblocks = self.get_block_tridiagonal(HD*self.h_factor,SD.cdouble(),self.structase,\ + leftmost_size,rightmost_size) + HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl}) + + torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) + + torch.set_default_dtype(torch.float32) + return structure_device, structure_leads, subblocks + + def remove_bonds_nonpbc(self,data,pbc): + + for ip,p in enumerate(pbc): + if not p: + mask = abs(data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip])<1e-7 + data[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] + data[AtomicDataDict.EDGE_INDEX_KEY] = data[AtomicDataDict.EDGE_INDEX_KEY][:,mask] + data[AtomicDataDict.EDGE_FEATURES_KEY] = data[AtomicDataDict.EDGE_FEATURES_KEY][mask] + if self.overlap: + data[AtomicDataDict.EDGE_OVERLAP_KEY] = data[AtomicDataDict.EDGE_OVERLAP_KEY][mask] + + def get_lead_structure(self,kk,natom): + stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] + cell = np.array(stru_lead.cell)[:2] + + R_vec = stru_lead[int(natom/2):].positions - stru_lead[:int(natom/2)].positions + assert np.abs(R_vec[0] - R_vec[-1]).sum() < 1e-5 + R_vec = R_vec.mean(axis=0) * 2 + cell = np.concatenate([cell, R_vec.reshape(1,-1)]) + + # get lead structure in ase format + pbc_lead = self.pbc_negf.copy() + pbc_lead[2] = True + stru_lead = Atoms(str(stru_lead.symbols), + positions=stru_lead.positions, + cell=cell, + pbc=pbc_lead) + stru_lead.set_chemical_symbols(stru_lead.get_chemical_symbols()) + return stru_lead + + def get_block_tridiagonal(self,HK,SK,structase:ase.Atoms,leftmost_size:int,rightmost_size:int): + + + # return hd in format: (k_index,block_index, orb, orb) + hd,hu,hl,sd,su,sl = [],[],[],[],[],[] + + if leftmost_size is None: + leftmost_atoms_index = np.where(structase.positions[:,2]==min(structase.positions[:,2]))[0] + leftmost_size = sum([self.h2k.atom_norbs[leftmost_atoms_index[i]] for i in range(len(leftmost_atoms_index))]) + if rightmost_size is None: + rightmost_atoms_index = np.where(structase.positions[:,2]==max(structase.positions[:,2]))[0] + rightmost_size = sum([self.h2k.atom_norbs[rightmost_atoms_index[i]] for i in range(len(rightmost_atoms_index))]) + + subblocks = split_into_subblocks_optimized(HK[0],leftmost_size,rightmost_size) + if subblocks[0] < leftmost_size or subblocks[-1] < rightmost_size: + subblocks = split_into_subblocks(HK[0],leftmost_size,rightmost_size) + log.info(msg="The optimized block tridiagonalization is not successful, \ + the original block tridiagonalization is used.") + subblocks = [0]+subblocks + + + for ik in range(HK.shape[0]): + hd_k,hu_k,hl_k,sd_k,su_k,sl_k = [],[],[],[],[],[] + counted_block = 0 + for id in range(len(subblocks)-1): + counted_block+=subblocks[id] + d_slice = slice(counted_block,counted_block+subblocks[id+1]) + hd_k.append(HK[ik,d_slice,d_slice]) + sd_k.append(SK[ik,d_slice,d_slice]) + if id < len(subblocks)-2: + u_slice = slice(counted_block+subblocks[id+1],counted_block+subblocks[id+1]+subblocks[id+2]) + hu_k.append(HK[ik,d_slice,u_slice]) + su_k.append(SK[ik,d_slice,u_slice]) + if id > 0: + l_slice = slice(counted_block-subblocks[id],counted_block) + hl_k.append(HK[ik,d_slice,l_slice]) + sl_k.append(SK[ik,d_slice,l_slice]) + hd.append(hd_k);hu.append(hu_k);hl.append(hl_k) + sd.append(sd_k);su.append(su_k);sl.append(sl_k) + + + num_diag = sum([i**2 for i in subblocks]) + num_updiag = sum([subblocks[i]*subblocks[i+1] for i in range(len(subblocks)-1)]) + num_lowdiag = num_updiag + num_total = num_diag+num_updiag+num_lowdiag + log.info(msg="The Hamiltonian is block tridiagonalized into {} subblocks.".format(len(hd[0]))) + log.info(msg=" the number of elements in subblocks: {}".format(num_total)) + log.info(msg=" occupation of subblocks: {} %".format(num_total/(HK[0].shape[0]**2)*100)) + + subblocks = subblocks[1:] + show_blocks(subblocks,HK[0],self.results_path) + + return hd, hu, hl, sd, su, sl, subblocks + def get_hs_device(self, kpoint, V, block_tridiagonal=False): """ get the device Hamiltonian and overlap matrix at a specific kpoint @@ -188,23 +419,38 @@ def get_hs_device(self, kpoint, V, block_tridiagonal=False): f = torch.load(os.path.join(self.results_path, "HS_device.pth")) kpoints = f["kpoints"] - ix = None + ik = None for i, k in enumerate(kpoints): if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: - ix = i + ik = i break - assert ix is not None + assert ik is not None - if not block_tridiagonal: - HD, SD = f["HD"][ix], f["SD"][ix] - else: - hd, sd, hl, su, sl, hu = f["hd"][ix], f["sd"][ix], f["hl"][ix], f["su"][ix], f["sl"][ix], f["hu"][ix] + if block_tridiagonal: - return hd, sd, hl, su, sl, hu + # hd format: ( k_index,block_index, orb, orb) + hd_k, sd_k, hl_k, su_k, sl_k, hu_k = f["hd"][ik], f["sd"][ik], f["hl"][ik], f["su"][ik], f["sl"][ik], f["hu"][ik] + + if V.shape == torch.Size([]): + allorb = sum([hd_k[i].shape[0] for i in range(len(hd_k))]) + V = V.repeat(allorb) + V = torch.diag(V).cdouble() + counted = 0 + for i in range(len(hd_k)): # TODO: this part may have probelms when V!=0 + l_slice = slice(counted, counted+hd_k[i].shape[0]) + hd_k[i] = hd_k[i] - V[l_slice,l_slice]@sd_k[i] + if i 0: + hl_k[i-1] = hl_k[i-1] - V[l_slice,l_slice]@sl_k[i-1] + counted += hd_k[i].shape[0] + + return hd_k , sd_k, hl_k , su_k, sl_k, hu_k else: - return [HD - V*SD], [SD], [], [], [], [] + HD_k, SD_k = f["HD"][ik], f["SD"][ik] + return HD_k - V*SD_k, SD_k, [], [], [], [] def get_hs_lead(self, kpoint, tab, v): """get the lead Hamiltonian and overlap matrix at a specific kpoint @@ -226,19 +472,19 @@ def get_hs_lead(self, kpoint, tab, v): f = torch.load(os.path.join(self.results_path, "HS_{0}.pth".format(tab))) kpoints = f["kpoints"] - ix = None + ik = None for i, k in enumerate(kpoints): if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: - ix = i + ik = i break - assert ix is not None + assert ik is not None - hL, hLL, hDL, sL, sLL, sDL = f["HL"][ix], f["HLL"][ix], f["HDL"][ix], \ - f["SL"][ix], f["SLL"][ix], f["SDL"][ix] + hL, hLL, hDL, sL, sLL, sDL = f["HL"][ik], f["HLL"][ik], f["HDL"][ik], \ + f["SL"][ik], f["SLL"][ik], f["SDL"][ik] - return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL + return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL def attach_potential(): pass @@ -251,8 +497,262 @@ def device_norbs(self): """ return the number of atoms in the device Hamiltonian """ - return self.atom_norbs[self.device_id[0]:self.device_id[1]] + return self.h2k.atom_norbs[self.device_id[0]:self.device_id[1]] # def get_hs_block_tridiagonal(self, HD, SD): # return hd, hu, hl, sd, su, sl + + + +# class _NEGFHamiltonianInit(object): +# '''The Class for Hamiltonian object in negf module. + +# It is used to initialize and manipulate device and lead Hamiltonians for negf. +# It is different from the Hamiltonian object in the dptb module. + +# Property +# ---------- +# apiH: the API object for Hamiltonian +# unit: the unit of energy +# structase: the structure object for the device and leads +# stru_options: the options for structure from input file +# results_path: the path to store the results + +# device_id: the start-atom id and end-atom id of the device in the structure file +# lead_ids: the start-atom id and end-atom id of the leads in the structure file + + +# Methods +# ---------- +# initialize: initializes the device and lead Hamiltonians +# get_hs_device: get the device Hamiltonian and overlap matrix at a specific kpoint +# get_hs_lead: get the lead Hamiltonian and overlap matrix at a specific kpoint + +# ''' + +# def __init__(self, apiH, structase, stru_options, results_path) -> None: +# self.apiH = apiH +# self.unit = apiH.unit +# self.structase = structase +# self.stru_options = stru_options +# self.results_path = results_path + +# self.device_id = [int(x) for x in self.stru_options.get("device")["id"].split("-")] +# self.lead_ids = {} +# for kk in self.stru_options: +# if kk.startswith("lead"): +# self.lead_ids[kk] = [int(x) for x in self.stru_options.get(kk)["id"].split("-")] + +# if self.unit == "Hartree": +# self.h_factor = 13.605662285137 * 2 +# elif self.unit == "eV": +# self.h_factor = 1. +# elif self.unit == "Ry": +# self.h_factor = 13.605662285137 +# else: +# log.error("The unit name is not correct !") +# raise ValueError + +# def initialize(self, kpoints, block_tridiagnal=False): +# """initializes the device and lead Hamiltonians + +# construct device and lead Hamiltonians and return the structures respectively.The lead Hamiltonian +# is k-resolved due to the transverse k point sampling. + +# Args: +# kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) +# block_tridiagnal: A boolean parameter that determines whether to block-tridiagonalize the +# device Hamiltonian or not. + +# Returns: +# structure_device and structure_leads corresponding to the structure of device and leads. + +# Raises: +# RuntimeError: if the lead hamiltonian attained from device and lead calculation does not match. + +# """ +# assert len(np.array(kpoints).shape) == 2 + +# HS_device = {} +# HS_leads = {} +# HS_device["kpoints"] = kpoints + +# self.apiH.update_struct(self.structase, mode="device", stru_options=j_must_have(self.stru_options, "device"), pbc=self.stru_options["pbc"]) +# # change parameters to match the structure projection +# n_proj_atom_pre = np.array([1]*len(self.structase))[:self.device_id[0]][self.apiH.structure.projatoms[:self.device_id[0]]].sum() +# n_proj_atom_device = np.array([1]*len(self.structase))[self.device_id[0]:self.device_id[1]][self.apiH.structure.projatoms[self.device_id[0]:self.device_id[1]]].sum() +# proj_device_id = [0,0] +# proj_device_id[0] = n_proj_atom_pre +# proj_device_id[1] = n_proj_atom_pre + n_proj_atom_device +# self.proj_device_id = proj_device_id +# projatoms = self.apiH.structure.projatoms + +# self.atom_norbs = [self.apiH.structure.proj_atomtype_norbs[i] for i in self.apiH.structure.proj_atom_symbols] +# self.apiH.get_HR() +# # output the allbonds and hamil_block for check +# # allbonds,hamil_block,_ =self.apiH.get_HR() +# # torch.save(allbonds, os.path.join(self.results_path, "allbonds"+".pth")) +# # torch.save(hamil_block, os.path.join(self.results_path, "hamil_block"+".pth")) + +# H, S = self.apiH.get_HK(kpoints=kpoints) +# d_start = int(np.sum(self.atom_norbs[:proj_device_id[0]])) +# d_end = int(np.sum(self.atom_norbs)-np.sum(self.atom_norbs[proj_device_id[1]:])) +# HD, SD = H[:,d_start:d_end, d_start:d_end], S[:, d_start:d_end, d_start:d_end] + +# if not block_tridiagnal: +# HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) +# else: +# hd, hu, hl, sd, su, sl = self.get_block_tridiagonal(HD*self.h_factor, SD) +# HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl}) + +# torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) +# structure_device = self.apiH.structure.projected_struct[self.device_id[0]:self.device_id[1]] + +# structure_leads = {} +# for kk in self.stru_options: +# if kk.startswith("lead"): +# HS_leads = {} +# stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] +# # write(os.path.join(self.results_path, "stru_"+kk+".vasp"), stru_lead) +# self.apiH.update_struct(stru_lead, mode="lead", stru_options=self.stru_options.get(kk), pbc=self.stru_options["pbc"]) +# # update lead id +# n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]][projatoms[:self.lead_ids[kk][0]]].sum() +# n_proj_atom_lead = np.array([1]*len(self.structase))[self.lead_ids[kk][0]:self.lead_ids[kk][1]][projatoms[self.lead_ids[kk][0]:self.lead_ids[kk][1]]].sum() +# proj_lead_id = [0,0] +# proj_lead_id[0] = n_proj_atom_pre +# proj_lead_id[1] = n_proj_atom_pre + n_proj_atom_lead + +# l_start = int(np.sum(self.atom_norbs[:proj_lead_id[0]])) +# l_end = int(l_start + np.sum(self.atom_norbs[proj_lead_id[0]:proj_lead_id[1]]) / 2) +# HL, SL = H[:,l_start:l_end, l_start:l_end], S[:, l_start:l_end, l_start:l_end] # lead hamiltonian in one principal layer +# HDL, SDL = H[:,d_start:d_end, l_start:l_end], S[:,d_start:d_end, l_start:l_end] # device and lead's hopping +# HS_leads.update({ +# "HL":HL.cdouble()*self.h_factor, +# "SL":SL.cdouble(), +# "HDL":HDL.cdouble()*self.h_factor, +# "SDL":SDL.cdouble()} +# ) + + +# structure_leads[kk] = self.apiH.structure.struct +# self.apiH.get_HR() +# # output the allbonds and hamil_block for check +# # allbonds_lead,hamil_block_lead,_ = self.apiH.get_HR() +# # torch.save(allbonds_lead, os.path.join(self.results_path, "allbonds_"+kk+".pth")) +# # torch.save(hamil_block_lead, os.path.join(self.results_path, "hamil_block_"+kk+".pth")) + +# h, s = self.apiH.get_HK(kpoints=kpoints) +# nL = int(h.shape[1] / 2) +# HLL, SLL = h[:, :nL, nL:], s[:, :nL, nL:] # H_{L_first2L_second} +# err_l = (h[:, :nL, :nL] - HL).abs().max() +# if err_l >= 1e-4: # check the lead hamiltonian get from device and lead calculation matches each other +# log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") +# raise RuntimeError +# elif 1e-7 <= err_l <= 1e-4: +# log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(err_l)) + +# HS_leads.update({ +# "HLL":HLL.cdouble()*self.h_factor, +# "SLL":SLL.cdouble()} +# ) + +# HS_leads["kpoints"] = kpoints + +# torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) + +# return structure_device, structure_leads + +# def get_hs_device(self, kpoint, V, block_tridiagonal=False): +# """ get the device Hamiltonian and overlap matrix at a specific kpoint + +# In diagonalization mode, the Hamiltonian and overlap matrix are block tridiagonalized, +# and hd,hu,hl refers to the diagnonal, upper and lower blocks of the Hamiltonian, respectively. +# The same rules apply to sd, su, sl. + +# Args: +# kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) +# V: voltage bias +# block_tridiagonal: a boolean flag that shows whether Hamiltonian has been diagonalized or not + +# Returns: +# if not diagonalized, return the whole Hamiltonian and Overlap HD-V*SD, SD +# if diagonalized, return the block tridiagonalized Hamiltonian and Overlap component hd, hu, hl, +# sd, su, sl. +# """ +# f = torch.load(os.path.join(self.results_path, "HS_device.pth")) +# kpoints = f["kpoints"] + +# ix = None +# for i, k in enumerate(kpoints): +# if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: +# ix = i +# break + +# assert ix is not None + +# if not block_tridiagonal: +# HD, SD = f["HD"][ix], f["SD"][ix] +# else: +# hd, sd, hl, su, sl, hu = f["hd"][ix], f["sd"][ix], f["hl"][ix], f["su"][ix], f["sl"][ix], f["hu"][ix] + +# if block_tridiagonal: +# return hd, sd, hl, su, sl, hu +# else: +# # print('HD shape:', HD.shape) +# # print('SD shape:', SD.shape) +# # print('V shape:', V.shape) +# log.info(msg='Device Hamiltonian shape: {0}x{0}'.format(HD.shape[0], HD.shape[1])) + +# return [HD - V*SD], [SD], [], [], [], [] + +# def get_hs_lead(self, kpoint, tab, v): +# """get the lead Hamiltonian and overlap matrix at a specific kpoint + +# In diagonalization mode, the Hamiltonian and overlap matrix are block tridiagonalized, +# and hd,hu,hl refers to the diagnonal, upper and lower blocks of the Hamiltonian, respectively. +# The same rules apply to sd, su, sl. + +# Args: +# kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) +# V: voltage bias +# block_tridiagonal: a boolean flag that shows whether Hamiltonian has been diagonalized or not + +# Returns: +# if not diagonalized, return the whole Hamiltonian and Overlap HD-V*SD, SD +# if diagonalized, return the block tridiagonalized Hamiltonian and Overlap component hd, hu, hl, +# sd, su, sl. +# """ +# f = torch.load(os.path.join(self.results_path, "HS_{0}.pth".format(tab))) +# kpoints = f["kpoints"] + +# ix = None +# for i, k in enumerate(kpoints): +# if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: +# ix = i +# break + +# assert ix is not None + +# hL, hLL, hDL, sL, sLL, sDL = f["HL"][ix], f["HLL"][ix], f["HDL"][ix], \ +# f["SL"][ix], f["SLL"][ix], f["SDL"][ix] + + +# return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL + +# def attach_potential(): +# pass + +# def write(self): +# pass + +# @property +# def device_norbs(self): +# """ +# return the number of atoms in the device Hamiltonian +# """ +# return self.atom_norbs[self.device_id[0]:self.device_id[1]] + +# # def get_hs_block_tridiagonal(self, HD, SD): + +# # return hd, hu, hl, sd, su, sl diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py new file mode 100644 index 00000000..44afdb57 --- /dev/null +++ b/dptb/negf/poisson_init.py @@ -0,0 +1,376 @@ +import numpy as np +# import pyamg #TODO: later add it to optional dependencies,like sisl +# from pyamg.gallery import poisson +from dptb.utils.constants import elementary_charge +from dptb.utils.constants import Boltzmann, eV2J +from scipy.constants import epsilon_0 as eps0 #TODO:later add to untils.constants.py +from scipy.sparse import csr_matrix +from scipy.sparse.linalg import spsolve +import logging +#eps0 = 8.854187817e-12 # in the unit of F/m +# As length in deeptb is in the unit of Angstrom, the unit of eps0 is F/Angstrom +eps0 = eps0*1e-10 # in the unit of F/Angstrom + +log = logging.getLogger(__name__) + +class Grid(object): + # define the grid in 3D space + def __init__(self,xg,yg,zg,xa,ya,za): + # xg,yg,zg are the coordinates of the basic grid points + self.xg = xg + self.yg = yg + self.zg = zg + # xa,ya,za are the coordinates of the atoms + # atom should be within the grid + assert np.min(xa) >= np.min(xg) and np.max(xa) <= np.max(xg) + assert np.min(ya) >= np.min(yg) and np.max(ya) <= np.max(yg) + assert np.min(za) >= np.min(zg) and np.max(za) <= np.max(zg) + + self.Na = len(xa) # number of atoms + uxa = np.unique(xa).round(decimals=6);uya = np.unique(ya).round(decimals=6);uza = np.unique(za).round(decimals=6) + # x,y,z are the coordinates of the grid points + self.xall = np.unique(np.concatenate((uxa,self.xg),0).round(decimals=3)) # unique results are sorted + self.yall = np.unique(np.concatenate((uya,self.yg),0).round(decimals=3)) + self.zall = np.unique(np.concatenate((uza,self.zg),0).round(decimals=3)) + self.shape = (len(self.xall),len(self.yall),len(self.zall)) + + + + # create meshgrid + xmesh,ymesh,zmesh = np.meshgrid(self.xall,self.yall,self.zall) + xmesh = xmesh.flatten() + ymesh = ymesh.flatten() + zmesh = zmesh.flatten() + self.grid_coord = np.array([xmesh,ymesh,zmesh]).T #(Np,3) + sorted_indices = np.lexsort((xmesh,ymesh,zmesh)) + self.grid_coord = self.grid_coord[sorted_indices] # sort the grid points firstly along x, then y, lastly z + ## check the number of grid points + self.Np = int(len(self.xall)*len(self.yall)*len(self.zall)) + assert self.Np == len(xmesh) + assert self.grid_coord.shape[0] == self.Np + + log.info(msg="Number of grid points: {:.1f} Number of atoms: {:.1f}".format(float(self.Np),self.Na)) + # print('Number of grid points: ',self.Np,' grid shape: ',self.grid_coord.shape,' Number of atoms: ',self.Na) + + # find the index of the atoms in the grid + self.atom_index_dict = self.find_atom_index(xa,ya,za) + + + # create surface area for each grid point along x,y,z axis + # each grid point corresponds to a Voronoi cell(box) + surface_grid = np.zeros((self.Np,3)) + x_vorlen = self.cal_vorlen(self.xall);y_vorlen = self.cal_vorlen(self.yall);z_vorlen = self.cal_vorlen(self.zall) + + XD,YD = np.meshgrid(x_vorlen,y_vorlen) + ## surface along x-axis (yz-plane) + ax,bx = np.meshgrid(YD.flatten(),z_vorlen) + surface_grid[:,0] = abs((ax*bx).flatten()) + ## surface along y-axis (xz-plane) + ay,by = np.meshgrid(XD.flatten(),z_vorlen) + surface_grid[:,1] = abs((ay*by).flatten()) + ## surface along z-axis (xy-plane) + az,_ = np.meshgrid((XD*YD).flatten(),self.zall) + surface_grid[:,2] = abs(az.flatten()) + + self.surface_grid = surface_grid # grid points order are the same as that of self.grid_coord + + + def find_atom_index(self,xa,ya,za): + # find the index of the atoms in the grid + swap = {} + for atom_index in range(self.Na): + for gp_index in range(self.Np): + if abs(xa[atom_index]-self.grid_coord[gp_index][0])<1e-3 and \ + abs(ya[atom_index]-self.grid_coord[gp_index][1])<1e-3 and \ + abs(za[atom_index]-self.grid_coord[gp_index][2])<1e-3: + swap.update({atom_index:gp_index}) + return swap + + def cal_vorlen(self,x): + # compute the length of the Voronoi segment of a one-dimensional array x + xd = np.zeros(len(x)) + xd[0] = abs(x[0]-x[1])/2 + xd[-1] = abs(x[-1]-x[-2])/2 + for i in range(1,len(x)-1): + xd[i] = (abs(x[i]-x[i-1])+abs(x[i]-x[i+1]))/2 + return xd + + +class region(object): + def __init__(self,x_range,y_range,z_range): + self.xmin,self.xmax = float(x_range[0]),float(x_range[1]) + self.ymin,self.ymax = float(y_range[0]),float(y_range[1]) + self.zmin,self.zmax = float(z_range[0]),float(z_range[1]) + +class Gate(region): + def __init__(self,x_range,y_range,z_range): + # Gate region + super().__init__(x_range,y_range,z_range) + # Fermi_level of gate (in unit eV) + self.Ef = 0.0 + + +class Dielectric(region): + def __init__(self,x_range,y_range,z_range): + # dielectric region + super().__init__(x_range,y_range,z_range) + # dielectric permittivity + self.eps = 1.0 + + + + +class Interface3D(object): + def __init__(self,grid,gate_list,dielectric_list): + assert grid.__class__.__name__ == 'Grid' + + + for i in range(0,len(gate_list)): + if not gate_list[i].__class__.__name__ == 'Gate': + raise ValueError('Unknown region type in Gate list: ',gate_list[i].__class__.__name__) + for i in range(0,len(dielectric_list)): + if not dielectric_list[i].__class__.__name__ == 'Dielectric': + raise ValueError('Unknown region type in Dielectric list: ',dielectric_list[i].__class__.__name__) + + self.grid = grid + self.eps = np.ones(grid.Np) # dielectric permittivity + self.phi,self.phi_old = np.zeros(grid.Np),np.zeros(grid.Np) # potential + self.free_charge,self.fixed_charge = np.zeros(grid.Np),np.zeros(grid.Np) # free charge density and fixed charge density + + self.Temperature = 300.0 # temperature in unit of Kelvin + self.kBT = Boltzmann*self.Temperature/eV2J # thermal energy in unit of eV + + # store the boundary information: xmin,xmax,ymin,ymax,zmin,zmax,gate + self.boudnary_points = {i:"in" for i in range(self.grid.Np)} # initially set all points as internal + self.boundary_points_get() + + self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially, all grid points are set to zero + self.potential_eps_get(gate_list+dielectric_list) + + + + def boundary_points_get(self): + # set the boundary points + xmin,xmax = np.min(self.grid.xall),np.max(self.grid.xall) + ymin,ymax = np.min(self.grid.yall),np.max(self.grid.yall) + zmin,zmax = np.min(self.grid.zall),np.max(self.grid.zall) + internal_NP = 0 + for i in range(self.grid.Np): + if self.grid.grid_coord[i,0] == xmin: self.boudnary_points[i] = "xmin" + elif self.grid.grid_coord[i,0] == xmax: self.boudnary_points[i] = "xmax" + elif self.grid.grid_coord[i,1] == ymin: self.boudnary_points[i] = "ymin" + elif self.grid.grid_coord[i,1] == ymax: self.boudnary_points[i] = "ymax" + elif self.grid.grid_coord[i,2] == zmin: self.boudnary_points[i] = "zmin" + elif self.grid.grid_coord[i,2] == zmax: self.boudnary_points[i] = "zmax" + else: internal_NP +=1 + + self.internal_NP = internal_NP + + def potential_eps_get(self,region_list): + # set the gate potential + # ingore the lead potential temporarily + gate_point = 0 + for i in range(len(region_list)): + # find gate region in grid + index=np.nonzero((region_list[i].xmin<=self.grid.grid_coord[:,0])& + (region_list[i].xmax>=self.grid.grid_coord[:,0])& + (region_list[i].ymin<=self.grid.grid_coord[:,1])& + (region_list[i].ymax>=self.grid.grid_coord[:,1])& + (region_list[i].zmin<=self.grid.grid_coord[:,2])& + (region_list[i].zmax>=self.grid.grid_coord[:,2]))[0] + if region_list[i].__class__.__name__ == 'Gate': + #attribute gate potential to the corresponding grid points + self.boudnary_points.update({index[i]: "Gate" for i in range(len(index))}) + self.lead_gate_potential[index] = region_list[i].Ef + gate_point += len(index) + elif region_list[i].__class__.__name__ == 'Dielectric': + # attribute dielectric permittivity to the corresponding grid points + self.eps[index] = region_list[i].eps + else: + raise ValueError('Unknown region type: ',region_list[i].__class__.__name__) + + log.info(msg="Number of gate points: {:.1f}".format(float(gate_point))) + + + def to_pyamg_Jac_B(self,dtype=np.float64): + # convert to amg format A,b matrix + # A = poisson(self.grid.shape,format='csr',dtype=dtype) + Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) + B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) + + Jacobian_lil = Jacobian.tolil() + self.NR_construct_Jac_B(Jacobian_lil,B) + Jacobian = Jacobian_lil.tocsr() + return Jacobian,B + + + def to_scipy_Jac_B(self,dtype=np.float64): + # create the Jacobian and B for the Poisson equation in scipy sparse format + + Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) + B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) + + Jacobian_lil = Jacobian.tolil() + self.NR_construct_Jac_B(Jacobian_lil,B) + Jacobian = Jacobian_lil.tocsr() + # self.construct_poisson(A,b) + return Jacobian,B + + + + def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7): + # solve the Poisson equation with Newton-Raphson method + # delta_phi: the correction on the potential + + + norm_delta_phi = 1.0 # Euclidean norm of delta_phi in each step + NR_cycle_step = 0 + + while norm_delta_phi > 1e-3 and NR_cycle_step < 100: + # obtain the Jacobian and B for the Poisson equation + Jacobian,B = self.to_scipy_Jac_B() + norm_B = np.linalg.norm(B) + + if method == 'scipy': + if NR_cycle_step == 0: + log.info(msg="Solve Poisson equation by scipy") + delta_phi = spsolve(Jacobian,B) + elif method == 'pyamg': + if NR_cycle_step == 0: + log.info(msg="Solve Poisson equation by pyamg") + delta_phi = self.solver_pyamg(Jacobian,B,tolerance=1e-5) + else: + raise ValueError('Unknown Poisson solver: ',method) + + max_delta_phi = np.max(abs(delta_phi)) + norm_delta_phi = np.linalg.norm(delta_phi) + self.phi += delta_phi + + if norm_delta_phi > 1e-3: + _,B = self.to_scipy_Jac_B() + norm_B_new = np.linalg.norm(B) + control_count = 1 + # control the norm of B to avoid larger norm_B after one NR cycle + while norm_B_new > norm_B and control_count < 2: + if control_count==1: + log.warning(msg="norm_B increase after this NR cycle, contorler starts!") + self.phi -= delta_phi/np.power(2,control_count) + _,B = self.to_scipy_Jac_B() + norm_B_new = np.linalg.norm(B) + control_count += 1 + log.info(msg=" control_count: {:.1f} norm_B_new: {:.5f}".format(float(control_count),norm_B_new)) + + NR_cycle_step += 1 + log.info(msg=" NR cycle step: {:d} norm_delta_phi: {:.8f} max_delta_phi: {:.8f}".format(int(NR_cycle_step),norm_delta_phi,max_delta_phi)) + + max_diff = np.max(abs(self.phi-self.phi_old)) + return max_diff + + def solver_pyamg(self,A,b,tolerance=1e-7,accel=None): + # solve the Poisson equation + # log.info(msg="Solve Poisson equation by pyamg") + try: + import pyamg + except: + raise ImportError("pyamg is required for Poisson solver. Please install pyamg firstly! ") + + pyamg_solver = pyamg.aggregation.smoothed_aggregation_solver(A, max_levels=1000) + del A + # print('Poisson equation solver: ',pyamg_solver) + residuals = [] + + def callback(x): + # residuals calculated in solver is a pre-conditioned residual + # residuals.append(np.linalg.norm(b - A.dot(x)) ** 0.5) + print( + " {:4d} residual = {:.5e} x0-residual = {:.5e}".format( + len(residuals) - 1, residuals[-1], residuals[-1] / residuals[0] + ) + ) + + x = pyamg_solver.solve( + b, + tol=tolerance, + # callback=callback, + residuals=residuals, + accel=accel, + cycle="W", + maxiter=1e3, + ) + return x + + def NR_construct_Jac_B(self,J,B): + # construct the Jacobian and B for the Poisson equation + + Nx = self.grid.shape[0];Ny = self.grid.shape[1];Nz = self.grid.shape[2] + for gp_index in range(self.grid.Np): + if self.boudnary_points[gp_index] == "in": + flux_xm_J = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index-1]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index,0]-self.grid.grid_coord[gp_index-1,0]) + flux_xm_B = flux_xm_J*(self.phi[gp_index-1]-self.phi[gp_index]) + + flux_xp_J = self.grid.surface_grid[gp_index,0]*eps0*(self.eps[gp_index+1]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index+1,0]-self.grid.grid_coord[gp_index,0]) + flux_xp_B = flux_xp_J*(self.phi[gp_index+1]-self.phi[gp_index]) + + flux_ym_J = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index-Nx]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index-Nx,1]-self.grid.grid_coord[gp_index,1]) + flux_ym_B = flux_ym_J*(self.phi[gp_index-Nx]-self.phi[gp_index]) + + flux_yp_J = self.grid.surface_grid[gp_index,1]*eps0*(self.eps[gp_index+Nx]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index+Nx,1]-self.grid.grid_coord[gp_index,1]) + flux_yp_B = flux_yp_J*(self.phi[gp_index+Nx]-self.phi[gp_index]) + + flux_zm_J = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index-Nx*Ny]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index-Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) + flux_zm_B = flux_zm_J*(self.phi[gp_index-Nx*Ny]-self.phi[gp_index]) + + flux_zp_J = self.grid.surface_grid[gp_index,2]*eps0*(self.eps[gp_index+Nx*Ny]+self.eps[gp_index])*0.5\ + /abs(self.grid.grid_coord[gp_index+Nx*Ny,2]-self.grid.grid_coord[gp_index,2]) + flux_zp_B = flux_zp_J*(self.phi[gp_index+Nx*Ny]-self.phi[gp_index]) + + # add flux term to matrix J + J[gp_index,gp_index] = -(flux_xm_J+flux_xp_J+flux_ym_J+flux_yp_J+flux_zm_J+flux_zp_J)\ + +elementary_charge*self.free_charge[gp_index]*(-np.sign(self.free_charge[gp_index]))/self.kBT*\ + np.exp(-np.sign(self.free_charge[gp_index])*(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT) + J[gp_index,gp_index-1] = flux_xm_J + J[gp_index,gp_index+1] = flux_xp_J + J[gp_index,gp_index-Nx] = flux_ym_J + J[gp_index,gp_index+Nx] = flux_yp_J + J[gp_index,gp_index-Nx*Ny] = flux_zm_J + J[gp_index,gp_index+Nx*Ny] = flux_zp_J + + + # add flux term to matrix B + B[gp_index] = (flux_xm_B+flux_xp_B+flux_ym_B+flux_yp_B+flux_zm_B+flux_zp_B) + B[gp_index] += elementary_charge*self.free_charge[gp_index]*np.exp(-np.sign(self.free_charge[gp_index])\ + *(self.phi[gp_index]-self.phi_old[gp_index])/self.kBT)+elementary_charge*self.fixed_charge[gp_index] + + else:# boundary points + J[gp_index,gp_index] = 1.0 # correct for both Dirichlet and Neumann boundary condition + + if self.boudnary_points[gp_index] == "xmin": + J[gp_index,gp_index+1] = -1.0 + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+1]) + elif self.boudnary_points[gp_index] == "xmax": + J[gp_index,gp_index-1] = -1.0 + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-1]) + elif self.boudnary_points[gp_index] == "ymin": + J[gp_index,gp_index+Nx] = -1.0 + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+Nx]) + elif self.boudnary_points[gp_index] == "ymax": + J[gp_index,gp_index-Nx] = -1.0 + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx]) + elif self.boudnary_points[gp_index] == "zmin": + J[gp_index,gp_index+Nx*Ny] = -1.0 + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index+Nx*Ny]) + elif self.boudnary_points[gp_index] == "zmax": + J[gp_index,gp_index-Nx*Ny] = -1.0 + B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx*Ny]) + elif self.boudnary_points[gp_index] == "Gate": + B[gp_index] = (self.phi[gp_index]+self.lead_gate_potential[gp_index]) + + if B[gp_index]!=0: # for convenience change the sign of B in later NR iteration + B[gp_index] = -B[gp_index] + + \ No newline at end of file diff --git a/dptb/negf/recursive_green_cal.py b/dptb/negf/recursive_green_cal.py index bba73933..bce7e2b3 100644 --- a/dptb/negf/recursive_green_cal.py +++ b/dptb/negf/recursive_green_cal.py @@ -85,25 +85,30 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list, sd, su, sl, s_i gru = [None for _ in range(num_of_matrices-1)] grd = [i.clone() for i in gr_left] # Our glorious benefactor. g_trans = gr_left[len(gr_left) - 1].clone() + gr_lc = [gr_left[len(gr_left) - 1].clone()] for q in range(num_of_matrices - 2, -1, -1): # Recursive algorithm grl[q] = grd[q + 1] @ mat_l_list[q] @ gr_left[q] # (B5) We get the off-diagonal blocks for free. gru[q] = gr_left[q] @ mat_u_list[q] @ grd[q + 1] # (B6) because we need .Tthem.T for the next calc: grd[q] = gr_left[q] + gr_left[q] @ mat_u_list[q] @ grl[q] # (B4) I suppose I could also use the lower. g_trans = gr_left[q] @ mat_u_list[q] @ g_trans - + gr_lc.append(g_trans) + gr_lc.reverse() # ------------------------------------------------------------------- - # ------ compute the electron correlation function if needed -------- + # ------ compute the electron correlation function ( Lesser Green Function ) if needed -------- # ------------------------------------------------------------------- if isinstance(s_in, list): - + gin_left = [None for _ in range(num_of_matrices)] + # Keldysh formula: G^< = G^r * Sigma^< * G^a ====> (-i * G^<) = G^r * (-i * Sigma^<) * G^a gin_left[0] = gr_left[0] @ s_in[0] @ gr_left[0].conj().T for q in range(num_of_matrices - 1): - sla2 = mat_l_list[q] @ gin_left[q] @ mat_u_list[q].conj().T + # sla2: coupling with the left layer + # s_in[q + 1]: coupling directly with the q+1 layer from lead + sla2 = mat_l_list[q] @ gin_left[q] @ mat_u_list[q] prom = s_in[q + 1] + sla2 - gin_left[q + 1] = gr_left[q + 1] @ prom @ gr_left[q + 1].conj().T + gin_left[q + 1] = gr_left[q + 1] @ prom @ gr_left[q + 1].conj().T # --------------------------------------------------------------- @@ -113,12 +118,12 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list, sd, su, sl, s_i for q in range(num_of_matrices - 2, -1, -1): # Recursive algorithm gnl[q] = grd[q + 1] @ mat_l_list[q] @ gin_left[q] + \ - gnd[q + 1] @ mat_l_list[q].conj().T @ gr_left[q].conj().T + gnd[q + 1] @ mat_l_list[q] @ gr_left[q].conj().T # (B10) gnd[q] = gin_left[q] + \ - gr_left[q] @ mat_u_list[q] @ gnd[q + 1] @ mat_l_list[q].conj().T @ \ + gr_left[q] @ mat_u_list[q] @ gnd[q + 1] @ mat_l_list[q] @ \ gr_left[q].conj().T + \ - ((gin_left[q] @ mat_u_list[q].conj().T @ grl[q].conj().T) + (gru[q] @ - mat_l_list[q] @ gin_left[q])) + ((gin_left[q] @ mat_u_list[q] @ gru[q].conj().T) + (gru[q] @ + mat_l_list[q] @ gin_left[q])) # (B11) gnu[q] = gnl[q].conj().T @@ -128,12 +133,12 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list, sd, su, sl, s_i if isinstance(s_out, list): gip_left = [None for _ in range(num_of_matrices)] - gip_left[0] = gr_left[0] @ s_out[0] @ gr_left[0].conj().T + gip_left[0] = gr_left[0] @ s_out[0] @ gr_left[0].conj() for q in range(num_of_matrices - 1): - sla2 = mat_l_list[q] @ gip_left[q] @ mat_u_list[q].conj().T + sla2 = mat_l_list[q] @ gip_left[q] @ mat_u_list[q].conj() prom = s_out[q + 1] + sla2 - gip_left[q + 1] = gr_left[q + 1] @ prom @ gr_left[q + 1].conj().T + gip_left[q + 1] = gr_left[q + 1] @ prom @ gr_left[q + 1].conj() # --------------------------------------------------------------- @@ -143,11 +148,11 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list, sd, su, sl, s_i for q in range(num_of_matrices - 2, -1, -1): # Recursive algorithm gpl[q] = grd[q + 1] @ mat_l_list[q] @ gip_left[q] + \ - gpd[q + 1] @ mat_l_list[q].conj().T @ gr_left[q].conj().T + gpd[q + 1] @ mat_l_list[q].conj() @ gr_left[q].conj() gpd[q] = gip_left[q] + \ - gr_left[q] @ mat_u_list[q] @ gpd[q + 1] @ mat_l_list[q].conj().T @ \ - gr_left[q].conj().T + \ - ((gip_left[q]@ mat_u_list[q].conj().T @ grl[q].conj().T) + (gru[q] @ + gr_left[q] @ mat_u_list[q] @ gpd[q + 1] @ mat_l_list[q].conj() @ \ + gr_left[q].conj()+ \ + ((gip_left[q]@ mat_u_list[q].conj() @ grl[q].conj()) + (gru[q] @ mat_l_list[q] @ gip_left[q])) gpu[0] = gpl[0].conj().T @@ -157,36 +162,36 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list, sd, su, sl, s_i # ------------------------------------------------------------------- for jj, item in enumerate(mat_d_list): - mat_d_list[jj] = mat_d_list[jj] + (energy - 1j * eta) * sd[jj] + mat_d_list[jj] = mat_d_list[jj] + (energy + 1j * eta) * sd[jj] for jj, item in enumerate(mat_l_list): - mat_l_list[jj] = mat_l_list[jj] + (energy - 1j * eta) * sl[jj] + mat_l_list[jj] = mat_l_list[jj] + (energy + 1j * eta) * sl[jj] for jj, item in enumerate(mat_u_list): - mat_u_list[jj] = mat_u_list[jj] + (energy - 1j * eta) * su[jj] + mat_u_list[jj] = mat_u_list[jj] + (energy + 1j * eta) * su[jj] # ------------------------------------------------------------------- # ---- choose a proper output depending on the list of arguments ---- # ------------------------------------------------------------------- if not isinstance(s_in, list) and not isinstance(s_out, list): - return g_trans, \ + return g_trans,gr_lc, \ grd, grl, gru, gr_left, \ None, None, None, None, \ None, None, None, None elif isinstance(s_in, list) and not isinstance(s_out, list): - return g_trans, \ + return g_trans,gr_lc, \ grd, grl, gru, gr_left, \ gnd, gnl, gnu, gin_left, \ None, None, None, None elif not isinstance(s_in, list) and isinstance(s_out, list): - return g_trans, \ + return g_trans,gr_lc, \ grd, grl, gru, gr_left, \ None, None, None, None, \ gpd, gpl, gpu, gip_left else: - return g_trans, \ + return g_trans,gr_lc, \ grd, grl, gru, gr_left, \ gnd, gnl, gnu, gin_left, \ gpd, gpl, gpu, gip_left @@ -211,7 +216,7 @@ def recursive_gf(energy, hl, hd, hu, sd, su, sl, left_se, right_se, seP=None, ch List of upper-diagonal blocks mat_l_list : list of numpy.ndarray (dtype=numpy.float) List of lower-diagonal blocks - s_in : Sigma_in contains self-energy about electron phonon scattering + s_in : Coupling Matrix Gamma from leads to the device (Default value = 0) s_out : (Default value = 0) @@ -249,7 +254,13 @@ def recursive_gf(energy, hl, hd, hu, sd, su, sl, left_se, right_se, seP=None, ch """ shift_energy = energy + chemiPot + # shift_energy = torch.scalar_tensor(shift_energy, dtype=torch.complex128) + # if isinstance(hd,torch.Tensor): # hd, hl, hu are torch.nested.nested_tensor + # temp_mat_d_list = [hd[i] * 1. for i in range(hd.size(0))] + # temp_mat_l_list = [hl[i] * 1. for i in range(hl.size(0))] + # temp_mat_u_list = [hu[i] * 1. for i in range(hu.size(0))] + # else: temp_mat_d_list = [hd[i] * 1. for i in range(len(hd))] temp_mat_l_list = [hl[i] * 1. for i in range(len(hl))] temp_mat_u_list = [hu[i] * 1. for i in range(len(hu))] diff --git a/dptb/negf/sort_btd.py b/dptb/negf/sort_btd.py new file mode 100644 index 00000000..4da72975 --- /dev/null +++ b/dptb/negf/sort_btd.py @@ -0,0 +1,106 @@ +"""This module contains three sorting function: lexicographic sort of atomic coordinates, +sort that uses projections on a vector pointing from one electrode to another as the sorting keys +and sort that uses a potential function over atomic coordinates as the sorting keys. +A user can define his own sorting procedure - the user-defined sorting function should contain +`**kwargs` in the list of arguments and it can uses in its body one of the arguments with following name convention: +`coords` is the list of atomic coordinates, +`left_lead` is the list of the indices of the atoms contacting the left lead, +`right_lead` is the list of the indices of the atoms contacting the right lead, and +`mat` is the adjacency matrix of the tight-binding model. +All functions return the list of sorted atomic indices. +""" +import numpy as np +import matplotlib.pyplot as plt +from scipy.sparse.linalg import lgmres + + +def sort_lexico(coords=None, **kwargs): + """Lexicographic sort + + Parameters + ---------- + coords : array + list of atomic coordinates (Default value = None) + **kwargs : + + + Returns + ------- + + + """ + return np.lexsort((coords[:, 0], coords[:, 1], coords[:, 2])) + + +def sort_projection(coords=None, left_lead=None, right_lead=None, **kwargs): + """Sorting procedure that uses projections on a vector pointing from one electrode to another as the sorting keys. + + Parameters + ---------- + coords : array + list of atomic coordinates (Default value = None) + left_lead : array + list of the atom indices contacting the left lead (Default value = None) + right_lead : array + list of the atom indices contacting the right lead (Default value = None) + **kwargs : + + + Returns + ------- + + + """ + vec = np.mean(coords[left_lead], axis=0) - np.mean(coords[right_lead], axis=0) + keys = np.dot(coords, vec) / np.linalg.norm(vec) + + return np.argsort(keys, kind='mergesort') + + +def sort_capacitance(coords, mat, left_lead, right_lead, **kwargs): + """Sorting procedure that uses a potential function defined over atomic coordinates as the sorting keys. + + Parameters + ---------- + coords : array + list of atomic coordinates + mat : 2D array + adjacency matrix of the tight-binding model + left_lead : array + list of the atom indices contacting the left lead + right_lead : array + list of the atom indices contacting the right lead + **kwargs : + + + Returns + ------- + + + """ + + charge = np.zeros(coords.shape[0], dtype=complex) + charge[left_lead] = 1e3 + charge[right_lead] = -1e3 + + x = coords[:, 1].T + y = coords[:, 0].T + + mat = (mat != 0.0).astype(float) + mat = 10 * (mat - np.diag(np.diag(mat))) + mat = mat - np.diag(np.sum(mat, axis=1)) + 0.001 * np.identity(mat.shape[0]) + + col, info = lgmres(mat, charge.T, x0=1.0 / np.diag(mat), tol=1e-5, maxiter=15) + col = col / np.max(col) + + indices = np.argsort(col, kind='heapsort') + + mat = mat[indices, :] + mat = mat[:, indices] + + plt.scatter(x, y, c=col, cmap=plt.cm.get_cmap('seismic'), s=50, marker="o", edgecolors="k") + plt.colorbar() + plt.axis('off') + plt.show() + + return indices diff --git a/dptb/negf/split_btd.py b/dptb/negf/split_btd.py new file mode 100644 index 00000000..d3cd25b8 --- /dev/null +++ b/dptb/negf/split_btd.py @@ -0,0 +1,1325 @@ +"""This module contains a set of functions facilitating computations of +the block-tridiagonal structure of a band matrix. +""" +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle +from itertools import product +import math +import scipy + + +def accum(accmap, input, func=None, size=None, fill_value=0, dtype=None): + """An accumulation function similar to Matlab's `accumarray` function. + + Parameters + ---------- + accmap : ndarray + This is the "accumulation map". It maps input (i.e. indices into + `a`) to their destination in the output array. The first `a.ndim` + dimensions of `accmap` must be the same as `a.shape`. That is, + `accmap.shape[:a.ndim]` must equal `a.shape`. For example, if `a` + has shape (15,4), then `accmap.shape[:2]` must equal (15,4). In this + case `accmap[i,j]` gives the index into the output array where + element (i,j) of `a` is to be accumulated. If the output is, say, + a 2D, then `accmap` must have shape (15,4,2). The value in the + last dimension give indices into the output array. If the output is + 1D, then the shape of `accmap` can be either (15,4) or (15,4,1) + input : ndarray + The input data to be accumulated. + func : callable or None + The accumulation function. The function will be passed a list + of values from `a` to be accumulated. + If None, numpy.sum is assumed. (Default value = None) + size : ndarray or None + The size of the output array. If None, the size will be determined + from `accmap`. (Default value = None) + fill_value : scalar + The default value for elements of the output array. + dtype : numpy data type, or None + The data type of the output array. If None, the data type of + `a` is used. (Default value = None) + + Returns + ------- + + + """ + + # Check for bad arguments and handle the defaults. + if accmap.shape[:input.ndim] != input.shape: + raise ValueError("The initial dimensions of accmap must be the same as a.shape") + if func is None: + func = np.sum + if dtype is None: + dtype = input.dtype + if accmap.shape == input.shape: + accmap = np.expand_dims(accmap, -1) + adims = tuple(range(input.ndim)) + if size is None: + size = 1 + np.squeeze(np.apply_over_axes(np.max, accmap, axes=adims)) + size = np.atleast_1d(size) + + # Create an array of python lists of values. + vals = np.empty(size, dtype='O') + for s in product(*[range(k) for k in size]): + vals[s] = [] + for s in product(*[range(k) for k in input.shape]): + indx = tuple(accmap[s]) + val = input[s] + vals[indx].append(val) + + # Create the output array. + out = np.empty(size, dtype=dtype) + for s in product(*[range(k) for k in size]): + if vals[s] == []: + out[s] = fill_value + else: + out[s] = func(vals[s]) + + return out + + +def cut_in_blocks(h_0, blocks): + """Cut a matrix into diagonal, upper-diagonal and lower-diagonal blocks + if sizes of the diagonal blocks are specified. + + Parameters + ---------- + h_0 : ndarray + Input matrix + blocks : ndarray(dtype=int) + Sizes of diagonal blocks + + Returns + ------- + h_0_s, h_l_s, h_r_s : ndarray + List of diagonal matrices, + list of lower-diagonal matrices and + list of upper-diagonal matrices. + Note that if the size of the list h_0_s is N, + the sizes of h_l_s, h_r_s are N-1. + + Examples + -------- + >>> import numpy as np + >>> from nanonet.tb.block_tridiagonalization import cut_in_blocks + >>> a = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> a + array([[1, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> # Sum the diagonals. + >>> blocks = [2, 2] + >>> blocks + [2, 2] + >>> h0, h1, h2 = cut_in_blocks(a, blocks) + >>> h0 + [array([[1, 1], + [1, 1]]), array([[1, 1], + [1, 1]])] + >>> h1 + [array([[0, 1], + [0, 0]])] + >>> h2 + [array([[0, 0], + [1, 0]])] + """ + + j1 = 0 + + h_0_s = [] + h_l_s = [] + h_r_s = [] + + for j, block in enumerate(blocks): + h_0_s.append(h_0[j1:block + j1, j1:block + j1]) + if j < len(blocks) - 1: + h_l_s.append(h_0[block + j1:block + j1 + blocks[j + 1], j1:block + j1]) + h_r_s.append(h_0[j1:block + j1, j1 + block:j1 + block + blocks[j + 1]]) + j1 += block + + return h_0_s, h_l_s, h_r_s + + +def find_optimal_cut(edge, edge1, left, right): + """Computes the index corresponding to the optimal cut such that applying + the function compute_blocks() to the sub-blocks defined by the cut reduces + the cost function comparing to the case when the function compute_blocks() is + applied to the whole matrix. If cutting point can not be find, the algorithm returns + the result from the function compute_blocks(). + + Parameters + ---------- + edge : ndarray + sparsity pattern profile of the matrix + edge1 : ndarray + conjugated sparsity pattern profile of the matrix + left : int + size of the leftmost diagonal block + right : int + size of the rightmost diagonal block + + Returns + ------- + + + """ + + unique_indices = np.arange(left, len(edge) - right + 1) + blocks = [] + seps = [] + sizes = [] + metric = [] + size = len(edge) + + for j1, item1 in enumerate(unique_indices): + seps.append(item1) + item2 = size - item1 + + # print(item1, item2) + # print(item1) + + edge_1 = edge[:item1] + edge_2 = (edge1 - np.arange(len(edge1)))[item2:] + np.arange(item1) + + edge_3 = edge1[:item2] + edge_4 = (edge - np.arange(len(edge)))[item1:] + np.arange(item2) + + block1 = compute_blocks(left, (edge1 - np.arange(len(edge)))[item2], + edge_1, edge_2) + + block2 = compute_blocks(right, (edge - np.arange(len(edge1)))[item1], + edge_3, edge_4) + + block = block1 + block2[::-1] + blocks.append(block) + metric.append(np.sum(np.array(block) ** 3)) + sizes.append((block1[-1], block2[-1])) + + if len(metric) == 0: + return [left, right], np.nan, 0, 0 + else: + + best = np.argmin(np.array(metric)) + + blocks = blocks[best] + blocks = [item for item in blocks if item != 0] + + sep = seps[best] + + right_block, left_block = sizes[best] + + return blocks, sep, right_block, left_block + + +def compute_blocks_optimized(edge, edge1, left=1, right=1): + """Computes optimal sizes of diagonal blocks of a matrix whose + sparsity pattern is defined by the sparsity pattern profiles edge and edge1. + This function is based on the algorithm which uses defined above function + find_optimal_cut() to subdivide the problem into sub-problems in a optimal way + according to some cost function. + + Parameters + ---------- + edge : ndarray + sparsity pattern profile of the matrix + edge1 : ndarray + conjugated sparsity pattern profile of the matrix + left : int + size of the leftmost diagonal block (constrained) (Default value = 1) + right : int + size of the rightmost diagonal block (constrained) (Default value = 1) + + Returns + ------- + + + """ + + blocks, sep, right_block, left_block = find_optimal_cut(edge, edge1, left=left, right=right) + flag = False + + if not math.isnan(sep): + + # print(left, right_block, sep) + + if left + right_block < sep: + + edge_1 = edge[:sep] + # edge_1[edge_1 > sep] = sep + edge_2 = (edge1 - np.arange(len(edge1)))[-sep:] + np.arange(sep) + + blocks1 = compute_blocks_optimized(edge_1, edge_2, left=left, right=right_block) + + elif left + right_block == sep: + + blocks1 = [left, right_block] + else: + + flag = True + + # print(left_block, right, len(edge) - sep) + + if right + left_block < len(edge) - sep: + + edge_3 = (edge - np.arange(len(edge)))[sep:] + np.arange(len(edge) - sep) + edge_4 = edge1[:-sep] + # edge_4[edge_4 > len(edge) - sep] = len(edge) - sep + + blocks2 = compute_blocks_optimized(edge_3, edge_4, left=left_block, right=right) + + elif right + left_block == len(edge) - sep: + blocks2 = [left_block, right] + else: + flag = True + + if flag: + return blocks + else: + blocks = blocks1 + blocks2 + + return blocks + + +def find_nonzero_lines(mat, order): + """ + + Parameters + ---------- + mat : + + order : + + + Returns + ------- + + """ + + if scipy.sparse.issparse(mat): + lines = _find_nonzero_lines_sparse(mat, order) + else: + lines = _find_nonzero_lines(mat, order) + + if lines == max(mat.shape[0], mat.shape[1]) - 1: + lines = 1 + if lines == 0: + lines = 1 + + return lines + + +def _find_nonzero_lines(mat, order): + """ + + Parameters + ---------- + mat : + + order : + + + Returns + ------- + + """ + if order == 'top': + line = mat.shape[0] + while line > 0: + if np.count_nonzero(mat[line - 1, :]) == 0: + line -= 1 + else: + break + elif order == 'bottom': + line = -1 + while line < mat.shape[0] - 1: + if np.count_nonzero(mat[line + 1, :]) == 0: + line += 1 + else: + line = mat.shape[0] - (line + 1) + break + elif order == 'left': + line = mat.shape[1] + while line > 0: + if np.count_nonzero(mat[:, line - 1]) == 0: + line -= 1 + else: + break + elif order == 'right': + line = -1 + while line < mat.shape[1] - 1: + if np.count_nonzero(mat[:, line + 1]) == 0: + line += 1 + else: + line = mat.shape[1] - (line + 1) + break + else: + raise ValueError('Wrong value of the parameter order') + + return line + + +def _find_nonzero_lines_sparse(mat, order): + """ + + Parameters + ---------- + mat : + + order : + + + Returns + ------- + + """ + if order == 'top': + line = mat.shape[0] + while line > 0: + if np.count_nonzero(mat[line - 1, :].todense()) == 0: + line -= 1 + else: + break + elif order == 'bottom': + line = -1 + while line < mat.shape[0] - 1: + if np.count_nonzero(mat[line + 1, :].todense()) == 0: + line += 1 + else: + line = mat.shape[0] - (line + 1) + break + elif order == 'left': + line = mat.shape[1] + while line > 0: + if np.count_nonzero(mat[:, line - 1].todense()) == 0: + line -= 1 + else: + break + elif order == 'right': + line = -1 + while line < mat.shape[1] - 1: + if np.count_nonzero(mat[:, line + 1].todense()) == 0: + line += 1 + else: + line = mat.shape[1] - (line + 1) + break + else: + raise ValueError('Wrong value of the parameter order') + + return line + + +def split_into_subblocks_optimized(h_0, left=1, right=1): + """ + + Parameters + ---------- + h_0 : + param left: + right : + return: (Default value = 1) + left : + (Default value = 1) + + Returns + ------- + + """ + + if not (isinstance(left, int) and isinstance(right, int)): + h_r_h = find_nonzero_lines(right, 'bottom') + h_r_v = find_nonzero_lines(right[-h_r_h:, :], 'left') + h_l_h = find_nonzero_lines(left, 'top') + h_l_v = find_nonzero_lines(left[:h_l_h, :], 'right') + left = max(h_l_h, h_r_v) + right = max(h_r_h, h_l_v) + + if left + right > h_0.shape[0]: + return [h_0.shape[0]] + else: + edge, edge1 = compute_edge(h_0) + return compute_blocks_optimized(edge, edge1, left=left, right=right) + + +def split_into_subblocks(h_0, h_l, h_r): + """Split Hamiltonian matrix and coupling matrices into subblocks + + Parameters + ---------- + h_0 : + Hamiltonian matrix + h_l : + left inter-cell coupling matrices + h_r : + right inter-cell coupling matrices + :return h_0_s, h_l_s, h_r_s: lists of subblocks + + Returns + ------- + + """ + + if isinstance(h_l, np.ndarray) and isinstance(h_r, np.ndarray): + h_r_h = find_nonzero_lines(h_r, 'bottom') + h_r_v = find_nonzero_lines(h_r[-h_r_h:, :], 'left') + h_l_h = find_nonzero_lines(h_l, 'top') + h_l_v = find_nonzero_lines(h_l[:h_l_h, :], 'right') + left_block = max(h_l_h, h_r_v) + right_block = max(h_r_h, h_l_v) + elif isinstance(h_l, int) and isinstance(h_r, int): + left_block = h_l + right_block = h_r + else: + raise TypeError + + edge, edge1 = compute_edge(h_0) + + blocks = compute_blocks(left_block, right_block, edge, edge1) + + return blocks + + +def compute_edge(mat): + """Computes edges of the sparsity pattern of a matrix. + + Parameters + ---------- + mat : ndarray + Input matrix + + Returns + ------- + edge : ndarray + edge of the sparsity pattern + edge1 : ndarray + conjugate edge of the sparsity pattern + + Examples + -------- + >>> import numpy as np + >>> from nanonet.tb.block_tridiagonalization import compute_edge + >>> input_matrix = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> e1 + array([2, 3, 4, 4]) + >>> e2 + array([2, 3, 4, 4]) + >>> input_matrix = np.array([[1, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 0, 0, 0], + [0, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> e1 + array([1, 3, 4, 4]) + >>> e2 + array([2, 3, 3, 4]) + """ + + # First get some statistics + if isinstance(mat, scipy.sparse.lil_matrix): + row, col = mat.nonzero() + else: + row, col = np.where(mat != 0.0) # Output rows and columns of all non-zero elements. + + # Clever use of accumarray: + edge = accum(row, col, np.max) + 1 + edge[0] = max(0, edge[0]) + edge = np.maximum.accumulate(edge) + + edge1 = accum(np.max(row) - row[::-1], np.max(row) - col[::-1], np.max) + 1 + edge1[0] = max(0, edge1[0]) + edge1 = np.maximum.accumulate(edge1) + + return edge, edge1 + + +def compute_blocks(left_block, right_block, edge, edge1): + """This is an implementation of the greedy algorithm for + computing block-tridiagonal representation of a matrix. + The information regarding the input matrix is represented + by the sparsity patters edges, `edge` and `edge1`. + + Parameters + ---------- + left_block : int + a predefined size of the leftmost block + right_block : int + a predefined size of the rightmost block + edge : ndarray + edge of sparsity pattern + edge1 : ndarray + conjugate edge of sparsity pattern + + Returns + ------- + ans : list + + + Examples + -------- + >>> import numpy as np + >>> from nanonet.tb.block_tridiagonalization import compute_edge + >>> input_matrix = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> compute_blocks(1, 1, e1, e2) + [1, 1, 1, 1] + >>> input_matrix = np.array([[1, 1, 1, 0], [1, 1, 1, 0], [1, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 1, 1, 0], + [1, 1, 1, 0], + [1, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> compute_blocks(1, 1, e1, e2) + [1, 2, 1] + >>> e1, e2 = compute_edge(input_matrix) + >>> compute_blocks(2, 2, e1, e2) + [2, 2] + """ + + size = len(edge) + left_block = max(1, left_block) + right_block = max(1, right_block) + + if left_block + right_block < size: # if blocks do not overlap + + new_left_block = edge[left_block - 1] - left_block + new_right_block = edge1[right_block - 1] - right_block + # + # new_right_block = np.max(np.argwhere(np.abs(edge - (size - right_block)) - + # np.min(np.abs(edge - (size - right_block))) == 0)) + 1 + # new_right_block = size - new_right_block - right_block + + if left_block + new_left_block <= size - right_block and \ + size - right_block - new_right_block >= left_block: # spacing between blocks is sufficient + + blocks = compute_blocks(new_left_block, + new_right_block, + edge[left_block:-right_block] - left_block, + edge1[right_block:-left_block] - right_block) + + return [left_block] + blocks + [right_block] + else: + if new_left_block > new_right_block: + return [left_block] + [size - left_block] + else: + return [size - right_block] + [right_block] + + elif left_block + right_block == size: # sum of blocks equal to the matrix size + return [left_block] + [right_block] + else: # blocks overlap + return [size] + + +"""This module contains a set of functions facilitating computations of +the block-tridiagonal structure of a band matrix. +""" +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle +from itertools import product +import math +import scipy + + +def accum(accmap, input, func=None, size=None, fill_value=0, dtype=None): + """An accumulation function similar to Matlab's `accumarray` function. + + Parameters + ---------- + accmap : ndarray + This is the "accumulation map". It maps input (i.e. indices into + `a`) to their destination in the output array. The first `a.ndim` + dimensions of `accmap` must be the same as `a.shape`. That is, + `accmap.shape[:a.ndim]` must equal `a.shape`. For example, if `a` + has shape (15,4), then `accmap.shape[:2]` must equal (15,4). In this + case `accmap[i,j]` gives the index into the output array where + element (i,j) of `a` is to be accumulated. If the output is, say, + a 2D, then `accmap` must have shape (15,4,2). The value in the + last dimension give indices into the output array. If the output is + 1D, then the shape of `accmap` can be either (15,4) or (15,4,1) + input : ndarray + The input data to be accumulated. + func : callable or None + The accumulation function. The function will be passed a list + of values from `a` to be accumulated. + If None, numpy.sum is assumed. (Default value = None) + size : ndarray or None + The size of the output array. If None, the size will be determined + from `accmap`. (Default value = None) + fill_value : scalar + The default value for elements of the output array. + dtype : numpy data type, or None + The data type of the output array. If None, the data type of + `a` is used. (Default value = None) + + Returns + ------- + + + """ + + # Check for bad arguments and handle the defaults. + if accmap.shape[:input.ndim] != input.shape: + raise ValueError("The initial dimensions of accmap must be the same as a.shape") + if func is None: + func = np.sum + if dtype is None: + dtype = input.dtype + if accmap.shape == input.shape: + accmap = np.expand_dims(accmap, -1) + adims = tuple(range(input.ndim)) + if size is None: + size = 1 + np.squeeze(np.apply_over_axes(np.max, accmap, axes=adims)) + size = np.atleast_1d(size) + + # Create an array of python lists of values. + vals = np.empty(size, dtype='O') + for s in product(*[range(k) for k in size]): + vals[s] = [] + for s in product(*[range(k) for k in input.shape]): + indx = tuple(accmap[s]) + val = input[s] + vals[indx].append(val) + + # Create the output array. + out = np.empty(size, dtype=dtype) + for s in product(*[range(k) for k in size]): + if vals[s] == []: + out[s] = fill_value + else: + out[s] = func(vals[s]) + + return out + + +def cut_in_blocks(h_0, blocks): + """Cut a matrix into diagonal, upper-diagonal and lower-diagonal blocks + if sizes of the diagonal blocks are specified. + + Parameters + ---------- + h_0 : ndarray + Input matrix + blocks : ndarray(dtype=int) + Sizes of diagonal blocks + + Returns + ------- + h_0_s, h_l_s, h_r_s : ndarray + List of diagonal matrices, + list of lower-diagonal matrices and + list of upper-diagonal matrices. + Note that if the size of the list h_0_s is N, + the sizes of h_l_s, h_r_s are N-1. + + Examples + -------- + >>> import numpy as np + >>> from nanonet.tb.block_tridiagonalization import cut_in_blocks + >>> a = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> a + array([[1, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> # Sum the diagonals. + >>> blocks = [2, 2] + >>> blocks + [2, 2] + >>> h0, h1, h2 = cut_in_blocks(a, blocks) + >>> h0 + [array([[1, 1], + [1, 1]]), array([[1, 1], + [1, 1]])] + >>> h1 + [array([[0, 1], + [0, 0]])] + >>> h2 + [array([[0, 0], + [1, 0]])] + """ + + j1 = 0 + + h_0_s = [] + h_l_s = [] + h_r_s = [] + + for j, block in enumerate(blocks): + h_0_s.append(h_0[j1:block + j1, j1:block + j1]) + if j < len(blocks) - 1: + h_l_s.append(h_0[block + j1:block + j1 + blocks[j + 1], j1:block + j1]) + h_r_s.append(h_0[j1:block + j1, j1 + block:j1 + block + blocks[j + 1]]) + j1 += block + + return h_0_s, h_l_s, h_r_s + + +def find_optimal_cut(edge, edge1, left, right): + """Computes the index corresponding to the optimal cut such that applying + the function compute_blocks() to the sub-blocks defined by the cut reduces + the cost function comparing to the case when the function compute_blocks() is + applied to the whole matrix. If cutting point can not be find, the algorithm returns + the result from the function compute_blocks(). + + Parameters + ---------- + edge : ndarray + sparsity pattern profile of the matrix + edge1 : ndarray + conjugated sparsity pattern profile of the matrix + left : int + size of the leftmost diagonal block + right : int + size of the rightmost diagonal block + + Returns + ------- + + + """ + + unique_indices = np.arange(left, len(edge) - right + 1) + blocks = [] + seps = [] + sizes = [] + metric = [] + size = len(edge) + + for j1, item1 in enumerate(unique_indices): + seps.append(item1) + item2 = size - item1 + + # print(item1, item2) + # print(item1) + + edge_1 = edge[:item1] + edge_2 = (edge1 - np.arange(len(edge1)))[item2:] + np.arange(item1) + + edge_3 = edge1[:item2] + edge_4 = (edge - np.arange(len(edge)))[item1:] + np.arange(item2) + + block1 = compute_blocks(left, (edge1 - np.arange(len(edge)))[item2], + edge_1, edge_2) + + block2 = compute_blocks(right, (edge - np.arange(len(edge1)))[item1], + edge_3, edge_4) + + block = block1 + block2[::-1] + blocks.append(block) + metric.append(np.sum(np.array(block) ** 3)) + sizes.append((block1[-1], block2[-1])) + + if len(metric) == 0: + return [left, right], np.nan, 0, 0 + else: + + best = np.argmin(np.array(metric)) + + blocks = blocks[best] + blocks = [item for item in blocks if item != 0] + + sep = seps[best] + + right_block, left_block = sizes[best] + + return blocks, sep, right_block, left_block + + +def compute_blocks_optimized(edge, edge1, left=1, right=1): + """Computes optimal sizes of diagonal blocks of a matrix whose + sparsity pattern is defined by the sparsity pattern profiles edge and edge1. + This function is based on the algorithm which uses defined above function + find_optimal_cut() to subdivide the problem into sub-problems in a optimal way + according to some cost function. + + Parameters + ---------- + edge : ndarray + sparsity pattern profile of the matrix + edge1 : ndarray + conjugated sparsity pattern profile of the matrix + left : int + size of the leftmost diagonal block (constrained) (Default value = 1) + right : int + size of the rightmost diagonal block (constrained) (Default value = 1) + + Returns + ------- + + + """ + + blocks, sep, right_block, left_block = find_optimal_cut(edge, edge1, left=left, right=right) + flag = False + + if not math.isnan(sep): + + # print(left, right_block, sep) + + if left + right_block < sep: + + edge_1 = edge[:sep] + # edge_1[edge_1 > sep] = sep + edge_2 = (edge1 - np.arange(len(edge1)))[-sep:] + np.arange(sep) + + blocks1 = compute_blocks_optimized(edge_1, edge_2, left=left, right=right_block) + + elif left + right_block == sep: + + blocks1 = [left, right_block] + else: + + flag = True + + # print(left_block, right, len(edge) - sep) + + if right + left_block < len(edge) - sep: + + edge_3 = (edge - np.arange(len(edge)))[sep:] + np.arange(len(edge) - sep) + edge_4 = edge1[:-sep] + # edge_4[edge_4 > len(edge) - sep] = len(edge) - sep + + blocks2 = compute_blocks_optimized(edge_3, edge_4, left=left_block, right=right) + + elif right + left_block == len(edge) - sep: + blocks2 = [left_block, right] + else: + flag = True + + if flag: + return blocks + else: + blocks = blocks1 + blocks2 + + return blocks + + +def find_nonzero_lines(mat, order): + """ + + Parameters + ---------- + mat : + + order : + + + Returns + ------- + + """ + + if scipy.sparse.issparse(mat): + lines = _find_nonzero_lines_sparse(mat, order) + else: + lines = _find_nonzero_lines(mat, order) + + if lines == max(mat.shape[0], mat.shape[1]) - 1: + lines = 1 + if lines == 0: + lines = 1 + + return lines + + +def _find_nonzero_lines(mat, order): + """ + + Parameters + ---------- + mat : + + order : + + + Returns + ------- + + """ + if order == 'top': + line = mat.shape[0] + while line > 0: + if np.count_nonzero(mat[line - 1, :]) == 0: + line -= 1 + else: + break + elif order == 'bottom': + line = -1 + while line < mat.shape[0] - 1: + if np.count_nonzero(mat[line + 1, :]) == 0: + line += 1 + else: + line = mat.shape[0] - (line + 1) + break + elif order == 'left': + line = mat.shape[1] + while line > 0: + if np.count_nonzero(mat[:, line - 1]) == 0: + line -= 1 + else: + break + elif order == 'right': + line = -1 + while line < mat.shape[1] - 1: + if np.count_nonzero(mat[:, line + 1]) == 0: + line += 1 + else: + line = mat.shape[1] - (line + 1) + break + else: + raise ValueError('Wrong value of the parameter order') + + return line + + +def _find_nonzero_lines_sparse(mat, order): + """ + + Parameters + ---------- + mat : + + order : + + + Returns + ------- + + """ + if order == 'top': + line = mat.shape[0] + while line > 0: + if np.count_nonzero(mat[line - 1, :].todense()) == 0: + line -= 1 + else: + break + elif order == 'bottom': + line = -1 + while line < mat.shape[0] - 1: + if np.count_nonzero(mat[line + 1, :].todense()) == 0: + line += 1 + else: + line = mat.shape[0] - (line + 1) + break + elif order == 'left': + line = mat.shape[1] + while line > 0: + if np.count_nonzero(mat[:, line - 1].todense()) == 0: + line -= 1 + else: + break + elif order == 'right': + line = -1 + while line < mat.shape[1] - 1: + if np.count_nonzero(mat[:, line + 1].todense()) == 0: + line += 1 + else: + line = mat.shape[1] - (line + 1) + break + else: + raise ValueError('Wrong value of the parameter order') + + return line + + +def split_into_subblocks_optimized(h_0, left=1, right=1): + """ + + Parameters + ---------- + h_0 : + param left: + right : + return: (Default value = 1) + left : + (Default value = 1) + + Returns + ------- + + """ + + if not (isinstance(left, int) and isinstance(right, int)): + h_r_h = find_nonzero_lines(right, 'bottom') + h_r_v = find_nonzero_lines(right[-h_r_h:, :], 'left') + h_l_h = find_nonzero_lines(left, 'top') + h_l_v = find_nonzero_lines(left[:h_l_h, :], 'right') + left = max(h_l_h, h_r_v) + right = max(h_r_h, h_l_v) + + if left + right > h_0.shape[0]: + return [h_0.shape[0]] + else: + edge, edge1 = compute_edge(h_0) + return compute_blocks_optimized(edge, edge1, left=left, right=right) + + +def split_into_subblocks(h_0, h_l, h_r): + """Split Hamiltonian matrix and coupling matrices into subblocks + + Parameters + ---------- + h_0 : + Hamiltonian matrix + h_l : + left inter-cell coupling matrices + h_r : + right inter-cell coupling matrices + :return h_0_s, h_l_s, h_r_s: lists of subblocks + + Returns + ------- + + """ + + if isinstance(h_l, np.ndarray) and isinstance(h_r, np.ndarray): + h_r_h = find_nonzero_lines(h_r, 'bottom') + h_r_v = find_nonzero_lines(h_r[-h_r_h:, :], 'left') + h_l_h = find_nonzero_lines(h_l, 'top') + h_l_v = find_nonzero_lines(h_l[:h_l_h, :], 'right') + left_block = max(h_l_h, h_r_v) + right_block = max(h_r_h, h_l_v) + elif isinstance(h_l, int) and isinstance(h_r, int): + left_block = h_l + right_block = h_r + else: + raise TypeError + + edge, edge1 = compute_edge(h_0) + + blocks = compute_blocks(left_block, right_block, edge, edge1) + + return blocks + + +def compute_edge(mat): + """Computes edges of the sparsity pattern of a matrix. + + Parameters + ---------- + mat : ndarray + Input matrix + + Returns + ------- + edge : ndarray + edge of the sparsity pattern + edge1 : ndarray + conjugate edge of the sparsity pattern + + Examples + -------- + >>> import numpy as np + >>> from nanonet.tb.block_tridiagonalization import compute_edge + >>> input_matrix = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> e1 + array([2, 3, 4, 4]) + >>> e2 + array([2, 3, 4, 4]) + >>> input_matrix = np.array([[1, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 0, 0, 0], + [0, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> e1 + array([1, 3, 4, 4]) + >>> e2 + array([2, 3, 3, 4]) + """ + + # First get some statistics + if isinstance(mat, scipy.sparse.lil_matrix): + row, col = mat.nonzero() + else: + row, col = np.where(mat != 0.0) # Output rows and columns of all non-zero elements. + + # Clever use of accumarray: + edge = accum(row, col, np.max) + 1 + edge[0] = max(0, edge[0]) + edge = np.maximum.accumulate(edge) + + edge1 = accum(np.max(row) - row[::-1], np.max(row) - col[::-1], np.max) + 1 + edge1[0] = max(0, edge1[0]) + edge1 = np.maximum.accumulate(edge1) + + return edge, edge1 + + +def compute_blocks(left_block, right_block, edge, edge1): + """This is an implementation of the greedy algorithm for + computing block-tridiagonal representation of a matrix. + The information regarding the input matrix is represented + by the sparsity patters edges, `edge` and `edge1`. + + Parameters + ---------- + left_block : int + a predefined size of the leftmost block + right_block : int + a predefined size of the rightmost block + edge : ndarray + edge of sparsity pattern + edge1 : ndarray + conjugate edge of sparsity pattern + + Returns + ------- + ans : list + + + Examples + -------- + >>> import numpy as np + >>> from nanonet.tb.block_tridiagonalization import compute_edge + >>> input_matrix = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> compute_blocks(1, 1, e1, e2) + [1, 1, 1, 1] + >>> input_matrix = np.array([[1, 1, 1, 0], [1, 1, 1, 0], [1, 1, 1, 1], [0, 0, 1, 1]]) + >>> input_matrix + array([[1, 1, 1, 0], + [1, 1, 1, 0], + [1, 1, 1, 1], + [0, 0, 1, 1]]) + >>> e1, e2 = compute_edge(input_matrix) + >>> compute_blocks(1, 1, e1, e2) + [1, 2, 1] + >>> e1, e2 = compute_edge(input_matrix) + >>> compute_blocks(2, 2, e1, e2) + [2, 2] + """ + + size = len(edge) + left_block = max(1, left_block) + right_block = max(1, right_block) + + if left_block + right_block < size: # if blocks do not overlap + + new_left_block = edge[left_block - 1] - left_block + new_right_block = edge1[right_block - 1] - right_block + # + # new_right_block = np.max(np.argwhere(np.abs(edge - (size - right_block)) - + # np.min(np.abs(edge - (size - right_block))) == 0)) + 1 + # new_right_block = size - new_right_block - right_block + + if left_block + new_left_block <= size - right_block and \ + size - right_block - new_right_block >= left_block: # spacing between blocks is sufficient + + blocks = compute_blocks(new_left_block, + new_right_block, + edge[left_block:-right_block] - left_block, + edge1[right_block:-left_block] - right_block) + + return [left_block] + blocks + [right_block] + else: + if new_left_block > new_right_block: + return [left_block] + [size - left_block] + else: + return [size - right_block] + [right_block] + + elif left_block + right_block == size: # sum of blocks equal to the matrix size + return [left_block] + [right_block] + else: # blocks overlap + return [size] + + +def show_blocks(subblocks, input_mat, results_path): + """This is a script for visualizing the sparsity pattern and + a block-tridiagonal structure of a matrix. + + Parameters + ---------- + subblocks : + + input_mat : + + + Returns + ------- + + + """ + + cumsum = np.cumsum(np.array(subblocks))[:-1] + cumsum = np.insert(cumsum, 0, 0) + + fig, ax = plt.subplots(1) + plt.spy(input_mat, markersize=0.9, c='k') + # plt.plot(edge) + + for jj in range(2): + cumsum = cumsum + jj * input_mat.shape[0] + + if jj == 1: + rect = Rectangle((input_mat.shape[0] - subblocks[-1] - 0.5, input_mat.shape[1] - 0.5), + subblocks[-1], subblocks[0], + linestyle='--', + linewidth=1.3, + edgecolor='b', + facecolor='none', zorder=200) + ax.add_patch(rect) + rect = Rectangle((input_mat.shape[0] - 0.5, input_mat.shape[1] - subblocks[-1] - 0.5), + subblocks[0], subblocks[-1], + linestyle='--', + linewidth=1.3, + edgecolor='g', + facecolor='none', zorder=200) + ax.add_patch(rect) + + for j, item in enumerate(cumsum): + if j < len(cumsum) - 1: + rect = Rectangle((item - 0.5, cumsum[j + 1] - 0.5), subblocks[j], subblocks[j + 1], + linewidth=1.3, + edgecolor='b', + facecolor='none', zorder=200) + ax.add_patch(rect) + rect = Rectangle((cumsum[j + 1] - 0.5, item - 0.5), subblocks[j + 1], subblocks[j], + linewidth=1.3, + edgecolor='g', + facecolor='none', zorder=200) + ax.add_patch(rect) + rect = Rectangle((item - 0.5, item - 0.5), subblocks[j], subblocks[j], + linewidth=1.3, + edgecolor='r', + facecolor='none', zorder=200) + ax.add_patch(rect) + + plt.xlim(input_mat.shape[0] - 0.5, -1.0) + plt.ylim(-1.0, input_mat.shape[0] - 0.5) + plt.axis('off') + plt.savefig(results_path +'/subblocks.png', dpi=300) + + +# if __name__ == "__main__": +# import doctest + +# doctest.testmod() diff --git a/dptb/negf/surface_green.py b/dptb/negf/surface_green.py index 6f560f18..4530e236 100644 --- a/dptb/negf/surface_green.py +++ b/dptb/negf/surface_green.py @@ -24,7 +24,8 @@ class SurfaceGreen(torch.autograd.Function): def forward(ctx, H, h01, S, s01, ee, method='Lopez-Sancho'): # ''' # gs = [A_l - A_{l,l-1} gs A_{l-1,l}]^{-1} - # + # H : HL + # h01 : HLL # 1. ee can be a list, to handle a batch of samples # ''' @@ -200,7 +201,8 @@ def selfEnergy(hL, hLL, sL, sLL, ee, hDL=None, sDL=None, etaLead=1e-8, Bulk=Fals else: a, b = hDL.shape SGF = SurfaceGreen.apply(hL, hLL, sL, sLL, eeshifted + 1j * etaLead, method) - Sig = (ee*sDL-hDL) @ SGF[:b,:b] @ (ee*sDL.conj().T-hDL.conj().T) + #SGF = iterative_simple(eeshifted + 1j * etaLead, hL, hLL, sL, sLL, iter_max=1000) + Sig = (eeshifted*sDL-hDL) @ SGF[:b,:b] @ (eeshifted*sDL.conj().T-hDL.conj().T) return Sig, SGF # R(nuo, nuo) @@ -288,3 +290,20 @@ def iterative_gf(ee, gs, h00, h01, s00, s01, iter=1): gs = tLA.pinv(gs) return gs + +def iterative_simple(ee, h00, h01, s00, s01, iter_max=1000): + gs = torch.linalg.inv(ee*s00 - h00) + diff_gs = 1 + iter = 0 + while diff_gs > 1e-8: + iter +=1 + gs = ee*s00 - h00 - (ee * s01 - h01) @ gs @ (ee * s01.conj().T - h01.conj().T) + # gs = tLA.pinv(gs) + gs = torch.linalg.inv(gs) + diff_gs = \ + torch.max(torch.abs(gs - torch.inverse(ee * s00 - h00 - torch.mm(h01 - ee * s01, gs).mm(h01.conj().T - ee * s01.conj().T)))) + if iter > iter_max: + log.warning("iterative_simple not converged after 1000 iteration.") + break + + return gs \ No newline at end of file diff --git a/dptb/negf/try_bloch.ipynb b/dptb/negf/try_bloch.ipynb new file mode 100644 index 00000000..bbcbfaa4 --- /dev/null +++ b/dptb/negf/try_bloch.ipynb @@ -0,0 +1,650 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Atoms(symbols='Au36', pbc=True, cell=[8.8188394545, 8.8188394545, 8.314432480000008])\n" + ] + } + ], + "source": [ + "import ase.io\n", + "import spglib\n", + "\n", + "# Read the structure from a file (replace 'structure.cif' with your file)\n", + "structure = ase.io.read('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/Au_lead.vasp')\n", + "\n", + "\n", + "# Convert the ASE atoms object to a spglib compatible format\n", + "cell = (\n", + " structure.get_cell(), # Lattice vectors\n", + " structure.get_scaled_positions(), # Atomic positions\n", + " structure.get_atomic_numbers() # Atomic numbers\n", + ")\n", + "\n", + "# Get the conventional cell using spglib\n", + "standardized_cell = spglib.standardize_cell(cell, to_primitive=False, no_idealize=True)\n", + "standardized_cell2 = spglib.standardize_cell(standardized_cell, to_primitive=False, no_idealize=True)\n", + "# Create an ASE Atoms object from the standardized cell\n", + "conv_atoms = ase.Atoms(\n", + " numbers=standardized_cell[2],\n", + " scaled_positions=standardized_cell[1],\n", + " cell=standardized_cell[0],\n", + " pbc=True\n", + ")\n", + "\n", + "# Print the conventional cell\n", + "print(conv_atoms)\n", + "\n", + "ase.io.write('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/Au_lead_conv.vasp', conv_atoms)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Atoms(symbols='Au36', pbc=True, cell=[8.8188394545, 8.8188394545, 8.314432480000008])\n" + ] + } + ], + "source": [ + "import ase.io\n", + "import spglib\n", + "import numpy as np\n", + "\n", + "# Read the structure from a file (replace 'structure.cif' with your file)\n", + "structure = ase.io.read('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/Au_lead_2PL.vasp')\n", + "\n", + "# Extract the cell and atomic positions\n", + "cell = structure.get_cell()\n", + "positions = structure.get_positions()\n", + "atomic_numbers = structure.get_atomic_numbers()\n", + "\n", + "# Extract lattice vectors and positions\n", + "a, b, c = cell\n", + "scaled_positions = structure.get_scaled_positions()\n", + "\n", + "# Create a new cell with the z-direction fixed\n", + "fixed_z_cell = np.array([\n", + " [a[0], a[1], 0],\n", + " [b[0], b[1], 0],\n", + " [0, 0, c[2]]\n", + "])\n", + "\n", + "# Create a spglib-compatible cell tuple\n", + "spglib_cell = (fixed_z_cell, scaled_positions, atomic_numbers)\n", + "\n", + "# Get the smallest cell using spglib\n", + "smallest_cell = spglib.standardize_cell(spglib_cell, to_primitive=True, no_idealize=False)\n", + "\n", + "# Convert the smallest cell back to ASE Atoms object\n", + "smallest_cell_ase = ase.Atoms(\n", + " numbers=smallest_cell[2],\n", + " scaled_positions=smallest_cell[1],\n", + " cell=smallest_cell[0],\n", + " pbc=True\n", + ")\n", + "\n", + "# Extract the reduced xy-plane lattice vectors and original z-direction\n", + "xy_cell = smallest_cell_ase.get_cell()\n", + "xy_cell[2] = cell[2] # Keep the original z-direction\n", + "\n", + "# Set the new cell with fixed z-direction\n", + "smallest_cell_ase.set_cell(xy_cell, scale_atoms=True)\n", + "\n", + "# Print the new smallest periodic cell\n", + "print(smallest_cell_ase)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": {}, + "outputs": [], + "source": [ + "# Read the structure from a file (replace 'structure.cif' with your file)\n", + "import ase\n", + "from ase.build import sort\n", + "structure = ase.io.read('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/Au_lead_2PL.vasp')\n", + "cell = structure.get_cell()\n", + "positions = structure.get_positions()\n", + "atomic_numbers = structure.get_atomic_numbers()\n", + "\n", + "bloch_factor = [6,6,1]\n", + "# Create a new cell with the z-direction fixed\n", + "fixed_z_cell = np.array([\n", + " [cell[0][0]/bloch[0], 0, 0],\n", + " [0, cell[1][1]/bloch[1], 0],\n", + " [0, 0, cell[2][2]]\n", + "])\n", + "\n", + "new_positions = []\n", + "new_atomic_numbers = []\n", + "\n", + "delta = 1e-4\n", + "for ip,pos in enumerate(positions):\n", + " if pos[0])" + ] + }, + "execution_count": 85, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "abs(Sig_sumup_sorted[-10:,-10:].real-Sig_all[-10:,-10:].real).max()" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor(3.3628e-05, dtype=torch.float64, grad_fn=)" + ] + }, + "execution_count": 86, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "abs(Sig_all-Sig_sumup_sorted).max()" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor(3.5893e-14, dtype=torch.float64, grad_fn=)" + ] + }, + "execution_count": 87, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#在Nx=72时误差显著放大,代码里显然有bug\n", + "Nx=73\n", + "abs(Sig_all[:Nx,:Nx]-Sig_sumup_sorted[:Nx,:Nx]).max()\n", + "# Sig_all[17:Nx,17:Nx]-Sig_sumup[17:Nx,17:Nx]" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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7ne/U0VFRbbrR48eVT179lQrVqxQSil10003qe9+97u2e9auXasAqObm5qT6JntokrpNAshJwum2HwzG7pM5nSWcya1MIEDiwfHK1NXpfgBKVVTQXlgwGFsPu95zfRUV9utmPQDVw3ter7yi89evp9d773Vvx1lPMKjLmPW011/eBzTHv3Wr/bs3X53tuu1HJbM/lkxya8/tc7z6nf+GEu2PZbptj+Pukj20/v37o6ysDK+99lpbXjAYxKZNmzBq1CgAwKhRoxAIBLBly5a2e9auXQvLsjBy5Mi2e9avX4+jR4+23bNmzRqcc845OPXUUzPZZUE48amspGW+U06x53MQRwDgfW0O0QHoV/O6WYbx+ehwtLPML3+pIysD+oAyB4l0qnf4fDqP6zPFgd00Ff1+0jUEKPQJM2gQtTd7dmw7bvXk51MZsx6+L15/zbFxf/m7du4xOfedzOTcXzPhvHjLgO253bdX1rn0Z/aLyzvznftZzjKptu12PdVxx8GzQQuFQqirq0NdXR0AcgSpq6tDQ0MDevTogRkzZuCxxx7DqlWrsG3bNkyZMgXl5eWYcOwcyuDBgzFu3Djccccd2Lx5M95++21Mnz4dkyZNQvkxtebvf//7yMnJwW233YYdO3bgj3/8I379619jJm92C4Kg2b6d9qt69iQnB0DHE9uzhz7zXld+vlab57hTfN1ZhsnNBR59NLZM374kI1VeTsuBS5boa+xkAQBPP63zWC6L+wmQgj5ADzDn/lQ4rA31c8/pfL+f2nv+eaqXjamzHq67tVUbLK4nFLL3zSmXZVm0L2j2l6NHOx0q+P72HEVSeEjHxc1YtHd2y61vznyn4XXbQ0ul7c4gqfU7g9dff13h2PTPTDfffLNSSinLstTDDz+sSktLVa9evdTll1+udu/ebavj888/VzfccIPKz89XBQUF6pZbblFffvml7Z4PPvhAXXLJJapXr17q61//upo3b56nfsqSo6Ruk5xLOLNmKfXqq+SqzufDamroNRJRau9e96WdwYPpOi851tRQrKpIRMcQc7rVA1RfUxPFIps7l1z8zbbr6+m1tVW/37uX6n71VXvfzP4OHEh5vEy6ebMu09iolN9P/WptpTbr65WaOpXK8HdRU0NnzcwjATyWwYPpu7r9dh35mvvLZTZvto9h8GCqx/ndt7fkmO7yYoaX8Dz3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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "diff_sig = Sig_all - Sig_sumup_sorted\n", + "# diff_sig = Sig_sumup_sorted[-18:,-18:].real-Sig_all[-18:,-18:].real\n", + "plt.matshow(abs(Sig_sumup_sorted.detach().numpy()),cmap='bwr',vmax=0.005,vmin=-0.005)\n", + "plt.matshow(abs(Sig_all.detach().numpy()),cmap='bwr',vmax=0.005,vmin=-0.005)\n", + "plt.matshow(abs(diff_sig.detach().numpy()),cmap='bwr',vmax=0.05,vmin=-0.05)\n", + "\n", + "#部分对角元上都没有问题,说明可能是相位设置出现了问题\n", + "#所有对角块总能对上\n", + "#调整默认相位convention,可以使更多格子对上,但仍有较多非对角元无法对上\n", + "#应该还是轨道顺序的问题,自能矩阵中每个原子的onsite block是一致的,而原子-原子间的相互作用有问题\n", + "##要过一遍自能计算的流程,将原子的轨道顺序check一下" + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "metadata": {}, + "outputs": [], + "source": [ + "# a = Sig_sumup_sorted[-9:,-18:-9].flatten()[14]\n", + "for idj,j in enumerate(Sig_sumup_sorted.flatten().detach().numpy()[::-1]):\n", + " pair = False\n", + " for idi,i in enumerate(Sig_all.flatten().detach().numpy()[::-1]):\n", + " if abs(i-j)<1e-4:\n", + " pair = True\n", + " break\n", + " if pair == False:\n", + " print(idj)\n", + " print(j)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "deeptb-dev", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index f4b18069..0dd11e78 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -5,7 +5,7 @@ from dptb.negf.negf_utils import quad, gauss_xw,leggauss,update_kmap from dptb.negf.ozaki_res_cal import ozaki_residues from dptb.negf.negf_hamiltonian_init import NEGFHamiltonianInit -from dptb.negf.density import Ozaki +from dptb.negf.density import Ozaki,Fiori from dptb.negf.areshkin_pole_sum import pole_maker from dptb.negf.device_property import DeviceProperty from dptb.negf.lead_property import LeadProperty @@ -19,63 +19,98 @@ import numpy as np from dptb.utils.make_kpoints import kmesh_sampling_negf import logging +from dptb.negf.poisson_init import Grid,Interface3D,Gate,Dielectric +from typing import Optional, Union +# from pyinstrument import Profiler +from dptb.data import AtomicData, AtomicDataDict log = logging.getLogger(__name__) # TODO : add common class to set all the dtype and precision. class NEGF(object): - def __init__(self, apiHrk, run_opt, jdata): - self.apiH = apiHrk - if isinstance(run_opt['structure'],str): - self.structase = read(run_opt['structure']) - elif isinstance(run_opt['structure'],ase.Atoms): - self.structase = run_opt['structure'] - else: - raise ValueError('structure must be ase.Atoms or str') + def __init__(self, + model: torch.nn.Module, + AtomicData_options: dict, + structure: Union[AtomicData, ase.Atoms, str], + ele_T: float,e_fermi: float, + emin: float, emax: float, espacing: float, + density_options: dict, + unit: str, + scf: bool, poisson_options: dict, + stru_options: dict,eta_lead: float,eta_device: float, + block_tridiagonal: bool,sgf_solver: str, + out_tc: bool=False,out_dos: bool=False,out_density: bool=False,out_potential: bool=False, + out_current: bool=False,out_current_nscf: bool=False,out_ldos: bool=False,out_lcurrent: bool=False, + results_path: Optional[str]=None, + torch_device: Union[str, torch.device]=torch.device('cpu'), + **kwargs): - self.results_path = run_opt.get('results_path') - self.jdata = jdata - self.cdtype = torch.complex128 - self._device = "cpu" + # self.apiH = apiHrk + + self.model = model + self.results_path = results_path + # self.jdata = jdata + self.cdtype = torch.complex128 + self.torch_device = torch_device # get the parameters - self.ele_T = jdata["ele_T"] - self.kBT = Boltzmann * self.ele_T / eV2J - self.e_fermi = jdata["e_fermi"] - self.stru_options = j_must_have(jdata, "stru_options") + self.ele_T = ele_T + self.kBT = Boltzmann * self.ele_T / eV2J # change to eV + self.e_fermi = e_fermi + self.eta_lead = eta_lead; self.eta_device = eta_device + self.emin = emin; self.emax = emax; self.espacing = espacing + self.stru_options = stru_options + self.sgf_solver = sgf_solver self.pbc = self.stru_options["pbc"] # check the consistency of the kmesh and pbc assert len(self.pbc) == 3, "pbc should be a list of length 3" for i in range(3): - if self.pbc[i] == False and self.jdata["stru_options"]["kmesh"][i] > 1: + if self.pbc[i] == False and self.stru_options["kmesh"][i] > 1: raise ValueError("kmesh should be 1 for non-periodic direction") - elif self.pbc[i] == False and self.jdata["stru_options"]["kmesh"][i] == 0: - self.jdata["stru_options"]["kmesh"][i] = 1 - log.info(msg="Warning! kmesh should be set to 1 for non-periodic direction") - elif self.pbc[i] == True and self.jdata["stru_options"]["kmesh"][i] == 0: + elif self.pbc[i] == False and self.stru_options["kmesh"][i] == 0: + self.stru_options["kmesh"][i] = 1 + log.warning(msg="kmesh should be set to 1 for non-periodic direction! Automatically Setting kmesh to 1 in direction {}.".format(i)) + elif self.pbc[i] == True and self.stru_options["kmesh"][i] == 0: raise ValueError("kmesh should be > 0 for periodic direction") - + if not any(self.pbc): self.kpoints,self.wk = np.array([[0,0,0]]),np.array([1.]) else: - self.kpoints,self.wk = kmesh_sampling_negf(self.jdata["stru_options"]["kmesh"], - self.jdata["stru_options"]["gamma_center"], - self.jdata["stru_options"]["time_reversal_symmetry"],) - - self.unit = jdata["unit"] - self.scf = jdata["scf"] - self.block_tridiagonal = jdata["block_tridiagonal"] - - - # computing the hamiltonian - self.negf_hamiltonian = NEGFHamiltonianInit(apiH=self.apiH, structase=self.structase, stru_options=jdata["stru_options"], results_path=self.results_path) + self.kpoints,self.wk = kmesh_sampling_negf(self.stru_options["kmesh"], + self.stru_options["gamma_center"], + self.stru_options["time_reversal_symmetry"]) + log.info(msg="------ k-point for NEGF -----") + log.info(msg="Gamma Center: {0}".format(self.stru_options["gamma_center"])) + log.info(msg="Time Reversal: {0}".format(self.stru_options["time_reversal_symmetry"])) + log.info(msg="k-points Num: {0}".format(len(self.kpoints))) + if len(self.wk)<10: + log.info(msg="k-points: {0}".format(self.kpoints)) + log.info(msg="k-points weights: {0}".format(self.wk)) + log.info(msg="--------------------------------") + + self.unit = unit + self.scf = scf + self.block_tridiagonal = block_tridiagonal + # computing the hamiltonian #需要改写NEGFHamiltonianInit + self.negf_hamiltonian = NEGFHamiltonianInit(model=model, + AtomicData_options=AtomicData_options, + structure=structure, + block_tridiagonal=self.block_tridiagonal, + pbc_negf = self.pbc, + stru_options=self.stru_options, + unit = self.unit, + results_path=self.results_path, + torch_device = self.torch_device) with torch.no_grad(): - struct_device, struct_leads = self.negf_hamiltonian.initialize(kpoints=self.kpoints) - + struct_device, struct_leads, subblocks = self.negf_hamiltonian.initialize(kpoints=self.kpoints, + block_tridiagnal=self.block_tridiagonal) + self.subblocks = subblocks # for not block_tridiagonal case, subblocks is [HD.shape[1]] + self.left_connected = abs(struct_device.positions[:,2]-min(struct_device.positions[:,2]))<1e-6 + self.right_connected = abs(struct_device.positions[:,2]-max(struct_device.positions[:,2]))<1e-6 self.deviceprop = DeviceProperty(self.negf_hamiltonian, struct_device, results_path=self.results_path, efermi=self.e_fermi) self.deviceprop.set_leadLR( @@ -86,7 +121,7 @@ def __init__(self, apiHrk, run_opt, jdata): results_path=self.results_path, e_T=self.ele_T, efermi=self.e_fermi, - voltage=self.jdata["stru_options"]["lead_L"]["voltage"] + voltage=self.stru_options["lead_L"]["voltage"] ), lead_R=LeadProperty( hamiltonian=self.negf_hamiltonian, @@ -95,30 +130,49 @@ def __init__(self, apiHrk, run_opt, jdata): results_path=self.results_path, e_T=self.ele_T, efermi=self.e_fermi, - voltage=self.jdata["stru_options"]["lead_R"]["voltage"] + voltage=self.stru_options["lead_R"]["voltage"] ) ) # initialize density class - self.density_options = j_must_have(self.jdata, "density_options") + # self.density_options = j_must_have(self.jdata, "density_options") + self.density_options = density_options if self.density_options["method"] == "Ozaki": self.density = Ozaki(R=self.density_options["R"], M_cut=self.density_options["M_cut"], n_gauss=self.density_options["n_gauss"]) + elif self.density_options["method"] == "Fiori": + if self.density_options["integrate_way"] == "gauss": + assert self.density_options["n_gauss"] is not None, "n_gauss should be set for Fiori method using gauss integration" + self.density = Fiori(n_gauss=self.density_options["n_gauss"]) + else: + self.density = Fiori() #calculate the density by integrating the energy window in direct way else: raise ValueError - + + # number of orbitals on atoms in device region + self.device_atom_norbs = self.negf_hamiltonian.h2k.atom_norbs[self.negf_hamiltonian.device_id[0]:self.negf_hamiltonian.device_id[1]] + # np.save(self.results_path+"/device_atom_norbs.npy",self.device_atom_norbs) + # geting the output settings - self.out_tc = jdata["out_tc"] - self.out_dos = jdata["out_dos"] - self.out_density = jdata["out_density"] - self.out_potential = jdata["out_potential"] - self.out_current = jdata["out_current"] - self.out_current_nscf = jdata["out_current_nscf"] - self.out_ldos = jdata["out_ldos"] - self.out_lcurrent = jdata["out_lcurrent"] + self.out_tc = out_tc + self.out_dos = out_dos + self.out_density = out_density + self.out_potential = out_potential + self.out_current = out_current + self.out_current_nscf = out_current_nscf + self.out_ldos = out_ldos + self.out_lcurrent = out_lcurrent assert not (self.out_lcurrent and self.block_tridiagonal) self.generate_energy_grid() self.out = {} + ## Poisson equation settings + self.poisson_options = poisson_options + # self.LDOS_integral = {} # for electron density integral + self.free_charge = {} # net charge: hole - electron + self.gate_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("gate")] + self.dielectric_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("dielectric")] + + def generate_energy_grid(self): @@ -131,10 +185,12 @@ def generate_energy_grid(self): v_list = [self.stru_options[i].get("voltage", None) for i in self.stru_options if i.startswith("lead")] v_list_b = [i == v_list[0] for i in v_list] if not all(v_list_b): - cal_pole = True + if self.density_options["method"] == "Ozaki": + cal_pole = True cal_int_grid = True elif self.out_density or self.out_potential: - cal_pole = True + if self.density_options["method"] == "Ozaki": + cal_pole = True v_list = [self.stru_options[i].get("voltage", None) for i in self.stru_options if i.startswith("lead")] v_list_b = [i == v_list[0] for i in v_list] if not all(v_list_b): @@ -147,10 +203,11 @@ def generate_energy_grid(self): cal_int_grid = True if self.out_dos or self.out_tc or self.out_current_nscf or self.out_ldos: - self.uni_grid = torch.linspace(start=self.jdata["emin"], end=self.jdata["emax"], steps=int((self.jdata["emax"]-self.jdata["emin"])/self.jdata["espacing"])) + # Energy gird is set relative to Fermi level + self.uni_grid = torch.linspace(start=self.emin, end=self.emax, steps=int((self.emax-self.emin)/self.espacing)) - if cal_pole: - self.poles, self.residues = ozaki_residues(M_cut=self.jdata["density_options"]["M_cut"]) + if cal_pole and self.density_options["method"] == "Ozaki": + self.poles, self.residues = ozaki_residues(M_cut=self.density_options["M_cut"]) self.poles = 1j* self.poles * self.kBT + self.deviceprop.lead_L.mu - self.deviceprop.mu if cal_int_grid: @@ -160,128 +217,311 @@ def generate_energy_grid(self): def compute(self): - # check if scf is required if self.scf: - # perform k-point sampling and scf calculation to get the converged density - for k in self.kpoints: - pass + # if not self.out_density: + # self.out_density = True + # raise UserWarning("SCF is required, but out_density is set to False. Automatically Setting out_density to True.") + self.poisson_negf_scf(err=self.poisson_options['err'],tolerance=self.poisson_options['tolerance'],\ + max_iter=self.poisson_options['max_iter'],mix_rate=self.poisson_options['mix_rate']) else: - pass + self.negf_compute(scf_require=False,Vbias=None) + + def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): + - self.out['k']=[]; self.out['wk']=[] - if hasattr(self, "uni_grid"): self.out["E"] = self.uni_grid + # profiler.start() + # create real-space grid + grid = self.get_grid(self.poisson_options["grid"],self.deviceprop.structure) + + # create gate + Gate_list = [] + for gg in range(len(self.gate_region)): + gate_init = Gate(self.gate_region[gg].get("x_range",None).split(':'),\ + self.gate_region[gg].get("y_range",None).split(':'),\ + self.gate_region[gg].get("z_range",None).split(':')) + gate_init.Ef = float(self.gate_region[gg].get("voltage",None)) # in unit of volt + Gate_list.append(gate_init) + + # create dielectric + Dielectric_list = [] + for dd in range(len(self.dielectric_region)): + dielectric_init = Dielectric(self.dielectric_region[dd].get("x_range",None).split(':'),\ + self.dielectric_region[dd].get("y_range",None).split(':'),\ + self.dielectric_region[dd].get("z_range",None).split(':')) + dielectric_init.eps = float(self.dielectric_region[dd].get("relative permittivity",None)) + Dielectric_list.append(dielectric_init) + + # create interface + interface_poisson = Interface3D(grid,Gate_list,Dielectric_list) + + #initial guess for electrostatic potential + log.info(msg="-----Initial guess for electrostatic potential----") + interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) + atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) + log.info(msg="-------------------------------------------\n") + + max_diff_phi = 1e30; max_diff_list = [] + iter_count=0 + # Gummel type iteration + while max_diff_phi > err: + # update Hamiltonian by modifying onsite energy with potential + atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) + self.potential_at_atom = interface_poisson.phi[atom_gridpoint_index] + self.potential_at_orb = torch.cat([torch.full((norb,), p) for p, norb\ + in zip(self.potential_at_atom, self.device_atom_norbs)]) + + + self.negf_compute(scf_require=True,Vbias=self.potential_at_orb) + # Vbias makes sense for orthogonal basis as in NanoTCAD + # TODO: check if Vbias makes sense for non-orthogonal basis + + # update electron density for solving Poisson equation SCF + # DM_eq,DM_neq = self.out["DM_eq"], self.out["DM_neq"] + # elec_density = torch.diag(DM_eq+DM_neq) + - # output kpoints information - log.info(msg="------ k-point for NEGF -----\n") - log.info(msg="Gamma Center: {0}".format(self.jdata["stru_options"]["gamma_center"])+"\n") - log.info(msg="Time Reversal: {0}".format(self.jdata["stru_options"]["time_reversal_symmetry"])+"\n") - log.info(msg="k-points Num: {0}".format(len(self.kpoints))+"\n") - log.info(msg="k-points weights: {0}".format(self.wk)+"\n") - log.info(msg="--------------------------------\n") + # elec_density_per_atom = [] + # pre_atom_orbs = 0 + # for i in range(len(device_atom_norbs)): + # elec_density_per_atom.append(torch.sum(elec_density[pre_atom_orbs : pre_atom_orbs+device_atom_norbs[i]]).numpy()) + # pre_atom_orbs += device_atom_norbs[i] + + # TODO: check the sign of free_charge + # TODO: check the spin degenracy + # TODO: add k summation operation + free_charge_allk = torch.zeros_like(torch.tensor(self.device_atom_norbs)) + for ik,k in enumerate(self.kpoints): + free_charge_allk += np.real(self.free_charge[str(k)].numpy()) * self.wk[ik] + interface_poisson.free_charge[atom_gridpoint_index] = free_charge_allk + + + interface_poisson.phi_old = interface_poisson.phi.copy() + max_diff_phi = interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) + interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi_old-interface_poisson.phi) + + + iter_count += 1 # Gummel type iteration + log.info(msg="Poisson-NEGF iteration: {} Potential Diff Maximum: {}\n".format(iter_count,max_diff_phi)) + max_diff_list.append(max_diff_phi) + if max_diff_phi <= err: + log.info(msg="Poisson-NEGF SCF Converges Successfully!") + + + if iter_count > max_iter: + log.warning(msg="Warning! Poisson-NEGF iteration exceeds the upper limit of iterations {}".format(int(max_iter))) + break + # profiler.stop() + # with open('profile_report.html', 'w') as report_file: + # report_file.write(profiler.output_html()) + # break + + self.poisson_out = {} + self.poisson_out['potential'] = torch.tensor(interface_poisson.phi) + self.poisson_out['potential_at_atom'] = self.potential_at_atom + self.poisson_out['grid_point_number'] = interface_poisson.grid.Np + self.poisson_out['grid'] = torch.tensor(interface_poisson.grid.grid_coord) + self.poisson_out['free_charge_at_atom'] = torch.tensor(interface_poisson.free_charge[atom_gridpoint_index]) + self.poisson_out['max_diff_list'] = torch.tensor(max_diff_list) + torch.save(self.poisson_out, self.results_path+"/poisson.out.pth") + + # calculate transport properties with converged potential + self.negf_compute(scf_require=False,Vbias=self.potential_at_orb) + + # output the profile report in html format + # if iter_count <= max_iter: + # profiler.stop() + # with open('profile_report.html', 'w') as report_file: + # report_file.write(profiler.output_html()) + + def negf_compute(self,scf_require=False,Vbias=None): + + + assert scf_require is not None + + self.out['k']=[];self.out['wk']=[] + if hasattr(self, "uni_grid"): self.out["uni_grid"] = self.uni_grid + for ik, k in enumerate(self.kpoints): - self.out["k"].append(k) + + self.out['k'].append(k) self.out['wk'].append(self.wk[ik]) + self.free_charge.update({str(k):torch.zeros_like(torch.tensor(self.device_atom_norbs),dtype=torch.complex128)}) log.info(msg="Properties computation at k = [{:.4f},{:.4f},{:.4f}]".format(float(k[0]),float(k[1]),float(k[2]))) - # computing properties that is functions of E - if hasattr(self, "uni_grid"): - output_freq = int(len(self.uni_grid)/10) - for ie,e in enumerate(self.uni_grid): - if ie % output_freq == 0: - log.info(msg="computing green's function at e = {:.3f}".format(float(e))) + + if scf_require: + if self.density_options["method"] == "Fiori": leads = self.stru_options.keys() + for ll in leads: if ll.startswith("lead"): - getattr(self.deviceprop, ll).self_energy( - energy=e, - kpoint=k, - eta_lead=self.jdata["eta_lead"], - method=self.jdata["sgf_solver"] - ) - - self.deviceprop.cal_green_function( - energy=e, - kpoint=k, - eta_device=self.jdata["eta_device"], - block_tridiagonal=self.block_tridiagonal + if Vbias is not None and self.density_options["method"] == "Fiori": + # set voltage as -1*potential_at_orb[0] and -1*potential_at_orb[-1] for self-energy same as in NanoTCAD + if ll == 'lead_L' : + getattr(self.deviceprop, ll).voltage = Vbias[self.left_connected].mean() + # getattr(self.deviceprop, ll).voltage = Vbias[0] + else: + getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected].mean() + # getattr(self.deviceprop, ll).voltage = Vbias[-1] + + self.density.density_integrate_Fiori( + e_grid = self.uni_grid, + kpoint=k, + Vbias=Vbias, + block_tridiagonal=self.block_tridiagonal, + subblocks=self.subblocks, + integrate_way = self.density_options["integrate_way"], + deviceprop=self.deviceprop, + device_atom_norbs=self.device_atom_norbs, + potential_at_atom = self.potential_at_atom, + free_charge = self.free_charge, + eta_lead = self.eta_lead, + eta_device = self.eta_device ) + else: + # TODO: add Ozaki support for NanoTCAD-style SCF + raise ValueError("Ozaki method does not support Poisson-NEGF SCF in this version.") + + # in non-scf case, computing properties in uni_gird + else: + if hasattr(self, "uni_grid"): + output_freq = int(len(self.uni_grid)/10) + + for ie, e in enumerate(self.uni_grid): + if ie % output_freq == 0: + log.info(msg="computing green's function at e = {:.3f}".format(float(e))) + leads = self.stru_options.keys() + for ll in leads: + if ll.startswith("lead"): + if Vbias is not None and self.density_options["method"] == "Fiori": + # set voltage as -1*potential_at_orb[0] and -1*potential_at_orb[-1] for self-energy same as in NanoTCAD + if ll == 'lead_L': + getattr(self.deviceprop, ll).voltage = self.potential_at_atom[self.left_connected].mean() + + else: + getattr(self.deviceprop, ll).voltage = self.potential_at_atom[self.right_connected].mean() + + + getattr(self.deviceprop, ll).self_energy( + energy=e, + kpoint=k, + eta_lead=self.eta_lead, + method=self.sgf_solver + ) + # self.out[str(ll)+"_se"][str(e.numpy())] = getattr(self.deviceprop, ll).se + + self.deviceprop.cal_green_function( + energy=e, kpoint=k, + eta_device=self.eta_device, + block_tridiagonal=self.block_tridiagonal, + Vbias=Vbias + ) + # self.out["gtrans"][str(e.numpy())] = gtrans + + + if self.out_dos: + # prop = self.out.setdefault("DOS", []) + # prop.append(self.compute_DOS(k)) + prop = self.out.setdefault('DOS', {}) + propk = prop.setdefault(str(k), []) + propk.append(self.compute_DOS(k)) + if self.out_tc or self.out_current_nscf: + # prop = self.out.setdefault("TC", []) + # prop.append(self.compute_TC(k)) + prop = self.out.setdefault('T_k', {}) + propk = prop.setdefault(str(k), []) + propk.append(self.compute_TC(k)) + if self.out_ldos: + # prop = self.out['LDOS'].setdefault(str(k), []) + # prop.append(self.compute_LDOS(k)) + prop = self.out.setdefault('LDOS', {}) + propk = prop.setdefault(str(k), []) + propk.append(self.compute_LDOS(k)) + + + # over energy loop in uni_gird + # The following code is for output properties before NEGF ends + # TODO: check following code for multiple k points calculation + + if self.out_density or self.out_potential: + if self.density_options["method"] == "Ozaki": + prop_DM_eq = self.out.setdefault('DM_eq', {}) + prop_DM_neq = self.out.setdefault('DM_neq', {}) + prop_DM_eq[str(k)], prop_DM_neq[str(k)] = self.compute_density_Ozaki(k,Vbias) + elif self.density_options["method"] == "Fiori": + log.warning("Fiori method does not support output density in this version.") + else: + raise ValueError("Unknown method for density calculation.") + if self.out_potential: + pass if self.out_dos: - # prop = self.out['DOS'].setdefault(str(k), []) - # prop.append(self.compute_DOS(k)) - prop = self.out.setdefault('DOS', {}) - propk = prop.setdefault(str(k), []) - propk.append(self.compute_DOS(k)) + self.out["DOS"][str(k)] = torch.stack(self.out["DOS"][str(k)]) if self.out_tc or self.out_current_nscf: - # prop = self.out['TC'].setdefault(str(k), []) - # prop.append(self.compute_TC(k)) - prop = self.out.setdefault('T_k', {}) - propk = prop.setdefault(str(k), []) - propk.append(self.compute_TC(k)) - if self.out_ldos: - # prop = self.out['LDOS'].setdefault(str(k), []) - # prop.append(self.compute_LDOS(k)) - prop = self.out.setdefault('LDOS', {}) - propk = prop.setdefault(str(k), []) - propk.append(self.compute_LDOS(k)) - - if self.out_dos: - self.out["DOS"][str(k)] = torch.stack(self.out["DOS"][str(k)]) - - if self.out_tc or self.out_current_nscf: - self.out["T_k"][str(k)] = torch.stack(self.out["T_k"][str(k)]) - - # if self.out_current_nscf: - # self.out["BIAS_POTENTIAL_NSCF"][str(k)], self.out["CURRENT_NSCF"][str(k)] \ - # = self.compute_current_nscf(k, self.uni_grid, self.out["TC"][str(k)]) - - # computing properties that are not functions of E (improvement can be made here in properties related to integration of energy window of fermi functions) - if self.out_current: - pass - - if self.out_density or self.out_potential: - prop_DM_eq = self.out.setdefault('DM_eq', {}) - prop_DM_neq = self.out.setdefault('DM_neq', {}) - prop_DM_eq[str(k)], prop_DM_neq[str(k)] = self.compute_density(k) + self.out["T_k"][str(k)] = torch.stack(self.out["T_k"][str(k)]) + # if self.out_current_nscf: + # self.out["BIAS_POTENTIAL_NSCF"], self.out["CURRENT_NSCF"] = self.compute_current_nscf(k, self.uni_grid, self.out["TC"]) + # computing properties that are not functions of E (improvement can be made here in properties related to integration of energy window of fermi functions) + if self.out_current: + pass - if self.out_potential: - pass - - if self.out_lcurrent: - lcurrent = 0 - for i, e in enumerate(self.int_grid): - log.info(msg="computing green's function at e = {:.3f}".format(float(e))) - leads = self.stru_options.keys() - for ll in leads: - if ll.startswith("lead"): - getattr(self.deviceprop, ll).self_energy( - energy=e, + # TODO: check the following code for multiple k points calculation + if self.out_lcurrent: + lcurrent = 0 + log.info(msg="computing local current at k = [{:.4f},{:.4f},{:.4f}]".format(float(k[0]),float(k[1]),float(k[2]))) + for i, e in enumerate(self.int_grid): + log.info(msg=" computing green's function at e = {:.3f}".format(float(e))) + leads = self.stru_options.keys() + for ll in leads: + if ll.startswith("lead"): + getattr(self.deviceprop, ll).self_energy( + energy=e, + kpoint=k, + eta_lead=self.eta_lead, + method=self.sgf_solver + ) + + self.deviceprop.cal_green_function( + energy=e, kpoint=k, - eta_lead=self.jdata["eta_lead"], - method=self.jdata["sgf_solver"] + eta_device=self.eta_device, + block_tridiagonal=self.block_tridiagonal ) - self.deviceprop.cal_green_function( - energy=e, - kpoint=k, - eta_device=self.jdata["eta_device"], - block_tridiagonal=self.block_tridiagonal - ) - - lcurrent += self.int_weight[i] * self.compute_lcurrent(k) - - prop_local_current = self.out.setdefault('LOCAL_CURRENT', {}) - prop_local_current[str(k)] = lcurrent + lcurrent += self.int_weight[i] * self.compute_lcurrent(k) + + prop_local_current = self.out.setdefault('LOCAL_CURRENT', {}) + prop_local_current[str(k)] = lcurrent - self.out["k"] = np.array(self.out["k"]) - self.out['T_avg'] = torch.tensor(self.out['wk']) @ torch.stack(list(self.out["T_k"].values())) - if self.out_current_nscf: - self.out["BIAS_POTENTIAL_NSCF"], self.out["CURRENT_NSCF"] \ - = self.compute_current_nscf(k, self.uni_grid, self.out["T_avg"]) - torch.save(self.out, self.results_path+"/negf.out.pth") + if scf_require==False: + self.out["k"] = np.array(self.out["k"]) + self.out['T_avg'] = torch.tensor(self.out['wk']) @ torch.stack(list(self.out["T_k"].values())) + # TODO:check the following code for multiple k points calculation + if self.out_current_nscf: + self.out["BIAS_POTENTIAL_NSCF"], self.out["CURRENT_NSCF"] = self.compute_current_nscf(self.uni_grid, self.out["T_avg"]) + torch.save(self.out, self.results_path+"/negf.out.pth") + + + def get_grid(self,grid_info,structase): + x_start,x_end,x_num = grid_info.get("x_range",None).split(':') + xg = np.linspace(float(x_start),float(x_end),int(x_num)) + + y_start,y_end,y_num = grid_info.get("y_range",None).split(':') + yg = np.linspace(float(y_start),float(y_end),int(y_num)) + # yg = np.array([(float(y_start)+float(y_end))/2]) # TODO: temporary fix for 2D case + + z_start,z_end,z_num = grid_info.get("z_range",None).split(':') + zg = np.linspace(float(z_start),float(z_end),int(z_num)) + + device_atom_coords = structase.get_positions() + xa,ya,za = device_atom_coords[:,0],device_atom_coords[:,1],device_atom_coords[:,2] + + # grid = Grid(xg,yg,zg,xa,ya,za) + grid = Grid(xg,yg,za,xa,ya,za) #TODO: change back to zg + return grid def fermi_dirac(self, x) -> torch.Tensor: return 1 / (1 + torch.exp(x / self.kBT)) @@ -305,12 +545,13 @@ def compute_TC(self, kpoint): def compute_LDOS(self, kpoint): return self.deviceprop.ldos - def compute_current_nscf(self, kpoint, ee, tc): + def compute_current_nscf(self, ee, tc): return self.deviceprop._cal_current_nscf_(ee, tc) - def compute_density(self, kpoint): - DM_eq, DM_neq = self.density.integrate(deviceprop=self.deviceprop, kpoint=kpoint) + def compute_density_Ozaki(self, kpoint,Vbias): + DM_eq, DM_neq = self.density.integrate(deviceprop=self.deviceprop, kpoint=kpoint, Vbias=Vbias) return DM_eq, DM_neq + def compute_current(self, kpoint): self.deviceprop.cal_green_function(e=self.int_grid, kpoint=kpoint, block_tridiagonal=self.block_tridiagonal) @@ -321,4 +562,5 @@ def compute_lcurrent(self, kpoint): def SCF(self): - pass \ No newline at end of file + pass + From f902f187ca40cffdb60e28ac10a955a87525644e Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 13 Jun 2024 20:28:53 +0800 Subject: [PATCH 153/209] update try_bloch.ipynb --- dptb/negf/try_bloch.ipynb | 50 +++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/dptb/negf/try_bloch.ipynb b/dptb/negf/try_bloch.ipynb index bbcbfaa4..a8fb50e6 100644 --- a/dptb/negf/try_bloch.ipynb +++ b/dptb/negf/try_bloch.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 15, + "execution_count": 1, "metadata": {}, "outputs": [ { @@ -47,14 +47,14 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Atoms(symbols='Au36', pbc=True, cell=[8.8188394545, 8.8188394545, 8.314432480000008])\n" + "Atoms(symbols='Au36', pbc=True, cell=[8.8188394545, 8.8188394545, 16.628864960000016])\n" ] } ], @@ -109,7 +109,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -124,8 +124,8 @@ "bloch_factor = [6,6,1]\n", "# Create a new cell with the z-direction fixed\n", "fixed_z_cell = np.array([\n", - " [cell[0][0]/bloch[0], 0, 0],\n", - " [0, cell[1][1]/bloch[1], 0],\n", + " [cell[0][0]/bloch_factor[0], 0, 0],\n", + " [0, cell[1][1]/bloch_factor[1], 0],\n", " [0, 0, cell[2][2]]\n", "])\n", "\n", @@ -134,7 +134,7 @@ "\n", "delta = 1e-4\n", "for ip,pos in enumerate(positions):\n", - " if pos[0]" + "" ] }, - "execution_count": 88, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" }, From ae7d8782c146c99656031069c8d4ea6f6b2beceb Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 24 Jun 2024 15:10:20 +0800 Subject: [PATCH 154/209] add Bloch theorem in NEGF module to accelarate self-energy calculation --- dptb/negf/lead_property.py | 96 ++++++++--- dptb/negf/negf_hamiltonian_init.py | 149 +++++++++++++--- dptb/negf/try_bloch.ipynb | 266 ++++++++++++++++++++++++++--- dptb/postprocess/NEGF.py | 33 ++-- 4 files changed, 464 insertions(+), 80 deletions(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 3cfe306c..4b1679c6 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -6,6 +6,9 @@ import os from dptb.utils.constants import Boltzmann, eV2J import numpy as np +from dptb.negf.bloch import Bloch +import torch.profiler + log = logging.getLogger(__name__) @@ -63,7 +66,9 @@ class LeadProperty(object): calculate the Gamma function from the self energy. ''' - def __init__(self, tab, hamiltonian, structure, results_path, voltage, e_T=300, efermi=0.0) -> None: + def __init__(self, tab, hamiltonian, structure, results_path, voltage,\ + structure_leads_fold,bloch_sorted_indice, useBloch: bool=False, bloch_factor: List[int]=[1,1,1],bloch_R_list:List=None,\ + e_T=300, efermi=0.0) -> None: self.hamiltonian = hamiltonian self.structure = structure self.tab = tab @@ -75,8 +80,15 @@ def __init__(self, tab, hamiltonian, structure, results_path, voltage, e_T=300, self.mu = self.efermi - self.voltage self.kpoint = None self.voltage_old = None + self.structure_leads_fold = structure_leads_fold + + self.useBloch = useBloch + self.bloch_factor = bloch_factor + self.bloch_sorted_indice = bloch_sorted_indice + self.bloch_R_list = bloch_R_list - def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-Sancho"): + def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-Sancho", \ + ): '''calculate and loads the self energy and surface green function at the given kpoint and energy. Parameters @@ -92,31 +104,75 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S ''' assert len(np.array(kpoint).reshape(-1)) == 3 - # according to given kpoint and e_mesh, calculating or loading the self energy and surface green function to self. if not isinstance(energy, torch.Tensor): energy = torch.tensor(energy) # Energy relative to Ef # if not hasattr(self, "HL"): #TODO: check here whether it is necessary to calculate the self energy every time - if not hasattr(self, "HL") or abs(self.voltage_old-self.voltage)>1e-6 or max(abs(self.kpoint-torch.tensor(kpoint)))>1e-6: - self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) - self.voltage_old = self.voltage - self.kpoint = torch.tensor(kpoint) - - self.se, _ = selfEnergy( - ee=energy, - hL=self.HL, - hLL=self.HLL, - sL=self.SL, - sLL=self.SLL, - hDL=self.HDL, - sDL=self.SDL, #TODO: check chemiPot settiing is correct or not - chemiPot=self.efermi, # temmporarily change to self.efermi for the case in which applying lead bias to corresponding to Nanotcad - etaLead=eta_lead, - method=method - ) + + if not self.useBloch: + if not hasattr(self, "HL") or abs(self.voltage_old-self.voltage)>1e-6 or max(abs(self.kpoint-torch.tensor(kpoint)))>1e-6: + self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL \ + = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) + self.voltage_old = self.voltage + self.kpoint = torch.tensor(kpoint) + + + self.se, _ = selfEnergy( + ee=energy, + hL=self.HL, + hLL=self.HLL, + sL=self.SL, + sLL=self.SLL, + hDL=self.HDL, + sDL=self.SDL, #TODO: check chemiPot settiing is correct or not + chemiPot=self.efermi, # temmporarily change to self.efermi for the case in which applying lead bias to corresponding to Nanotcad + etaLead=eta_lead, + method=method + ) + + else: + if not hasattr(self, "HL") or abs(self.voltage_old-self.voltage)>1e-6 or max(abs(self.kpoint-torch.tensor(kpoint)))>1e-6: + self.kpoint = torch.tensor(kpoint) + self.voltage_old = self.voltage + + bloch_unfolder = Bloch(self.bloch_factor) + kpoints_lead = bloch_unfolder.unfold_points(self.kpoint.tolist()) + se_k = [] + m_size = self.bloch_factor[2]*self.bloch_factor[1]*self.bloch_factor[0] + for k in kpoints_lead: + k = torch.tensor(k) + self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL \ + = self.hamiltonian.get_hs_lead(k, tab=self.tab, v=self.voltage) + + se, _ = selfEnergy( + ee=energy, + hL=self.HL, + hLL=self.HLL, + sL=self.SL, + sLL=self.SLL, + hDL=self.HDL, + sDL=self.SDL, #TODO: check chemiPot settiing is correct or not + chemiPot=self.efermi, # temmporarily change to self.efermi for the case in which applying lead bias to corresponding to Nanotcad + etaLead=eta_lead, + method=method + ) + phase_factor_m = torch.zeros([m_size,m_size],dtype=torch.complex128) + for i in range(m_size): + for j in range(m_size): + if i == j: + phase_factor_m[i,j] = 1 + else: + phase_factor_m[i,j] = torch.exp(torch.tensor(1j)*2*torch.pi*torch.dot(self.bloch_R_list[j]-self.bloch_R_list[i],k)) + + se_k.append(torch.kron(phase_factor_m,se)) + + se_k = torch.sum(torch.stack(se_k),dim=0)/len(se_k) + self.se = se_k[self.bloch_sorted_indice,:][:,self.bloch_sorted_indice] + + def sigmaLR2Gamma(self, se): '''calculate the Gamma function from the self energy. diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 6074b83a..cfc3be3e 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -23,7 +23,8 @@ from dptb.nn.hamiltonian import E3Hamiltonian from dptb.nn.hr2hk import HR2HK from ase import Atoms - +from ase.build import sort +from dptb.negf.bloch import Bloch from dptb.negf.sort_btd import sort_lexico, sort_projection, sort_capacitance from dptb.negf.split_btd import show_blocks,split_into_subblocks,split_into_subblocks_optimized ''' @@ -71,7 +72,7 @@ def __init__(self, ) -> None: # TODO: add dtype and device setting to the model - torch.set_default_dtype(torch.float64) + # torch.set_default_dtype(torch.float64) if isinstance(torch_device, str): torch_device = torch.device(torch_device) @@ -137,7 +138,7 @@ def __init__(self, log.error("The unit name is not correct !") raise ValueError - def initialize(self, kpoints, block_tridiagnal=False): + def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor=None): """initializes the device and lead Hamiltonians construct device and lead Hamiltonians and return the structures respectively.The lead Hamiltonian @@ -147,6 +148,8 @@ def initialize(self, kpoints, block_tridiagnal=False): kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) block_tridiagnal: A boolean parameter that determines whether to block-tridiagonalize the device Hamiltonian or not. + useBloch: A boolean parameter that determines whether to unfold the lead Hamiltonian with Bloch theorem or not. + bloch_factor: A list of three integers that determines the Bloch factor for unfolding the lead Hamiltonian. Returns: structure_device and structure_leads corresponding to the structure of device and leads. @@ -214,11 +217,25 @@ def initialize(self, kpoints, block_tridiagnal=False): structure_device = self.structase[self.device_id[0]:self.device_id[1]] structure_device.pbc = self.pbc_negf - structure_leads = {};coupling_width = {} + structure_leads = {};structure_leads_fold = {} + bloch_sorted_indices={};coupling_width = {};bloch_R_lists = {} for kk in self.stru_options: if kk.startswith("lead"): HS_leads = {} - HS_leads["kpoints"] = kpoints + if useBloch: + bloch_unfolder = Bloch(bloch_factor) + k_unfolds_list = [] + for kp in kpoints: + k_unfolds_list.append(bloch_unfolder.unfold_points(kp)) + k_unfolds = np.concatenate(k_unfolds_list,axis=0) + kpoints_bloch = np.unique(k_unfolds,axis=0) + HS_leads["kpoints"] = kpoints + HS_leads["kpoints_bloch"] = kpoints_bloch + HS_leads["bloch_factor"] = bloch_factor + else: + HS_leads["kpoints"] = kpoints + HS_leads["kpoints_bloch"] = None + HS_leads["bloch_factor"] = None # update lead id n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]].sum() @@ -239,13 +256,17 @@ def initialize(self, kpoints, block_tridiagnal=False): torch.max(nonzero_indice[:,2]).item()-torch.min(nonzero_indice[:,2]).item() +1) log.info(msg="The coupling width of {} is {}.".format(kk,coupling_width[kk])) - structure_leads[kk] = self.get_lead_structure(kk,n_proj_atom_lead) - + structure_leads[kk],structure_leads_fold[kk],bloch_sorted_indices[kk],bloch_R_lists[kk] \ + = self.get_lead_structure(kk,n_proj_atom_lead,useBloch=useBloch,bloch_factor=bloch_factor,lead_id=lead_id) # get lead_data - lead_data = AtomicData.from_ase(structure_leads[kk], **self.AtomicData_options) + lead_data = AtomicData.from_ase(structure_leads_fold[kk], **self.AtomicData_options) lead_data = AtomicData.to_AtomicDataDict(lead_data.to(self.torch_device)) lead_data = self.model.idp(lead_data) - lead_data[AtomicDataDict.KPOINT_KEY] = \ + if useBloch: + lead_data[AtomicDataDict.KPOINT_KEY] = \ + torch.nested.as_nested_tensor([torch.as_tensor(HS_leads["kpoints_bloch"], dtype=self.model.dtype, device=self.torch_device)]) + else: + lead_data[AtomicDataDict.KPOINT_KEY] = \ torch.nested.as_nested_tensor([torch.as_tensor(HS_leads["kpoints"], dtype=self.model.dtype, device=self.torch_device)]) lead_data = self.model(lead_data) # remove the edges corresponding to z-direction pbc for HR2HK @@ -271,8 +292,12 @@ def initialize(self, kpoints, block_tridiagnal=False): nL = int(HK_lead.shape[1] / 2) HLL, SLL = HK_lead[:, :nL, nL:], S_lead[:, :nL, nL:] # H_{L_first2L_second} hL, sL = HK_lead[:,:nL,:nL], S_lead[:,:nL,:nL] # lead hamiltonian in one principal layer - err_l_HK = (hL - HL).abs().max() - err_l_SK = (sL - SL).abs().max() + if not useBloch: + err_l_HK = (hL - HL).abs().max() + err_l_SK = (sL - SL).abs().max() + else: #TODO: add err_l_Hk and err_l_SK check in bloch case + err_l_HK = 0 + err_l_SK = 0 if max(err_l_HK,err_l_SK) >= 1e-4: # check the lead hamiltonian get from device and lead calculation matches each other @@ -285,8 +310,8 @@ def initialize(self, kpoints, block_tridiagnal=False): log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(max(err_l_HK,err_l_SK))) HS_leads.update({ - "HL":HL.cdouble()*self.h_factor, - "SL":SL.cdouble(), + "HL":hL.cdouble()*self.h_factor, + "SL":sL.cdouble(), "HDL":HDL.cdouble()*self.h_factor, "SDL":SDL.cdouble(), "HLL":HLL.cdouble()*self.h_factor, @@ -313,7 +338,7 @@ def initialize(self, kpoints, block_tridiagnal=False): torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) torch.set_default_dtype(torch.float32) - return structure_device, structure_leads, subblocks + return structure_device, structure_leads, structure_leads_fold, bloch_sorted_indices, bloch_R_lists,subblocks def remove_bonds_nonpbc(self,data,pbc): @@ -326,7 +351,7 @@ def remove_bonds_nonpbc(self,data,pbc): if self.overlap: data[AtomicDataDict.EDGE_OVERLAP_KEY] = data[AtomicDataDict.EDGE_OVERLAP_KEY][mask] - def get_lead_structure(self,kk,natom): + def get_lead_structure(self,kk,natom,useBloch=False,bloch_factor=None,lead_id=None): stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] cell = np.array(stru_lead.cell)[:2] @@ -334,16 +359,70 @@ def get_lead_structure(self,kk,natom): assert np.abs(R_vec[0] - R_vec[-1]).sum() < 1e-5 R_vec = R_vec.mean(axis=0) * 2 cell = np.concatenate([cell, R_vec.reshape(1,-1)]) - - # get lead structure in ase format pbc_lead = self.pbc_negf.copy() pbc_lead[2] = True + + # get lead structure in ase format stru_lead = Atoms(str(stru_lead.symbols), positions=stru_lead.positions, cell=cell, pbc=pbc_lead) stru_lead.set_chemical_symbols(stru_lead.get_chemical_symbols()) - return stru_lead + + if useBloch: + assert lead_id is not None + assert bloch_factor is not None + bloch_reduce_cell = np.array([ + [cell[0][0]/bloch_factor[0], 0, 0], + [0, cell[1][1]/bloch_factor[1], 0], + [0, 0, cell[2][2]] + ]) + bloch_reduce_cell = torch.from_numpy(bloch_reduce_cell) + new_positions = [] + new_atomic_numbers = [] + delta = 1e-4 + for ip,pos in enumerate(stru_lead.get_positions()): + if pos[0]" + "" ] }, - "execution_count": 13, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, @@ -624,6 +653,197 @@ " print(idj)\n", " print(j)\n" ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0. , 0. , 0. ],\n", + " [0. , 0.16666667, 0. ],\n", + " [0. , 0.33333333, 0. ],\n", + " [0. , 0.5 , 0. ],\n", + " [0. , 0.66666667, 0. ],\n", + " [0. , 0.83333333, 0. ],\n", + " [0.16666667, 0. , 0. ],\n", + " [0.16666667, 0.16666667, 0. ],\n", + " [0.16666667, 0.33333333, 0. ],\n", + " [0.16666667, 0.5 , 0. ],\n", + " [0.16666667, 0.66666667, 0. ],\n", + " [0.16666667, 0.83333333, 0. ],\n", + " [0.33333333, 0. , 0. ],\n", + " [0.33333333, 0.16666667, 0. ],\n", + " [0.33333333, 0.33333333, 0. ],\n", + " [0.33333333, 0.5 , 0. ],\n", + " [0.33333333, 0.66666667, 0. ],\n", + " [0.33333333, 0.83333333, 0. ],\n", + " [0.5 , 0. , 0. ],\n", + " [0.5 , 0.16666667, 0. ],\n", + " [0.5 , 0.33333333, 0. ],\n", + " [0.5 , 0.5 , 0. ],\n", + " [0.5 , 0.66666667, 0. ],\n", + " [0.5 , 0.83333333, 0. ],\n", + " [0.66666667, 0. , 0. ],\n", + " [0.66666667, 0.16666667, 0. ],\n", + " [0.66666667, 0.33333333, 0. ],\n", + " [0.66666667, 0.5 , 0. ],\n", + " [0.66666667, 0.66666667, 0. ],\n", + " [0.66666667, 0.83333333, 0. ],\n", + " [0.83333333, 0. , 0. ],\n", + " [0.83333333, 0.16666667, 0. ],\n", + " [0.83333333, 0.33333333, 0. ],\n", + " [0.83333333, 0.5 , 0. ],\n", + " [0.83333333, 0.66666667, 0. ],\n", + " [0.83333333, 0.83333333, 0. ]])" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from bloch import Bloch\n", + "import numpy as np\n", + "bloch_factor = [6,6,1]\n", + "\n", + "kps = [[0,0,0]]\n", + "# kps = [[0,0,1]]\n", + "k_unfolds_list = []\n", + "bloch = Bloch(bloch_factor)\n", + "\n", + "for kp in kps:\n", + " k_unfold = bloch.unfold_points(kp)\n", + " k_unfolds_list.append(k_unfold)\n", + "\n", + "k_unfolds_list\n", + "\n", + "k_unfolds = np.concatenate(k_unfolds_list,axis=0)\n", + "# k_unfolds.shape\n", + "unique_k_unfolds = np.unique(k_unfolds,axis=0)\n", + "unique_k_unfolds" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['kpoints', 'HL', 'SL', 'HDL', 'SDL', 'HLL', 'SLL'])" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import torch\n", + "\n", + "hs_lead = torch.load('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/negf_out/HS_lead_L.pth')\n", + "hs_lead.keys()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['kpoints', 'HL', 'SL', 'HDL', 'SDL', 'HLL', 'SLL'])" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hs_lead.keys()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([36, 2160, 2160])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hs_lead['SLL'].shape" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "\n", + "f = torch.load('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/negf_out/HS_lead_L.pth')\n", + "\n", + "kpoints = f[\"kpoints\"]\n", + "kpoints_bloch = f[\"kpoints_bloch\"]\n", + "bloch_factor = f[\"bloch_factor\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([4, 6840, 2160])" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f['HDL'].shape" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([36, 240, 240])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f['HL'].shape" + ] } ], "metadata": { diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 0dd11e78..e86562a3 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -55,6 +55,8 @@ def __init__(self, self.cdtype = torch.complex128 self.torch_device = torch_device + self.useBloch = True + self.bloch_factor = [3,3,1] # get the parameters self.ele_T = ele_T @@ -106,8 +108,9 @@ def __init__(self, results_path=self.results_path, torch_device = self.torch_device) with torch.no_grad(): - struct_device, struct_leads, subblocks = self.negf_hamiltonian.initialize(kpoints=self.kpoints, - block_tridiagnal=self.block_tridiagonal) + struct_device, struct_leads,structure_leads_fold,bloch_sorted_indices,bloch_R_lists,subblocks = \ + self.negf_hamiltonian.initialize(kpoints=self.kpoints,block_tridiagnal=self.block_tridiagonal,\ + useBloch=self.useBloch,bloch_factor=self.bloch_factor) self.subblocks = subblocks # for not block_tridiagonal case, subblocks is [HD.shape[1]] self.left_connected = abs(struct_device.positions[:,2]-min(struct_device.positions[:,2]))<1e-6 self.right_connected = abs(struct_device.positions[:,2]-max(struct_device.positions[:,2]))<1e-6 @@ -121,16 +124,26 @@ def __init__(self, results_path=self.results_path, e_T=self.ele_T, efermi=self.e_fermi, - voltage=self.stru_options["lead_L"]["voltage"] + voltage=self.stru_options["lead_L"]["voltage"], + useBloch=self.useBloch, + bloch_factor=self.bloch_factor, + structure_leads_fold=structure_leads_fold["lead_L"], + bloch_sorted_indice=bloch_sorted_indices["lead_L"], + bloch_R_list=bloch_R_lists["lead_L"] ), lead_R=LeadProperty( - hamiltonian=self.negf_hamiltonian, - tab="lead_R", - structure=struct_leads["lead_R"], - results_path=self.results_path, - e_T=self.ele_T, - efermi=self.e_fermi, - voltage=self.stru_options["lead_R"]["voltage"] + hamiltonian=self.negf_hamiltonian, + tab="lead_R", + structure=struct_leads["lead_R"], + results_path=self.results_path, + e_T=self.ele_T, + efermi=self.e_fermi, + voltage=self.stru_options["lead_R"]["voltage"], + useBloch=self.useBloch, + bloch_factor=self.bloch_factor, + structure_leads_fold=structure_leads_fold["lead_R"], + bloch_sorted_indice=bloch_sorted_indices["lead_R"], + bloch_R_list=bloch_R_lists["lead_R"] ) ) From aab17067a0c5ad933fd5a6decfd765a2c033920b Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 26 Jun 2024 14:23:07 +0800 Subject: [PATCH 155/209] fix bloch-fold self energy calculation bug and run successfully --- dptb/negf/lead_property.py | 42 +++++++++++++--------- dptb/negf/negf_hamiltonian_init.py | 57 +++++++++++++++++------------- dptb/postprocess/NEGF.py | 2 +- examples/get_fermi/get_fermi.ipynb | 2 +- 4 files changed, 60 insertions(+), 43 deletions(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 4b1679c6..10bc6080 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -132,6 +132,8 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S etaLead=eta_lead, method=method ) + + # torch.save(self.se, os.path.join(self.results_path, f"se_nobloch_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_{energy}.pth")) else: if not hasattr(self, "HL") or abs(self.voltage_old-self.voltage)>1e-6 or max(abs(self.kpoint-torch.tensor(kpoint)))>1e-6: @@ -139,22 +141,20 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S self.voltage_old = self.voltage bloch_unfolder = Bloch(self.bloch_factor) - kpoints_lead = bloch_unfolder.unfold_points(self.kpoint.tolist()) - se_k = [] - m_size = self.bloch_factor[2]*self.bloch_factor[1]*self.bloch_factor[0] - for k in kpoints_lead: - k = torch.tensor(k) + kpoints_bloch = bloch_unfolder.unfold_points(self.kpoint.tolist()) + sgf_k = [] + m_size = self.bloch_factor[1]*self.bloch_factor[0] + for ik_lead,k_bloch in enumerate(kpoints_bloch): + k_bloch = torch.tensor(k_bloch) self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL \ - = self.hamiltonian.get_hs_lead(k, tab=self.tab, v=self.voltage) + = self.hamiltonian.get_hs_lead(k_bloch, tab=self.tab, v=self.voltage) - se, _ = selfEnergy( + _, sgf = selfEnergy( ee=energy, hL=self.HL, hLL=self.HLL, sL=self.SL, - sLL=self.SLL, - hDL=self.HDL, - sDL=self.SDL, #TODO: check chemiPot settiing is correct or not + sLL=self.SLL, #TODO: check chemiPot settiing is correct or not chemiPot=self.efermi, # temmporarily change to self.efermi for the case in which applying lead bias to corresponding to Nanotcad etaLead=eta_lead, method=method @@ -165,13 +165,21 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S if i == j: phase_factor_m[i,j] = 1 else: - phase_factor_m[i,j] = torch.exp(torch.tensor(1j)*2*torch.pi*torch.dot(self.bloch_R_list[j]-self.bloch_R_list[i],k)) - - se_k.append(torch.kron(phase_factor_m,se)) - - se_k = torch.sum(torch.stack(se_k),dim=0)/len(se_k) - self.se = se_k[self.bloch_sorted_indice,:][:,self.bloch_sorted_indice] - + phase_factor_m[i,j] = torch.exp(torch.tensor(1j)*2*torch.pi*torch.dot(self.bloch_R_list[j]-self.bloch_R_list[i],k_bloch)) + phase_factor_m = phase_factor_m.contiguous() + sgf = sgf.contiguous() + sgf_k.append(torch.kron(phase_factor_m,sgf)) + + + sgf_k = torch.sum(torch.stack(sgf_k),dim=0)/len(sgf_k) + sgf_k = sgf_k[self.bloch_sorted_indice,:][:,self.bloch_sorted_indice] + b = self.HDL.shape[1] + if not isinstance(energy, torch.Tensor): + eeshifted = torch.scalar_tensor(energy, dtype=torch.complex128) + self.efermi + else: + eeshifted = energy + self.efermi + self.se = (eeshifted*self.SDL-self.HDL) @ sgf_k[:b,:b] @ (eeshifted*self.SDL.conj().T-self.HDL.conj().T) + # torch.save(self.se, os.path.join(self.results_path, f"se_bloch_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_{energy}.pth")) def sigmaLR2Gamma(self, se): diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index cfc3be3e..a4e55ff9 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -227,8 +227,7 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor k_unfolds_list = [] for kp in kpoints: k_unfolds_list.append(bloch_unfolder.unfold_points(kp)) - k_unfolds = np.concatenate(k_unfolds_list,axis=0) - kpoints_bloch = np.unique(k_unfolds,axis=0) + kpoints_bloch = np.concatenate(k_unfolds_list,axis=0) HS_leads["kpoints"] = kpoints HS_leads["kpoints_bloch"] = kpoints_bloch HS_leads["bloch_factor"] = bloch_factor @@ -256,10 +255,13 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor torch.max(nonzero_indice[:,2]).item()-torch.min(nonzero_indice[:,2]).item() +1) log.info(msg="The coupling width of {} is {}.".format(kk,coupling_width[kk])) - structure_leads[kk],structure_leads_fold[kk],bloch_sorted_indices[kk],bloch_R_lists[kk] \ - = self.get_lead_structure(kk,n_proj_atom_lead,useBloch=useBloch,bloch_factor=bloch_factor,lead_id=lead_id) + structure_leads[kk],structure_leads_fold[kk],bloch_sorted_indices[kk],bloch_R_lists[kk] = self.get_lead_structure(kk,n_proj_atom_lead,\ + useBloch=useBloch,bloch_factor=bloch_factor,lead_id=lead_id) # get lead_data - lead_data = AtomicData.from_ase(structure_leads_fold[kk], **self.AtomicData_options) + if useBloch: + lead_data = AtomicData.from_ase(structure_leads_fold[kk], **self.AtomicData_options) + else: + lead_data = AtomicData.from_ase(structure_leads[kk], **self.AtomicData_options) lead_data = AtomicData.to_AtomicDataDict(lead_data.to(self.torch_device)) lead_data = self.model.idp(lead_data) if useBloch: @@ -269,16 +271,7 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor lead_data[AtomicDataDict.KPOINT_KEY] = \ torch.nested.as_nested_tensor([torch.as_tensor(HS_leads["kpoints"], dtype=self.model.dtype, device=self.torch_device)]) lead_data = self.model(lead_data) - # remove the edges corresponding to z-direction pbc for HR2HK - # for ip,p in enumerate(self.pbc_negf): - # if not p: - # mask = abs(lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][:,ip])<1e-7 - # lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY] = lead_data[AtomicDataDict.EDGE_CELL_SHIFT_KEY][mask] - # lead_data[AtomicDataDict.EDGE_INDEX_KEY] = lead_data[AtomicDataDict.EDGE_INDEX_KEY][:,mask] - # lead_data[AtomicDataDict.EDGE_FEATURES_KEY] = lead_data[AtomicDataDict.EDGE_FEATURES_KEY][mask] - # if self.overlap: - # lead_data[AtomicDataDict.EDGE_OVERLAP_KEY] = lead_data[AtomicDataDict.EDGE_OVERLAP_KEY][mask] - + self.remove_bonds_nonpbc(lead_data,self.pbc_negf) lead_data = self.h2k(lead_data) HK_lead = lead_data[AtomicDataDict.HAMILTONIAN_KEY] @@ -299,7 +292,7 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor err_l_HK = 0 err_l_SK = 0 - if max(err_l_HK,err_l_SK) >= 1e-4: + if max(err_l_HK,err_l_SK) >= 1e-3: # check the lead hamiltonian get from device and lead calculation matches each other # a standard check to see the lead environment is bulk-like or not log.info(msg="The lead's hamiltonian or overlap attained from device and lead calculation does not match. \ @@ -368,6 +361,7 @@ def get_lead_structure(self,kk,natom,useBloch=False,bloch_factor=None,lead_id=No cell=cell, pbc=pbc_lead) stru_lead.set_chemical_symbols(stru_lead.get_chemical_symbols()) + write(os.path.join(self.results_path, "stru_leadall_"+kk+".xyz"),stru_lead,format='extxyz') if useBloch: assert lead_id is not None @@ -385,6 +379,7 @@ def get_lead_structure(self,kk,natom,useBloch=False,bloch_factor=None,lead_id=No if pos[0] Date: Wed, 26 Jun 2024 14:24:05 +0800 Subject: [PATCH 156/209] set dtype as float64 for unittest --- dptb/negf/negf_hamiltonian_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index a4e55ff9..03d5918c 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -72,7 +72,7 @@ def __init__(self, ) -> None: # TODO: add dtype and device setting to the model - # torch.set_default_dtype(torch.float64) + torch.set_default_dtype(torch.float64) if isinstance(torch_device, str): torch_device = torch.device(torch_device) From 9795811f44b69b31af89c326309ee991448054ef Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 26 Jun 2024 14:32:01 +0800 Subject: [PATCH 157/209] update try_bloch.ipynb --- dptb/negf/try_bloch.ipynb | 399 ++++++++++++++++++++++---------------- 1 file changed, 235 insertions(+), 164 deletions(-) diff --git a/dptb/negf/try_bloch.ipynb b/dptb/negf/try_bloch.ipynb index a05ae7b3..3ed6f51a 100644 --- a/dptb/negf/try_bloch.ipynb +++ b/dptb/negf/try_bloch.ipynb @@ -47,7 +47,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -109,12 +109,13 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Read the structure from a file (replace 'structure.cif' with your file)\n", "import ase\n", + "import numpy as np\n", "from ase.build import sort\n", "structure = ase.io.read('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/Au_lead_2PL.vasp')\n", "cell = structure.get_cell()\n", @@ -159,14 +160,6 @@ "execution_count": 3, "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/opt/mamba/envs/deeptb-dev/lib/python3.10/site-packages/torch/nested/__init__.py:58: UserWarning: The PyTorch API of nested tensors is in prototype stage and will change in the near future. (Triggered internally at ../aten/src/ATen/NestedTensorImpl.cpp:178.)\n", - " return torch._nested_tensor_from_tensor_list(tensor_list, dtype, None, device, None)\n" - ] - }, { "name": "stdout", "output_type": "stream", @@ -204,7 +197,10 @@ " if overlap:\n", " data[AtomicDataDict.EDGE_OVERLAP_KEY] = data[AtomicDataDict.EDGE_OVERLAP_KEY][mask]\n", "\n", - "model = build_model(checkpoint=\"/personal/DFTB/Au_Ag/Au_TS/nnsk_dftbint/checkpoint/nnsk.best.pth\")\n", + "common_options= {\"device\": \"cpu\"}\n", + "model = build_model(checkpoint=\"/personal/DFTB/Au_Ag/Au_TS/train_input_e3/nnenv.ep1651.pth\",\n", + " common_options=common_options)\n", + "# model = build_model(checkpoint=\"/personal/DFTB/Au_Ag/Au_TS/nnsk_dftbint/checkpoint/nnsk.best.pth\")\n", "AtomicData_options={\n", " \"r_max\": 5.5,\n", " \"pbc\": True\n", @@ -362,63 +358,6 @@ "cell_type": "code", "execution_count": 6, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "torch.Size([144, 144])" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Sig_all.shape\n", - "Sig_list[0].shape" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[0. , 0. , 0. ],\n", - " [0. , 2.93961903, 0. ],\n", - " [2.93961903, 0. , 0. ],\n", - " [2.93961903, 2.93961903, 0. ],\n", - " [1.46980599, 1.46980599, 2.07854976],\n", - " [1.46980599, 4.40941973, 2.07854976],\n", - " [4.40941973, 1.46980599, 2.07854976],\n", - " [4.40941973, 4.40941973, 2.07854976],\n", - " [0. , 0. , 4.15709952],\n", - " [0. , 2.93961903, 4.15709952],\n", - " [2.93961903, 0. , 4.15709952],\n", - " [2.93961903, 2.93961903, 4.15709952],\n", - " [1.46980599, 1.46980599, 6.23588272],\n", - " [1.46980599, 4.40941973, 6.23588272],\n", - " [4.40941973, 1.46980599, 6.23588272],\n", - " [4.40941973, 4.40941973, 6.23588272]])" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "small_struct = read('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/Au_lead_small.vasp')\n", - "small_struct.positions" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, "outputs": [ { "name": "stdout", @@ -444,7 +383,7 @@ "Sig_k_list = []\n", "origin_pos = []\n", "all_pos = []\n", - "atom_orb = 9\n", + "atom_orb = 15\n", "\n", "\n", "R_list = []\n", @@ -494,105 +433,47 @@ }, { "cell_type": "code", - "execution_count": 84, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "36" - ] - }, - "execution_count": 84, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(R_list)" - ] - }, - { - "cell_type": "code", - "execution_count": 85, + "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "tensor(1.7361e-05, dtype=torch.float64, grad_fn=)" + "array([5.8792263, 0. , 0. ])" ] }, - "execution_count": 85, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "abs(Sig_sumup_sorted[-10:,-10:].real-Sig_all[-10:,-10:].real).max()" - ] - }, - { - "cell_type": "code", - "execution_count": 86, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "tensor(3.3628e-05, dtype=torch.float64, grad_fn=)" - ] - }, - "execution_count": 86, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "abs(Sig_all-Sig_sumup_sorted).max()" - ] - }, - { - "cell_type": "code", - "execution_count": 87, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "tensor(3.5893e-14, dtype=torch.float64, grad_fn=)" - ] - }, - "execution_count": 87, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#在Nx=72时误差显著放大,代码里显然有bug\n", - "Nx=73\n", - "abs(Sig_all[:Nx,:Nx]-Sig_sumup_sorted[:Nx,:Nx]).max()\n", - "# Sig_all[17:Nx,17:Nx]-Sig_sumup[17:Nx,17:Nx]" + "small_struct = read('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/Au_lead_small.vasp')\n", + "small_struct = sort(small_struct,tags=small_struct.positions[:,0])\n", + "small_struct = sort(small_struct,tags=small_struct.positions[:,1])\n", + "small_struct = sort(small_struct,tags=small_struct.positions[:,2])\n", + "small_cell = small_struct.get_cell()\n", + "small_cell[0]" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 10, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAbQAAAGkCAYAAABZ4tDdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAACGIUlEQVR4nO2de3xU1bn3fwxjCDEmMdIkphDFIgIGRBtBrOVF5Sj1Qjl6yotFMd54tVBFqhWsWl5rkQ/ta085R4rUKiVi6eWDHORYjygiXrgoMXIrRIsYEEOKcTJOhzCMe71/PDxZa+/ZM5k9M0lg8nw/n/WZmbX3uk1gP7PWetbv6aGUUhAEQRCEExxfV3dAEARBEDKBGDRBEAQhKxCDJgiCIGQFYtAEQRCErEAMmiAIgpAViEETBEEQsgIxaIIgCEJWIAZNEARByArEoAmCIAhZgRg0QRAEISvISoP25JNP4swzz0Rubi5GjhyJzZs3d3WXkubxxx/HhRdeiFNOOQUlJSWYMGECdu/ebbuntbUV06ZNw2mnnYb8/Hxcf/31OHjwoO2ehoYGXH311cjLy0NJSQnuv/9+RKPRzhxK0sybNw89evTAjBkz2vKyZYyffvopbrzxRpx22mno3bs3hg4divfee6/tulIKjzzyCE4//XT07t0bY8eOxYcffmiro7m5GZMnT0ZBQQGKiopw2223IRQKdfZQXPnqq6/w8MMPo3///ujduze+8Y1v4Gc/+xlMRb0TcYzr16/Htddei/LycvTo0QMrV660Xc/UmLZu3Ypvf/vbyM3NRb9+/TB//vyOHlobicZ49OhRPPDAAxg6dChOPvlklJeXY8qUKThw4ICtjuNujCrLWL58ucrJyVHPPPOM2rFjh7rjjjtUUVGROnjwYFd3LSmuvPJK9eyzz6rt27eruro6ddVVV6mKigoVCoXa7rnzzjtVv3791Guvvabee+89ddFFF6mLL7647Xo0GlWVlZVq7Nix6v3331cvvfSS6tOnj5o9e3ZXDCkhmzdvVmeeeaYaNmyYuueee9rys2GMzc3N6owzzlDV1dVq06ZNas+ePep//ud/1EcffdR2z7x581RhYaFauXKl+uCDD9T48eNV//791eHDh9vuGTdunDrvvPPUxo0b1ZtvvqkGDBigbrjhhq4YUgw///nP1WmnnaZWr16tPv74Y/XnP/9Z5efnq1//+tdt95yIY3zppZfUT37yE7VixQoFQL3wwgu265kYU0tLiyotLVWTJ09W27dvV3/4wx9U79691VNPPdXlYwwEAmrs2LHqj3/8o9q1a5fasGGDGjFihPrmN79pq+N4G2PWGbQRI0aoadOmtX3+6quvVHl5uXr88ce7sFep09TUpACoN954QylF/9BOOukk9ec//7ntnr/97W8KgNqwYYNSiv6h+nw+1djY2HbPb37zG1VQUKCOHDnSuQNIwJdffqnOPvtstWbNGvW//tf/ajNo2TLGBx54QF1yySVxr1uWpcrKytQvfvGLtrxAIKB69eql/vCHPyillNq5c6cCoN599922e/7617+qHj16qE8//bTjOp8kV199tbr11lttedddd52aPHmyUio7xuh82GdqTAsXLlSnnnqq7d/rAw88oM4555wOHlEsbkbbyebNmxUA9cknnyiljs8xZtWSYyQSwZYtWzB27Ni2PJ/Ph7Fjx2LDhg1d2LPUaWlpAQAUFxcDALZs2YKjR4/axjho0CBUVFS0jXHDhg0YOnQoSktL2+658sorEQwGsWPHjk7sfWKmTZuGq6++2jYWIHvGuGrVKlRVVeF73/seSkpKcP755+O3v/1t2/WPP/4YjY2NtnEWFhZi5MiRtnEWFRWhqqqq7Z6xY8fC5/Nh06ZNnTeYOFx88cV47bXXUF9fDwD44IMP8NZbb+E73/kOgOwYo5NMjWnDhg0YPXo0cnJy2u658sorsXv3bnzxxRedNJrkaWlpQY8ePVBUVATg+ByjP+M1diGHDh3CV199ZXvIAUBpaSl27drVRb1KHcuyMGPGDHzrW99CZWUlAKCxsRE5OTlt/6iY0tJSNDY2tt3j9h3wteOB5cuXo7a2Fu+++27MtWwZ4549e/Cb3/wGM2fOxIMPPoh3330Xd999N3JycnDzzTe39dNtHOY4S0pKbNf9fj+Ki4uPi3HOmjULwWAQgwYNQs+ePfHVV1/h5z//OSZPngwAWTFGJ5kaU2NjI/r37x9TB1879dRTO6T/qdDa2ooHHngAN9xwAwoKCgAcn2PMKoOWbUybNg3bt2/HW2+91dVdySj79u3DPffcgzVr1iA3N7eru9NhWJaFqqoqzJ07FwBw/vnnY/v27Vi0aBFuvvnmLu5dZvjTn/6EZcuW4fnnn8e5556Luro6zJgxA+Xl5Vkzxu7O0aNHMXHiRCil8Jvf/Karu5OQrFpy7NOnD3r27BnjDXfw4EGUlZV1Ua9SY/r06Vi9ejVef/119O3bty2/rKwMkUgEgUDAdr85xrKyMtfvgK91NVu2bEFTUxMuuOAC+P1++P1+vPHGG1iwYAH8fj9KS0tP+DECwOmnn44hQ4bY8gYPHoyGhgYAup+J/r2WlZWhqanJdj0ajaK5ufm4GOf999+PWbNmYdKkSRg6dChuuukm3HvvvXj88ccBZMcYnWRqTCfCv2E2Zp988gnWrFnTNjsDjs8xZpVBy8nJwTe/+U289tprbXmWZeG1117DqFGjurBnyaOUwvTp0/HCCy9g7dq1MdP1b37zmzjppJNsY9y9ezcaGhraxjhq1Chs27bN9o+N/zE6H7BdweWXX45t27ahrq6uLVVVVWHy5Mlt70/0MQLAt771rZgjF/X19TjjjDMAAP3790dZWZltnMFgEJs2bbKNMxAIYMuWLW33rF27FpZlYeTIkZ0wisSEw2H4fPbHSM+ePWFZFoDsGKOTTI1p1KhRWL9+PY4ePdp2z5o1a3DOOeccF8uNbMw+/PBDvPrqqzjttNNs14/LMXaIq0kXsnz5ctWrVy+1ZMkStXPnTjV16lRVVFRk84Y7nrnrrrtUYWGhWrdunfrss8/aUjgcbrvnzjvvVBUVFWrt2rXqvffeU6NGjVKjRo1qu84u7VdccYWqq6tTL7/8svra1752XLm0OzG9HJXKjjFu3rxZ+f1+9fOf/1x9+OGHatmyZSovL08999xzbffMmzdPFRUVqf/6r/9SW7duVd/97ndd3b/PP/98tWnTJvXWW2+ps88++7hx27/55pvV17/+9Ta3/RUrVqg+ffqoH//4x233nIhj/PLLL9X777+v3n//fQVAPfHEE+r9999v8/DLxJgCgYAqLS1VN910k9q+fbtavny5ysvL6zS3/URjjEQiavz48apv376qrq7O9iwyPRaPtzFmnUFTSqn/+I//UBUVFSonJ0eNGDFCbdy4sau7lDQAXNOzzz7bds/hw4fVD37wA3XqqaeqvLw89a//+q/qs88+s9Wzd+9e9Z3vfEf17t1b9enTR/3oRz9SR48e7eTRJI/ToGXLGF988UVVWVmpevXqpQYNGqQWL15su25Zlnr44YdVaWmp6tWrl7r88svV7t27bfd8/vnn6oYbblD5+fmqoKBA3XLLLerLL7/szGHEJRgMqnvuuUdVVFSo3NxcddZZZ6mf/OQntofeiTjG119/3fX/4c0336yUytyYPvjgA3XJJZeoXr16qa9//etq3rx5nTXEhGP8+OOP4z6LXn/99eN2jD2UMo70C4IgCMIJSlbtoQmCIAjdFzFogiAIQlYgBk0QBEHICsSgCYIgCFmBGDRBEAQhKxCDJgiCIGQFWWvQjhw5gjlz5uDIkSNd3ZUOozuMEege4+wOYwS6xzhljF3HcX0O7cknn8QvfvELNDY24rzzzsN//Md/YMSIEUmVDQaDKCwsREtLi01/LJvoDmMEusc4u8MYge4xThlj13HcztD++Mc/YubMmfjpT3+K2tpanHfeebjyyitjxDAFQRAEATiODdoTTzyBO+64A7fccguGDBmCRYsWIS8vD88880xXd00QBEE4Djku46Fx5OnZs2e35bUXefrIkSO29VwOPcIRn7ORYDBoe81WusM4u8MYge4xThlj5lFK4csvv0R5eXlMdAfnjccdn376qQKg3nnnHVv+/fffr0aMGOFa5qc//WlcMU1JkiRJknTip3379iW0HcflDC0VZs+ejZkzZ7Z9bmlpQUVFBfY1NBxXm5aCIAiCN4LBIPpVVOCUU05JeN9xadBSiTzdq1cv9OrVKya/oKBADJogCEIW0KNHj4TXj0unkGyIPC0IgiB0LsflDA0AZs6ciZtvvhlVVVUYMWIE/v3f/x3//Oc/ccstt3R11wRBEITjkOPWoP3v//2/8Y9//AOPPPIIGhsbMXz4cLz88ssoLS3t6q4JgiAIxyHHtVJIOrSdZA8EZA9NEAThBCYYDKKwqKhdZZLjcg9NEARBELwiBk0QBEHICsSgCYIgCFmBGDRBEAQhKxCDJgiCIGQFYtAEQRCErEAMmiAIgpAViEETBEEQsgIxaIIgCEJWIAZNEARByArEoAmCIAhZgRg0QRAEISsQgyYIgiBkBWLQBEEQhKxADJogCIKQFYhBEwRBELICMWiCIAhCViAGTRAEQcgKxKAJgiAIWYEYNEEQBCErEIMmCIIgZAVi0ARBEISsQAyaIAiCkBWIQRMEQRCyAjFogiAIQlYgBk0QBEHICsSgCYIgCFmBGDRBEAQhKxCDJgiCIGQFYtAEQRCErEAMmiAIgpAViEETBEEQsgIxaIIgCEJWIAZNEARByArEoAmCIAhZgRg0QRAEISsQgyYIgiBkBWLQBEEQhKxADJogCIKQFYhBEwRBELICMWiCIAhCViAGTRAEQcgKxKAJgiAIWYEYNEEQBCErEIMmCIIgZAVi0ARBEISsQAyaIAiCkBVk3KA9/vjjuPDCC3HKKaegpKQEEyZMwO7du233tLa2Ytq0aTjttNOQn5+P66+/HgcPHrTd09DQgKuvvhp5eXkoKSnB/fffj2g0munuCoIgCFlCxg3aG2+8gWnTpmHjxo1Ys2YNjh49iiuuuAL//Oc/2+6599578eKLL+LPf/4z3njjDRw4cADXXXdd2/WvvvoKV199NSKRCN555x38/ve/x5IlS/DII49kuruCIAhCltBDKaU6soF//OMfKCkpwRtvvIHRo0ejpaUFX/va1/D888/j3/7t3wAAu3btwuDBg7FhwwZcdNFF+Otf/4prrrkGBw4cQGlpKQBg0aJFeOCBB/CPf/wDOTk57bYbDAZRWFiIlkAABQUFHTlEQRAEoQMJBoMoLCpCS0tLwud5h++htbS0AACKi4sBAFu2bMHRo0cxduzYtnsGDRqEiooKbNiwAQCwYcMGDB06tM2YAcCVV16JYDCIHTt2uLZz5MgRBINBWxIEQRC6Dx1q0CzLwowZM/Ctb30LlZWVAIDGxkbk5OSgqKjIdm9paSkaGxvb7jGNGV/na248/vjjKCwsbEv9+vXL8GgEQRCE45kONWjTpk3D9u3bsXz58o5sBgAwe/ZstLS0tKV9+/Z1eJuCIAjC8YO/oyqePn06Vq9ejfXr16Nv375t+WVlZYhEIggEArZZ2sGDB1FWVtZ2z+bNm231sRck3+OkV69e6NWrV4ZHIQiCIJwoZHyGppTC9OnT8cILL2Dt2rXo37+/7fo3v/lNnHTSSXjttdfa8nbv3o2GhgaMGjUKADBq1Chs27YNTU1NbfesWbMGBQUFGDJkSKa7LAiCIGQBGZ+hTZs2Dc8//zz+67/+C6ecckrbnldhYSF69+6NwsJC3HbbbZg5cyaKi4tRUFCAH/7whxg1ahQuuugiAMAVV1yBIUOG4KabbsL8+fPR2NiIhx56CNOmTZNZmCAIguBKxt32e/To4Zr/7LPPorq6GgAdrP7Rj36EP/zhDzhy5AiuvPJKLFy40Lac+Mknn+Cuu+7CunXrcPLJJ+Pmm2/GvHnz4PcnZ4PFbV8QBCE7SNZtv8PPoXUVYtAEQRCyg+PmHJogCIIgdAZi0ARBEISsQAyaIAiCkBWIQRMEQRCyAjFogiAIQlYgBk0QBEHICsSgCYIgCFmBGDRBEAQhKxCDJgiCIGQFYtAEQRCErEAMmiAIgpAViEETBEEQsgIxaIIgCEJWIAZNEARByArEoAmCIAhZgRg0QRAEISsQgyYIgiBkBWLQBEEQhKxADJogCIKQFYhBEwRBELICMWiCIAhCViAGTRAEQcgKxKAJgiAIWYEYNEEQBCErEIMmCIIgZAVi0ARBEISsQAyaIAiCkBWIQRMEQRCyAjFogiAIQlYgBk0QBEHICsSgCYIgCFmBGDRBEAQhKxCDJgiCIGQFYtAEQRCErEAMmiAIgpAViEETBEEQsgIxaIIgCEJWIAZNEARByArEoAmCIAhZgRg0QRAEISsQgyYIgiBkBWLQBEEQhKxADJogCIKQFYhBEwRBELICMWiCIAhCViAGTRAEQcgKOtygzZs3Dz169MCMGTPa8lpbWzFt2jScdtppyM/Px/XXX4+DBw/ayjU0NODqq69GXl4eSkpKcP/99yMajXZ0dwVBEIQTlA41aO+++y6eeuopDBs2zJZ/77334sUXX8Sf//xnvPHGGzhw4ACuu+66tutfffUVrr76akQiEbzzzjv4/e9/jyVLluCRRx7pyO4KgiAIJzAdZtBCoRAmT56M3/72tzj11FPb8ltaWvC73/0OTzzxBC677DJ885vfxLPPPot33nkHGzduBAC88sor2LlzJ5577jkMHz4c3/nOd/Czn/0MTz75JCKRSEd1WRAEQTiB6TCDNm3aNFx99dUYO3asLX/Lli04evSoLX/QoEGoqKjAhg0bAAAbNmzA0KFDUVpa2nbPlVdeiWAwiB07dri2d+TIEQSDQVsSBEEQug/+jqh0+fLlqK2txbvvvhtzrbGxETk5OSgqKrLll5aWorGxse0e05jxdb7mxuOPP47/+3//bwZ6LwiCIJyIZHyGtm/fPtxzzz1YtmwZcnNzM119XGbPno2Wlpa2tG/fvk5rWxAEQeh6Mm7QtmzZgqamJlxwwQXw+/3w+/144403sGDBAvj9fpSWliISiSAQCNjKHTx4EGVlZQCAsrKyGK9H/sz3OOnVqxcKCgpsSRAEQeg+ZNygXX755di2bRvq6uraUlVVFSZPntz2/qSTTsJrr73WVmb37t1oaGjAqFGjAACjRo3Ctm3b0NTU1HbPmjVrUFBQgCFDhmS6y4IgCEIWkPE9tFNOOQWVlZW2vJNPPhmnnXZaW/5tt92GmTNnori4GAUFBfjhD3+IUaNG4aKLLgIAXHHFFRgyZAhuuukmzJ8/H42NjXjooYcwbdo09OrVK9NdFgRBELKADnEKaY9f/epX8Pl8uP7663HkyBFceeWVWLhwYdv1nj17YvXq1bjrrrswatQonHzyybj55pvx6KOPdkV3BUEQhBOAHkop1dWd6AiCwSAKCwvREgjIfpogCMIJTDAYRGFREVpaWhI+z0XLURAEQcgKxKAJgiAIWYEYNEEQBCErEIMmCIIgZAVi0ARBEISsQAyaIAiCkBWIQRMEQRCyAjFogiAIQlYgBk0QBEHICsSgCYIgCFmBGDRBEAQhKxCDJgiCIGQFYtAEQRCErEAMmiAIgpAViEETBEEQsgIxaIIgCEJWIAZNEARByArEoAmCIAhZgRg0QRAEISsQgyYIgiBkBWLQBEEQhKxADJogCIKQFYhBEwRBELICMWiCIAhCViAGTRAEQcgKxKAJgiAIWYEYNEEQBCErEIMmCIIgZAVi0ARBEISsQAyaIAiCkBWIQRMEQRCyAjFogiAIQlYgBk0QBEHICsSgCYIgCFmBGDRBEAQhKxCDJgiCIGQFYtAEQRCErEAMmiAIgpAViEETBEEQsgIxaIIgCEJWIAZNEARByArEoAmCIAhZgRg0QRAEISsQgyYIgiBkBWLQBEEQhKxADJogCIKQFYhBEwRBELKCDjFon376KW688Uacdtpp6N27N4YOHYr33nuv7bpSCo888ghOP/109O7dG2PHjsWHH35oq6O5uRmTJ09GQUEBioqKcNtttyEUCnVEdwVBEIQsIOMG7YsvvsC3vvUtnHTSSfjrX/+KnTt34v/9v/+HU089te2e+fPnY8GCBVi0aBE2bdqEk08+GVdeeSVaW1vb7pk8eTJ27NiBNWvWYPXq1Vi/fj2mTp2a6e4KgiAIWUIPpZTKZIWzZs3C22+/jTfffNP1ulIK5eXl+NGPfoT77rsPANDS0oLS0lIsWbIEkyZNwt/+9jcMGTIE7777LqqqqgAAL7/8Mq666irs378f5eXl7fYjGAyisLAQLYEACgoKMjdAQRAEoVMJBoMoLCpCS0tLwud5xmdoq1atQlVVFb73ve+hpKQE559/Pn7729+2Xf/444/R2NiIsWPHtuUVFhZi5MiR2LBhAwBgw4YNKCoqajNmADB27Fj4fD5s2rTJtd0jR44gGAzakiAIgtB9yLhB27NnD37zm9/g7LPPxv/8z//grrvuwt13343f//73AIDGxkYAQGlpqa1caWlp27XGxkaUlJTYrvv9fhQXF7fd4+Txxx9HYWFhW+rXr1+mhyYIgiAcx2TcoFmWhQsuuABz587F+eefj6lTp+KOO+7AokWLMt2UjdmzZ6OlpaUt7du3r0PbEwRBEI4vMm7QTj/9dAwZMsSWN3jwYDQ0NAAAysrKAAAHDx603XPw4MG2a2VlZWhqarJdj0ajaG5ubrvHSa9evVBQUGBLgiAIQvch4wbtW9/6Fnbv3m3Lq6+vxxlnnAEA6N+/P8rKyvDaa6+1XQ8Gg9i0aRNGjRoFABg1ahQCgQC2bNnSds/atWthWRZGjhyZ6S4LgiAIWYA/0xXee++9uPjiizF37lxMnDgRmzdvxuLFi7F48WIAQI8ePTBjxgw89thjOPvss9G/f388/PDDKC8vx4QJEwDQjG7cuHFtS5VHjx7F9OnTMWnSpKQ8HAVBEITuR8bd9gFg9erVmD17Nj788EP0798fM2fOxB133NF2XSmFn/70p1i8eDECgQAuueQSLFy4EAMHDmy7p7m5GdOnT8eLL74In8+H66+/HgsWLEB+fn5SfRC3fUEQhOwgWbf9DjFoxwNi0ARBELKDLjuHJgiCIAhdgRg0QRAEISsQgyYIgiBkBWLQBEEQhKxADJogCIKQFYhBEwRBELICMWiCcKLj8wHbt9vzDhxov1woRGUBYMIE4E9/oveWBfzkJ0B9PWDGIPzJT+jaf/4n8PLL9L65GQgEgGgUWLcO2LED2L9flykrA1auBH75S/rc2goMGUKvJslovUaj9s/ptJ3uuH0+e7rgAv1+2TJg7Vqqx+cDhg2Lvd9MBQXA+PHAE0/QeM48U1+bOtV+b3k5MGIEUFcHXHUVcO65wFtvAVOm6LbvvJPG5PMBt95Krzk5VIbrKSvT12+7zV4mmcT1jhmj87Zu1WPgcSWqg78X87vivp57rv3eoqL2/31AzqEJwonP9u3AgAFAbq63cpZFhq9vXzJuOTmUACASAfx+euV6IxF9nR/qgYB+cLW20r3RKJUFgGAQyMuj95zH95m45bVHOm3v35/euOvq6CHc1ATceCPdGw7T9YIC/VBvbKT6CgqoveXLgYYG4JJLqN6PPgIqKyn5fFQ+EAC++AI4fJge5EuXUvlLLtEGe/9+4MILgc8+A4YPB4qLaWx9+tDfND8fePxxYPZs4PnngeuuA/7yF2pn505g3Djqb9++ut/5+WQcR4wAamvJ6LW2Utq5k/o1ZAiwejUZwIED6VowCHz1FfCNb+jv6KOP6Fp5OX1+5BEaBwAsXkzGrKBA98HnA3btAgYNoteqKurLFVfQnxJAISAHq8WgCd0Ky7I/jAH9EOeHMQDs2QOcdRY9pIqK6J6cHH09mXZ8PrsBSXSfl2tu+W556bSd7rh//GPgvPOAa68Ftm0Dhg7VhpFnkpYFbNkCnHIK0LMnzTqWLaMZWFER0K8f3Z+bq2eNbJz9fnotKqKH/vvv0/uTT9Zjr6ig9/x3cP6NQyGqhz83NFAdTrUltx8SkQi9hsNUN5fZsiW275EItcvvfT79dwmHKR08SOP/8Y+B+fNpNv3VV/RdcL9DITLMe/ZQG6EQXf/xjxEcOBCFU6e2a9CgspSWlhYFQLUEAkpZliRJ2Z+iUaWA2LxTT1Vq40ad19hIeZal1IIFSi1frlRtrb7fspSqqqK8Z56hz+GwLv/MM0o9/zzlrV2rr+3fT69r1+p7hw9X6p139OdIhPLCYaVuv12pvXuVWrnS3ufcXMqrrtZl+Fp1tVKbN1NeOm2nO24gfqquVsrvV6qkROfV1tJrWZnOGz6cXisr6fX225UaPVrXkagNv1+pmhp6v2uXzq+rU2rqVBqb2Z9f/tJe3ixTU0PXm5vtZaZOpfcVFfH7wWPIz1fqmmvo/fr19jHcfnvisThTbS2Nz8hrAeh53tKS8Lkve2iCkC3wL2VzthGJAPfeC5SU0L4HQO/vvZfeh0K0dPTPf9J+Es8QduygvD596L7cXKC6mn6t9+lDM5LcXOBrX9O/8MvLgb17KY8ZNIh+rU+bRp/9fsrLyQHGjqWlO16qYiZOpLzevXUej6l3b73slk7b6Y6bsSx738eOpf4/+yywfr3Ov+ACYNYsWoJkQiFaAiwrA156iWbMzc10bckSeq2vBx56SN//9NP03rmfaPbn29+m9pglS2iPLhHjxwP//d/2Mt/+Nr1vaKA9zoceov4C1F/uE6dVq6i/n3xiH8NZZ+l+u/V31ixaMmcuuIC+P4bbTIZOmjB1OjJDk9RtEs9OzOScqbmlaJRmQmvX0i/25ct1PqDU+PE0k+G82bPp9fbbdR0NDXoGtXatUtu2KXXoEH2ORJQaNIjy5s+nvHCYZi7mrMuylFq4sP3+Osuk03a6425vljF4sJ698Izq1Vft9/BszG1Glp9Przyzcpsl3XILvZaU0MwSUGrcOPssidPUqbqNwYPpulnGnFHyfWYZZ+LZmHNmyrNMs7/O2aH5vTgTz1Yd31VLz55JzdBkD00QhPiYM75Ee1LJlO9s0mm7vXEHgzTjy8mxz4xDIZoB8wyqtZX2n/hzNKqdTHivKRy2z/rCYSrDs79w2O64wvCskvN5L43fh0L2esz2eU/MWYadYsJh7VBjvud9S79fO934fHoM3JZl6f07y6LrgO5PJKK/q/37ac+Mv0P+LqJRSnl5CAYCKOzTR8SJBaFbYVmxLvF79lCe+WBz3sNlnWXMB3myxsFcDovn9JGI9vqW6bZTGfe+feThGA4Dc+bQg3rOHHpAB4PkdBIO6+99717dDj+Qc3KADRuozXBYl2HPTdPZgg1RMKhfmUiElgUty564HjYulmV3HnGWiUS0EcnN1WUKCiiP+x+J0Lj27aP+mmMwHWOiUeonj2HbNrqXvzP+rvr2pe9v2zaqZ/du3VZrK/WzoSHRX13TKet/XYAsOUrqdikcJmePRx/VeR99ZF/SsSxyEOH3/Dplil5a4/zeve1LlwAt1wG0nMdLeIEAlRs+XKlJk+iaWc+jj9JrNKpUayu951ezD5all/2mTIltm1+5zUy0neq4Ey03chkvKdNl7rwzcdmFC/X7MWOSK2MmLmOmefOSK2u2k2SZZJ1CxKBJkpRNaeNGpfbs0Z/Nh+/06ZQXDGqjx/tH27bRfhKX691bqTff1GU4LxpVatUqut+ylFqxQu9jvfMOeSDyNcuidvbsoTKWReUnTaLXPn0ob84c+ximTKE62MuR8yxLqbvu0m2m03a64+bvtKaG9n0GDIhvaMaO1fea+TU1SoVCusysWboeLjN3rlJPPx3foC1aZK+3poaS6cX40EOxbdfX6/e1tbFlnPdwWrSI6qutpb5XVlIaO1apl16yjwGgvtfXUxnO43a4jNvYZs0Sg2YiBk1St0s8yzANAc94pk/XD0HL0u8vvZTc0VetinV6WLWKXs3ZS0MDvbLTxPz5dmeNu+7SThhmPeasEdCzpOpqKmOOA6C88ePpMxtly6K822+nvHTaTnfc/LAdNix2xhMIJDdTmTBBu/EvXdr+/bm5sQaNDRLnLV2q1Pnn2/vDhsj8bJaxLLo+YYL9nvPP1++5n87+Tpig+2V+L4mS2Tf+/tpJyRo0cQoRBCEW3qBP9pBze/WkWj4dUmk72XHzPp95cJmlxHJzyT2/qAg4dIgcJ/LztQMJyztxmbw8rU7CdZsyUIDenzIPkvv9tE9lOq+Yeexcwge12VGEHTlMCa94ZQC9f2dSUGDfU2MnF97DDId13dx2a6t2LuHD/+ae5/79+rtqbqZD1scOdSfrFJLgmL0gCCccTU202W6yeTNJFrFnW2sr6e6NGGF3PDDhB47bw58dFnJy7JJV7AHnlmfWE43qsvzQNB/U7D3HzgkmZt3ptJ3uuBcvJieOyy8n+alhw+ghXFJCf4OLL6Z7pk4lLcm+fYGLLqK/xYYNwE030d+jro70EMNhqjsvj+rx++kBf8kl9PnQIeC998i5ZOJEer3iCjJQlkVKHACd3wsG7VJSfj+Naft2qnP0aCrT2EhGo08f7RhSV6fLRCJ0ruyss/TfZPNmktaaMIH6lZ9PdWzdquW7AHLi4DGwGsiuXfSdDRtGmpjXXEPfXb9+NJ4xY/R3NWkS1TN/PpVfsSL27+FGp6z/dQGy5Cip26WPPop1CuHlOnaOsKxYp5CqKtoncjpHsGNGNKrrYecIrp+dJCxLO2aUlNBnXu6cNEnXw0uNvISXm6v3x8wy5nIYL+FxvzLVdjrj5v6xU4MXhwq35OZkkWwZcwnP7Fuyics4lymTKZPuGDi18/3JkqMsOQrdBXMZiUlmWc/UeGS4TCCgZ3Tm0hP/6u7b114Hv8/J0UtfgD7XxDMjgMqXl8fOnBLpMrqNKd220xn35s00aykuppSfr+s0l+Py8mjWcd999Hn5cpoB9e3rTdi3slKLEw8YQH/zigoqz8r5LE68dSstd+7dS7Or554jlZi//IVc7UeNSk3QOBCgfu/dS+oeH38M9O9PszEeAy8bcvnaWloJeOQR+g769vUm5HxsphZEcuLEMkOTJOlETzzraG4mp4W5c+36iAB59V16KX12c5k3Zx/NzeSBuG6dPZ+9EvmXM890Um2bZ5Lr1mntRC7DTgxmXiRC9axcaXcGYRUP9lw0y/Tpo+sxnUvSbTvRTGXYMHfHjEQpEEitDDtkmM4abg4ezmS2s3SpUgMH2h1FzO/BLQ0bRm2a7ViWez2mcwkn05Ekif6KlqMgdBcsi37RFhXRHs64cfrwLl+fOJH2bfhzVZXeN3HOVgoK6Be3OXtraqI8hn9Jp9P2Qw/RzGD0aNonYYYPp30WbjsapbxoFLjnHtJgdB58fuih2P0wy6J+V1frPvM9mWzbSXk5HRKeMyf2WjxmzIgtU1tLr2Vl7mU4WsDo0cCpp9I4X3+dQrvMnEn38Gxz8GB72csu0++HDaP9q/p6nVddTbNAN/x+4P77aUb1zDM6f9kyXQ9HAgCAX/zCPvsePlwrqIwfb+8v/61uv9297XYQgyYIJzoTJtDDp7VVC+6Wl+vr1dUkumsK7u7YQe/dPPraEzSePp2CS/p86bW9fr13UWH2nHPiJnLs8wGPPmoXOZ44MbNtWxYtyZniupMn28WJx46N7a+JF0FjwC4QHAqRc8Y995AY8A9/aBcIZkHj7343fvteBY3ZM3HRIrug8eTJuh5W9njoIXL8ML0Z0xU0TkQnrQB2OrLkKKnbpOXLaSmPHRvGj7cL7s6eTYdZzfNWpiMEJ3OZLV5ylkmn7WeeUZ5FhefPV+rFFylv4UJ7eBfLSl7kON224y2PmeK65kFlv59EgRMtAyYjaGwmN0Hja67RoWBMgWBnCJdUBI0BHV6Hy5iCxrfcEhu6pr2UpKCxOIWIU4jQHUn27FVHnAXLVNsngqAxi+3m5WkHB6e4rnmui2cokYg+WuFV0JiFfc0lYhYOZscX80wZO8WYeXxUIhVBY8B+zXS2cZ6Bc9bDefydeRE0zslBMBwWcWJB6BY4vfWcQsTxzlQB5GHmVdCYRW7TbdskXUFjN9oTIk6n7TlzgBdeIC/DOXPo4b1vnxbpZbFfU5w4ECBjduaZ3gWNWdh39279Gg5rgWDuO5flvxELBPPZO/4bpiJonJtLZcx+RSJa1BjQBsmsx7KoD9u2eRc0ZuPlPNgdj05Z/+sCZMlRUrdJs2fTsp9l6bNVvHzI4sScZ56tYm/GVAWN020bSF1U2EzRaOx5Nm7HFDl2nmdLp23+btzEdc3ryaZMlUlWINgtpSJOnO75OxEnTg4xaJK6Tdq1Sz+Qa2tJXNfUQWTBXRbptSx60Eci5LbuVdDY1IpMp+1Fi7yLCjt1HzmtXGmvg9vZtk2XMQ9Hp9s2fz9u4rqWFSvSC9gFglMVNAZIFPjpp3WZAQPsAsFz5+q9PE6mQHCqgsZmYtd8s0xNDbXjJsJs9j0FQWMxaGLQJHWXxGK9lqUFdxcssM86WJzYzDMf8F4EjR99VKkZM+jhlIm2vYgKDxoUK0rM/XcTOa6udhc5Trdtfvi6ietyGWdynsdKRdAYsJ85cwoWx6snkThxsoLGzjE6z6HFO383cKD9cwqCxuIUIk4hgmCnvSjMx0vbJ4KgsVOcOIUozACSFzS2LMoznSUKCrSQcbKRrwG7qowXQWMTjljtJfK16USSrKDxsTLBYBCFxcUiTiwI3QpTzsr5EHaeNeOHn1dBYxbS5Y3/dNr2KipsOh+YxMs3+wXQQ5gfiOm07RQnLinR4ro5OcDdd+t7rruOzsM99hgdRL74Yi3Sm6ygcVERlWH5LRYIfvVVOsyel0cGMRymvxtAzhutrcDq1XSm7J136MAzixN7FTTOyaE6WRj5wAGqr6mJ/p2YMlaRCPDFF8App9AYt2+nQ98sypysoPGvfkVluK/t0Snrf12ALDlK6jbJdMywLG9RmN2cQpIRNAYoL522gdREhZ1tmm2ZeUDiyNfptO1cFkshCnOHCBqbf59EqSMEjTMV+VqWHGORJUeh2+CcEfHyEefx5wMHtLiumxiwF0HjTLTNy29eRIWjUe3S7TaGZEWO2R091babm2lceXla2HfPHpqlhMMk72XOtu67j/LfeovK9+3rTdB4yBAqEw7rUC4lJcDf/04CwcuX02ynspKWLPfupZkdCwQPHw4sXUoqHgMGpCZofOAAcOON2s2+vp7q4VkjLxmGwzTb4nquuoraOessmrV5ETSeORPo2xfBgwdReO65Ik4sMzRJWZ84irJlpRaFGdDejJalZyrV1XrmFgrZf5lnom0W+121KtYx4667yAnDsvRsrrXVPpM0k9fI1+m2zd8bO2GkEIXZlrwIGjsdKrxGvk5HVJgTt+M18nWKYsoiTiwI3YUBA/SsIzeXfgEvXqyvDxpEeQsW6P0NFhUGaEbC2nuAruuZZ2gmAtgPtvKsbPv29Npmsd+BA+3iuZYFPPmk1pEEKKSJKWjs5Ac/AP7lX+x5JSWUx/uDpipFJtouKyNR4aoqu6iwqWWZLF4EjdkhhbUWZ860ixPffjvteQFa95HHWFGRuqiwybBhNFszBY2XLSMHErNuZz2piikniSw5CsKJjrns51wSjJfH7N+vnTv4YWLCXmqWRRGTR4ygfDZm7KmWStu8HAnYHUXM6+ZSIC8BxqsLSD7ydbptb9yol9r2708tCnNJCYkqc5kzzqDve9Uq7UiydKlus7aWDMjYsXRfcTF99hL5mr+XigpaIuWI1bxsajpz5OZqgzVkCH1fn34KHD5MhpIVUdwiX9fWxka+Liigf0PjxpGDCve3qYmWO1klZfVq4MEHqb/NzUA4jOBnn6HwX/9VlhxlyVFS1qdDh0h5IxJRbYK7DQ36ekmJUitW2AV3OdozLxV5ERVesIAOxPLS5Nat9v7s399+n4PB9NtOZ9xmX5IVNDY/83IYi/eOHq3z6uq0QDBAosGJlgDTETQG7ILGNTX2epwHrJ3Ji6BxWZldnHjwYBI0vvFG3bYpTszfjd9vX5pMQdBYnEJkhiZ0FwIBPcviGZE5w+CZCmBf+snN1bOMUEhv9gPaOcKcvZgzFeeyY7xZWDwsSzuKpNp2OuM2STSLjMdf/kIziTvvpGVLL9Gneda1eDHN7NiRpG9fu+v8rl20nPnWW+Qmz/BMramJlv28RIBuaKDZWiRCfWNHEnZOCQRohnb4MM2ali6l8hwtG6DXCy+kWWUqka/HjdPj5X7n59M4R4yg8ZWVUZ2trcDOnQg2NqJw3rx2Z2hi0AThRKc9z75E3ouBgA4UaS4ftke8A8jO5Tu3s2l79pDHW7ptpzPueNeSPVj99tvU9379qO+5uXp/kN/zAWGAHtzhMJ2nOvdc4Mc/pqXHHTvICPK5K8siA19cTN9TURF9PvNMKnPeecAHH9DrtdfSvtvQoXaBX+7zli20dNizJ7W5bBnV4+w3H8421fhbW/Uy4Pvv0/uTT6a6o1G918Z/B+ffOBSyn+draKA6nMvabj8keL82HKa68/PpeV5WJkuOsuQoKevT2rV6GY2X+9au1deHDyfdQv4ciVBeOExLeMuX05KQZWlPRF5aeuYZ+mwu0z3zjFLPP2/P4+VHs1/RKJ1x27hR5zU2Up5lpd92OuO+/Xal9u4lT0ezz7m5lMdSYOZSY3U1aT+a0lfOZT9ewgN0fDFeenMu4bWXamtpuc5LmepqKsPLeqYnInsWmv3lJUmOSWb2N17y+7U+o7lEWlcXu3xYXR0T2yxG/9FcnuUyU6fS+2NLoOLlKAjdha99Tf/S9RqFORTSkaYnTNC/0nfsoDwz0nR1Nf1i7tOHZgXmr2uenXiJfJ1u2+mMe+xY9yjXEydSnhnlmsfUu7dedjOjRvNY0onCbFkUndqMfH3BBRTFmuE2zTImXiJfh0JUX1kZjeGss7TTCPe3vp48O/l+7rsZbsfZHy+Rr5nx4+2Rr5csoXoAmtktWgTcf3/iOphOmjB1OjJDk9RtUkODnkl4jcK8ciWV2bXL7pgBkLMGnzNjZ41o1O6s4eYA4pypuaVoNP220xm32Zd0nEJ4NuacJSURhdnmzJHIUcQ5O2pvlpZM5GvTicUt8rXZX3MMnG65RTt4eI18vX69vYw5o+T7zDLiFCJ7aILQRjrCvieKoHF75TMN7zWxSK/XKMwsGuxV0Jjr4KjT5sw42cjXvF+ZrKCx6bDD8Gw6lcjXXgSNj70PBoMSsVoQui2pRGE28zhitVm2PePgNfJ1Jttm0o183V7fTDgCtNcozBx9Ohy2R6xmOaxt26ie3bt1262tOlr0vn3k4cjlvUS+ZmOQk2PvL5dhr1HTwYUNUTCoX83vwGvka8uKLcNRr/keLlNQoCXHkqFT1v+6AFlylNRtUkkJSTBFo6lHYfYqKuw8zxUOpx75OtW2MzFus41kBI25TR6Tm7huKuLE6USadn7HXV2mPXHiFASNuyzAZzQaVQ899JA688wzVW5urjrrrLPUo48+qizLarvHsiz18MMPq7KyMpWbm6suv/xyVV9fb6vn888/V9///vfVKaecogoLC9Wtt96qvvzyy6T7IQZNUrdJK1boPR6vUZh572rbNtrL4jIcadrUeOzdm8qsWhUbHdqyvEe+TrftdMbdpw/lzZljHwNHuTYDnk6ZQq933aXbXLSIIirX1qYchbnN24/LuD3MZ82K/6CvqUk98nVNjV2f0+wvl5k7l/odz6ClGvm6vl6/r62NLeO8pysN2s9//nN12mmnqdWrV6uPP/5Y/fnPf1b5+fnq17/+dds98+bNU4WFhWrlypXqgw8+UOPHj1f9+/dXhw8fbrtn3Lhx6rzzzlMbN25Ub775phowYIC64YYbku6HGDRJ3SY5Izh7icJ86aWpCRqbosSWpe/xEvk63bbTGTfgXdB4/HgdodspBpxCFOaMCBo760k28rVXQWNnZGweYyqRr70KGpeVdZ1TyDXXXIPS0lL87ne/a8u7/vrr0bt3bzz33HNQSqG8vBw/+tGPcN+x8AgtLS0oLS3FkiVLMGnSJPztb3/DkCFD8O6776KqqgoA8PLLL+Oqq67C/v37UZ6E+Kc4hQjCMVKJAM3XuyJ6dKba7si+8z6SGfYl2SjMra3a6YEPopt7fxxOJT+fXOmLi9sOGLftRQH2g8vJRr72+bQjB0eiZlUWwH7Imu/jPUHzELsZ5Zq/12QjX5t/W/6u3MoAbft3wWAQhRUVnR+x+uKLL8bixYtRX1+PgQMH4oMPPsBbb72FJ554AgDw8ccfo7GxEWPHjm0rU1hYiJEjR2LDhg2YNGkSNmzYgKKiojZjBgBjx46Fz+fDpk2b8K//+q8x7R45cgRHjhxp+xw0Ny4FIZtxi7icbBRmc/PfhB+0bg9/dhpwer55jXydbtvpjNt8aCYraGzW/corOmp0c7O3KMy7dumI1V4FjVevpnNeZrRsL5GvN2zQUaOTFTQ+dIhEhffupXNuHLE6FEot8nU8QeO6ulhB47PO0pJiyeBxRbFdvvrqK/XAAw+oHj16KL/fr3r06KHmzp3bdv3tt99WANSBAwds5b73ve+piRMnKqVo2XLgwIExdX/ta19TCxcudG33pz/9qcKxaamZZMlRUtYn00EjlSjMVVW0R+V0zGDniGhUL72xY4azDx99lFrk63TaTnfcubl6f8wsYy6H8RIe94vvdS69mSnJKMyuyUu0Z3Yk6YjI18mW6YjI1y4p2SXHjM/Q/vSnP2HZsmV4/vnnce6556Kurg4zZsxAeXk5br755kw318bs2bMxk2PqgGZo/fr167D2BOG4obZWzxzeeUdHYQbs+nxLluilpIYGLZhrukTzzIJnHU8/rZey+JyQGTuNl5HOOgv4/HPKM89Rcd2sODFihHbvT7ftdMfNmDM5p3s4Lyk6+/r00zRTCYfJzd5LFOb77qNZUzxR4R/8IFZUmGdqAIWuCQa13mN+PvDYY8lFvh40iOr0IqZcWanFiQcMoL95RQWVLyujWVtrK/2NNm9uP/K1KWgciegxfPhhfEHjxkZg3jy0i9cZWHv07dtX/ed//qct72c/+5k655xzlFJK/f3vf1cA1Pvvv2+7Z/To0eruu+9WSin1u9/9ThUVFdmuHz16VPXs2VOtWLEiqX6IU4ikbpdYSYO9BzkB5NXHv4hNRweeUa1bp7UTuQxv5pt5kYiO9swzpoYG0uJbsIA840x9RID6c+ml9NnNZd5so7mZ+rpunT2fvRL5VzvPslJtOxPjBsgxI50I0KYjSRKRm11nN2byEvmaUyCQWpkUo0/b2lm61P27cjiSdJmWYzgchs+x9t2zZ09Yx3419O/fH2VlZXjttdfargeDQWzatAmjRo0CAIwaNQqBQABbtmxpu2ft2rWwLAsjR47MdJcFITvw+Wgm5Nz7sSzaW6mups+8rwHoKMyjR9MeDTN8OO3N8K/9aJTyolEd7ZlnNH370q/ykhIKDWJu2lsW/YK/6Sb9uapKz3ics6KCApo1mLO7pib7HgrPXtJpO91xcz/uvz/1CNDDh2s1j/Hj7ZGb+W91++2Ii9seo5fI18yMGbFlamvptazMvQxHSUgl+vRll+n3w4bp74qprqZZYApk3KBde+21+PnPf47//u//xt69e/HCCy/giSeeaHPk6NGjB2bMmIHHHnsMq1atwrZt2zBlyhSUl5djwoQJAIDBgwdj3LhxuOOOO7B582a8/fbbmD59OiZNmpSUh6MgdFvcBHd9Pop+bAruTpxIr+vXexf2Ze80gJwiQiF6uLHQsPl/tLqaxIZNoeEdO3S/vAoaT58OXHUVlUmn7XTHDWhvw0WL7OK6kydrkV5eIn3oIXLgML0ZMyFoXFlpFzSePNkuTmw437niRdAYsIsyh0K0PHzPPdTfH/7QPgYWNP7ud+O3n6qgcTySWr/zQDAYVPfcc4+qqKhoO1j9k5/8RB05cqTtHj5YXVpaqnr16qUuv/xytXv3bls9n3/+ubrhhhtUfn6+KigoULfccoscrJYkyS0tXJh6FOZnnlGehX3nz1fqxRcpb/lyWspjhw4v0afNvphLfPGSs0w6bac7bjNyM5BSFGZbSlbQOFFKJ/J1MoLGzv6an71Evk5B0FjEieUcmiCkTqbEgDvrHFum2k62b6aTCB8F4HzzPJZTpJfzAO+CxnwWjM+M5eVppxKnoLF5rotnhZGIPlrhVdCYx2AuEbOIMI/fPFPGTjxmHjvppCBonOw5tA48HSkIwnFBe0LETLqCxvzQcvNcNOG8QMC7oLF5sDidtk1SGTcLBZuHqllgF9APc1Ok17LIM3DbNu+CxvwQj0Ror+uFF+j6nDlkBPbt0/Ww2K8pThwIkDE780zvgsYsprx7t34Nh/UYnN8//414DHzekf+GqQgau0W2dsPLcuKJhCw5Sup2KRqNPVvFHo2m4K7zbFWqwr6WRUt4ra30ns+U8XUWJ+Y880wZezOmKmicbtvpjpv7l6S4brspFXFitzJm35JNmSqTjsDy8SpOfLwgBk1St0wrV8YKB7PgLusWmgeUFy3yLuxr6h/u2qWNQW0tiQqbOogsNMz1cH8iEXKZ9ypobGpFptN2uuPmPrK7ublnVVND4rpugsD8PlVBYzO5CRpbVmw9zrZTFTTmfj/9tC4zYIB9DHPn6r08TuYYUhQ0FoMmBk1Sd0zxBHerq90Fd/nVi7DvoEFaGJjFei1LCw0vWGCf8bA4sZlnGlUvgsaPPqrUjBn0YM1E26mO2zQGznNo8c6CDRxo/5yKoLGZ3ASN49XjPAOXiqAxYD9z5hQsjldPInHiJAWNxSlEnEIEIXUy7cxhlu1oceN02k523OZeWTDoLQqz6USSrKCxswy3zXtOXiNfA8kLGluWPVo2QPtdLGScbORrQDvC8PeapKBxshGrMy59JQhCF2I+aJ2Y4rrBoHY08CrsG68NLu9mAJxnzbgdr4LGLKTLDgTptJ3OuHft0iK9Bw7QAeqmJuqzKWMViQBffAGccgqd2dq+nQ4gs0BwsoLGv/oVlQmF6NCzKU5cUqIFjXNygLvvJnHiQIAkpB59lKSxnnmGhItZGDlZQeOiIiqzfz/lsyjzq6/SYfa8PDKI4TD93QD6blpbSUx5/HiSJquo0OLEXgWN3SKJu9Ep639dgCw5Sup2yVxWM/OAxFGYUxH2ZYcM0zHDsrxFn3ZzCklG0BigvHTaTnfcziW+FKIw21KmBI1TiZadSnLrr/n3SZRSEDSWJUdZchS6CzyraC/PielizcK+XIaXysylrv37SV0jGtXu5M4ZES8fcR5/PnCAft3H61syy5DmTMqtjJe2efkt1XE3N2s3+/p6kojiGQwvGYbDNNtikd6rrqIxnHUWzdq8CBrPnKnr5mW7vDwaW34+uewXFND1qir7bOu++yj/rbeo7337UhlermxP0HjIEN02h9cpKQH+/ncaw/LlNMOsrKQly717aWbHYxg+HFi6lNRABgyg74rd/Pv0oRlqO4LGwT17UDhrVrtLjjJDkyQpm5LXKMwsuLtqVaxzxF13kSOEZelZTWurfUZlRq5OJfo0oL0ZLUvPkqqrdTuhkP3XfCbaTnfc3HeO9uw1CnOqwr5mYieMTES+Tlac2OnE4jXydYpCzl0mTiwIQhfygx8A//Iv9rySEsrjvSpTlYIFdwcOtIvIWhbw5JNaTxGg0B6msC9Av7h5ppebS7/YFy/WZQYNorwFC/SeEosKA7EhYbiuZ56hWRCgnSD4us9H+yvptJ3uuJlhw4Abb7SL6y5bRntdpmAxYBcnTlXYlykrI1Hhqiq7qHAqWrdeBI15L4u1FmfOtI/h9ttpXIDWfeQxVFSkLuScJLLkKAjZhJvDRqIozLwsB9gdJszr5nKcM0q1ueznXBKMl8fs36+dO/gBbsLLa5ZFEZNHjKB8NmbsHZhK2+mOe8cO4PBhemizOodbFOba2tgozAUFNJ5x48hZgqNGNzXR0hsrdqxeDTz4IBn25ma95Jefr5c39+9PLfJ1SQkJWXOZM86g+let0o4kS5fq8dbWUt/HjqX7iovps5fI1wD1v6KClkj5u+JlU9OBJjdX/0gYMgTBUAiFZ54pS46y5Cgp69OhQ6R+EYmoNsHdhgZ9vaREqRUr7IK7ZrRnTskKGmeqbV5y8iIqvGABHeDlpcmtW+392b+//TEEg+m3bYoTDx5M4ro33kj11tTYxYlZZNfvty+tZULQGCBBY86rq9MCwQCJBieqIx1BY8AuaFxTY6/HecDamTwIGreUlCS15CgzNEE40QkE9EyHZyXmDINnaIB9ycvLjKYj2uZZRiikHSwA7ZhhzpzMWZJz2dFrny1LO4qk2vZf/kL9v/BCmuGYUZjNWcfjj8ePwjxuHM1q2OHCsqjMW2/RbLS2lpYWW1sp7dxJ3/eQITR7u/NOWjL1En2aZ12LF9PMjh1J+va1u87v2kXLmW+9RUcTGJ6pNTXRUmu8qNuNjbFRtxsaaLYWiVDf2JGEnVMCAZqhHT5MM9WlS6n8JZcgWF+PwurqdmdoYtAE4USnPY/GRB6E8a55OWCcatuBgA4UaS4ftke8vjmXDt3Opu3ZQ5506ba9dy+Nnfd9+PCzs71QyH6uraGB2nUusboZZd47DIepbi6zZQvV0a8f9T03V+8P8ns+8Mzlw2E6+3XuucCPf0xLjzt2kBHks26WRf0tLqbvqaiIPp95JpU57zzggw/o9dprad9t6FC7qDJ/R1u20NJhz57U5rJlVI+z33w421Tjb23VS6/vvw8UFSFoWSg87zxZcpQlR0lZn9au1cuHvOS2dq2+Pnw46Rby50iE8sJhWtrZu5c8/sw6c3Mpj2WpzKXG6mrSQORlxlTbXrCAlthqa+kaeyLyct4zz9Bnc2n0mWeUev55ex4vP5r9j0bpjNvGjTqvsZHyLCv9tnlZjjUHzeW6urrY5cPq6tjYZk79R3OpkMtMnUrvzeW4eMt+vIQH6PhivCzpjEnWXqqtpfF5KVNdTWV4KZXrYe9GZ395SZLjwJn9dSTxchSE7sLXvqZ/4XuNwjx2rHuU64kTKc+Mcs2zjN69abkt3bZDIR1pesIE/St9xw7KMyNNV1fTDKBPH5oVmDManp14iXydibbNsDMmqUZhHj/eHvl6yRKqB6CZ3aJF5GlpRo3msaQb+XrWLHvk6wsuoCjWDLdpljHxEvk6FKL6yspoDGedpZ1GuL/19TRWvv8//sO97046acLU6cgMTVK3SQ0NegblNQqzWU8qTiHptL1yJZXZtcvumAGQswafM2NnjWjU7qzh5gDinKm5pWg0/bZ59nDLLdrBw2MUZrV+vb2MObvh+8wyzsSzMecsKZnI16YzRyJHETM/mRlbMpGvTScWt8jXZn+PjUGUQmQPTRCIzhYDzlTbx7OgsRmBmvfh+HOSUZg9CRo7xY39fi2M7DXyNffHq6Ax18FRp82ZcbKRr3m/MllB42P7c8FgEIXFxRKxWhC6JalEYTZxE4Ntr0w6bZt5HLHaLNueMfMa+TrdtiMRMibmZ69RmC0rtgxHveZ7uExBgZbe4vZaW71Hvubo0+GwPWI1y2Ft20b17N6t22pt1RG69+0jD0cu7yXyNRuinBx7f7kMf1emgwsbQvO7TkSnrP91AbLkKKnbpJISkkGKRlOPwmwu1SUjaMxtZqJtr6LCzjN04XDqka9TbdusN97yW3sCwB0haJyKOHE6kaad33EHlZEAn2LQJHWXtGKF3sfyGoW5Tx/KmzPHXidHuTaDb06ZQq933aXbTKdt3rvato32srgMR5o2NR5796Yyq1bFRuS2LO+Rr9Nt23wgpxiFWdXX6/e1tbFlnPdwWrSI6qutTT3yNbfDZdwMyaxZ8Y1MTU3qka9rauz6nGZ/uczcudRvMWiEGDRJ3SZxFGX+7CUKM+Bd0Hj8eB0tOp22L700NUFjU5TYsvQ9XiJfp9u28yGeQhRmz4LG7PruFANOJfJ1JgSNnfUkG/naq6Bxbq44hYhTiCAcI9PRpzu6bb7eFf1Otm1TM9PUhfQQhdnWDjuGuJXh9kyRZsAe9iXZyNetrdq5hA+im3ueHMImP59c6YuL9aFu3v/jcZuHx5OJfO3zaecZ/g5YlQWwH7Lm+47tCQbDYYlYLQjdAreIy8lGYTYfmskKGpt1p9O2uflvwg9aN8PFTgNOsWCvka/TbduMWB0KpRaFOZ6gcV1drKDxWWfpv8nmzTpqdHOzt8jXu3bpiNVeBY1Xr6ZzZWa0bC+Rrzds0JG6kxU0PnSIhJx37479e7jRKet/XYAsOUrqNsl0kkglCnNurt4fM8uYy2G8nGRZ9iW8dNuuqqI9KqdjBjukRKN6uZMdM5zj/+ij1CJfp9M2fzfsmJFCFOaY79hZT7JlzJSpyNftJXYk6YjI1y5JlhxlyVHoLjhnTF6iMDtnTslEuTbLpNu26TLP9QYCelZlLrnxr3eOpWYu3cXrnxumxmOqbdfVUfsVFTRzSiEKc0qCxoEA9WHvXlLg+Phjb5Gv77uPynsRFeaZGgBs3EjjLS6m5CXy9aBBVKcXMeXKSk/ixDJDkyTpRE8842luJqeFuXPt2owAefVdeil9Nl3meVazbp3WL+Qy7MRg5kUiOtqz6QzCShrsPWiW6dNH12M6l6TbdjrjdpttNjdTX9ets+ezJyjPGMxo24FA6tGnzdnY0qXukZudjiRmGjYs5QjQCrA7kniNlm1+H2Z/ko18bX5/SZQRLUdB6C5wFOaiItrLGDdOH2Ll6xMn0v4Ff66qskdhHj2a9kmY4cNpf4R/cUejlBeN6mjPzsPHDz0UuydlWbS3Ul1Nn3kvCUi/7XTGbTp/MAUFNGswZ29NTZTH8OyFYcX+VKJPX3aZfj9smI7czFRX0yzQDb8fuP/+1CNADx+u1TzGj7f3l/9Wt9/u3jbgPgv2EvmamTEjtkxtLb2WlSVfD3fLcwlBEI4vJkygB19rqxbcLS/X16urSXTXFNzdsYPer1/vXVSYPdGcuIkc+3wU/dgUOZ44MTNtpzNuN0/G9gSNp08HrrqKypgCwaEQOTDccw+J6/7wh3aB4FCIhIG/+93Y74zxKmjM3oaLFtkFjSdP1vU0NFDeQw+RA4fpzZgJQePKSrug8eTJdnHisWPjj5evJytonCydtALY6ciSo6Ruk5Yvp+U0dmzwEoX5mWeUZ1Hh+fOVevFFylu4MPXI1+m2nc64zb6Yy4vxklsZwF1cN8kozCkJGgP2aNlcD/cl1cjXyQgaJ0rpRL5OQtBYnELEKUTojiR7divZs2BdQSptZ2rcXtozRXT5iIN5FswUGjbz+KhEKoLGgP2a6WzjPAPnrIfzAO+Cxjk59jNjeXnaqcQpaGyepeNxRSL6aIVXQeP8fAQDgaTOocmSoyCc6Di99ZyCvPHOVDlJV9C4vb4lyktX0NjruAMB74LG5sHiDRvobFQ4rAWCnf3g+1kgmM/ecX2pCBrn5mqhYPNQNYsaA9ogmfVYFvVh2zbvgsZsQCIR2ut64QW6PmcOGaB9+3Q9LLBsihMHAmTMzjzTu6Axiyl/+GHs39KNTln/6wJkyVFSt0mzZ9PSm2Xps1W8jMYivZxnnq3i6MipigqbKRqNPc/G7Zgix87zbOm0nc64+/RJXdCY38fz9ktH7DcVceJ0z4Kl0l+3Mm7fRXspyTKi5SgGTVJ3Sbt2aWNQW0viuqYGIwvuskCwZZGRiUTIXdurqLBT95HTypWxwsEscsxlzAPK6badzrjXrfMuaGxqRYZCJJ5rWXT/gAF2geC5c/W+EidTIDhVQWMzsWu+WaamhtpxEwTm96kKGpvJTdDYsmLrcbadoqCxGDQxaJK6S2KhYMvSgrsLFthnPCzSa+aZxsWLqPCgQbGixJYVX+S4utpd5DjdtjMxbi+Cxo8+qtSMGWQQzDNnfJ6LUzyR3kTixMkKGjsNiPMcWryzYAMH2j+nImhsJjdB43j1OM/ApSBoLE4h4hQiCHZMh4hUnEKOZ0HjZOvL1Bg44GRBgRbVTTYKM2BXOPEiaGzCEau9RL42nUiSFTR2luG2eX/Oa+RrIHlBY8sC8vKSdgoRcWJByCZMWSfng9t55ipVUWHT+cAkXr7ZL4AewvxQylTbqYzbq6AxC+kWFACvvKIFgl99lQ515+XRwzkcpjoAcnBobSVh3/HjgXfeoQPPLE7sVdA4J4fqZGHkAweovqYm6rMpYxWJAF98AZxyCp2T276dDn2zQHCygsa/+hWVCYXo0LMpTlxSogWNc3KAu+8mceJAgGS3Hn0UeOwxOvx98cVaGDlZQeOiIirz0Uex/6bc6JT1vy5AlhwldZtkOkdYlvcozKmICjvbNNsy84DEka/TaTudcbs5hSQjaAzoPMBdXNe8N1HqCEHjTEW+TjalEvk6BScWWXKUJUehu+CclfDSlRm7y+ejX/MsrsuzIF4K8iIqHI1ql243QeNkRY7ZHT3VttMZd6Lvzw1zBgnQLCovj2YZf/87CQQvX06zncpKWj7bu5dmGSwQPHw4sHQpKWEMGJCaoPGBA8CNN2o3+/p6qoeFk3nJMBym2RbXc9VV1M5ZZ9GszYug8cyZum5eKs3L02LKe/bQzDAcJmkxc7Z1332U/9Zb9P317etN0HjIEKBvXwQPHkTh4MEiTiwzNElZn8wIzl6jMLPY76pVsY4Zd91FThiWpWdzra32WY2ZvEa+TrftdMbNv/7Zm9Gy9Oywulq3EwrZZ0N8r9OhwmsU5nREhTlxO14jX6cqpmwmdubIRORrEScWBKGNAQP0jCc3l359L16srw8aRHkLFui9FRb2ZbHfgQPt4rmWBTz5pNY0BCikiSlo7OQHPwD+5V/seSUllMd7VaYqRbptpzNugGZqrHfI7VoW7fc0N1OeGSWaZ3Lbt+vD0ay1OHOmXZz49ttpzwvQuo88xoqK1EWFTYYNo9maKWi8bBntdZl1O+tJVUyZKSsjUeGqKruosKmjmSypCBonQJYcBeFEx1wycy6NxctjeFkOsDtMmNfNpUBnpGjnvUDyka/TbTudce/fr5U0+AFuwstrlkURk0eMoPzt28mQhsOUiovJoHiJwszfS0UFLddxxGpewjOdOXJztcEaMoT6++mnwOHDZChZncMt8nVtbWzk64ICGs+4ceSgwv1taqLlTlbsWL0aePBB6m9zM+WzRyUvb+7fn1rk65IS+q64zBlnUP2rVmlHkqVL9d+ithbBujoU3nqrLDnKkqOkrE+87HTffbHLQtu22fPCYaX+/d/pvencwCkUiq3HLHPokM5/5x16/dOf6AAzoA8Th8N6eXHRIlreCgToXoAO7pptVFXRkt+ePfb8SESprVtj2962jcr86U+xZUxVELd6zCU67m80qsfA58zM/u7cqZfqzH44/w7OPM53uyfRUpzz/kylZPobr++Z6E+K45YlR0HoTtTXA+edZ8/z+4GTT469NxCgVz5L5cRZj1mGZ0TsiADQr/miInq/fbtum5cW9+/XoUB4uXDvXn0fQEuFplaiCd9rztAOH6Yy+/e7O3o4NRrNeswYY9xfy9JjWL48tr95ebpOPksmHHeIQROEbKCpCbj2WnrPhiQnh5ahAKCmhpaH/H46QzRvHl0zVdYBenBfey1df+klexm+HgjQklXfvrQHc+edtNQEUJlrrtHnyMaMoWWkpUtpuWvCBGpr1iyq+9lnqW8PPkjlKyqoTG2t9ny84gp6n5ND9dTV0XLbgw8CF15IZbieW26hdvlwLyeuZ8wYup8N9Esv6YCW/frRvbfeau/vmDFaJf6aa/T3xkuenNzyzLhrzoCiTDKiz6ncY0YEYPgHSbz+Ou9NdN1r2+2VSfUek05aAex0ZMlRUrdJAAnu8pKZ2/KhubTGy2jmUh1fd1tydCvDy328BOdc4jOXO9lTMBrVy33btmkvO5Z3MpcpAb3st3MnfeYlwxtvpDYHD6Y+8Dk1Ts7lTl5y5KXCrVv18iH3zbk8m5tLdfDSqHmtttb+3SdalnO7z20JzpnvZekxUVmvy4/OPnVk2x7G3WFLjuvXr8e1116L8vJy9OjRAytXrnQaSDzyyCM4/fTT0bt3b4wdOxYfOqT/m5ubMXnyZBQUFKCoqAi33XYbQo5Q41u3bsW3v/1t5Obmol+/fpg/f77XrgpC92D4cHIeGDrUnm9ZtBEP0HLZwoX0ftgwmlWZzhBmiBTn/zXLohkKl1m4kLwDhw0jbzl2vhg+XEcwzsvTs7aLLqKZDqDb7N+f8pubaVm0pEQvKd55J53zuugiur+igtosK6OZ2zXXUJ+eeoraZHWNXbvs9QBUd22tnm1wPezpd9FFsf0dMEDLWY0bp/s7YQJFVnY6kSQ6x2bKW8W73l75VOPWmbOwRLTX945oO91xx8PrzOell15SP/nJT9SKFSsUAPXCCy/Yrs+bN08VFhaqlStXqg8++ECNHz9e9e/fXx0+fLjtnnHjxqnzzjtPbdy4Ub355ptqwIAB6oYbbrDNrkpLS9XkyZPV9u3b1R/+8AfVu3dv9dRTTyXdT5mhSeo2Kd4syW22FQhoZw7TucFtZmXWw+9NRxJ2FNm5U8+SzNmY6SjCMx5uk2ddzpmgcwymo4jpzLF1q56pmWXiOZdEo3pchw7R7G/wYPfZoTmb5RA7Tz+ty/DM1G2WE+/vY75PNbVXRzL1O/vl1mdnW25lUmk7jXG3BAJJzdA8GzRbYYdBsyxLlZWVqV/84hdteYFAQPXq1Uv94Q9/UEoptXPnTgVAvfvuu233/PWvf1U9evRQn376qVJKqYULF6pTTz1VHTlypO2eBx54QJ1zzjlJ900MmqRukwClmpr0wWAOwdHYqK9zCgZ13K+mJvu1ykq6Xldnz29sVOqXv4w1RFzPtm1kDPLz7e1wPdyfaFQbRKfRrK6mZUGnQbMs3U+zv/v361fnGC1LfxecIhHdJpetrqbX/Hz7wWvubzCox8v9rq6298Ptb9FeXrKGpj2DYeaZ7bRXv7OdRP1NZAS9tp3GuLvEy/Hjjz9GY2Mjxo4d25ZXWFiIkSNHYsOGDQCADRs2oKioCFVVVW33jB07Fj6fD5s2bWq7Z/To0cgxlg6uvPJK7N69G1988YVr20eOHEEwGLQlQegW1NSQA0M0Sg4OfECWIwLz0k8gQEuDTU10vaCAlg3HjaM6tm+n62eeaXeo4GW8efPIw4/LfPYZOWh84xtUbswYur+6Wtczbx71h5eXKivpvm98g/Jqaui1Rw8txstjGjaMxlRQoM9LcZk+ffSrZVE/amupXyznxfUsXUp1c5t9+lBejx5UZswYLUHF/eXvitusrNRlOM/NYaI9pxA34jmXmLjlm/W5LeGZ9zv7lI4TS6ptpzNuduJph4watMZjrq6lpaW2/NLS0rZrjY2NKHEoXPv9fhQXF9vucavDbMPJ448/jsLCwrbUj9fDBSHbOf10/R/+ueeAzz8nlYdolA7U8gOjqIjc0S++mO61LKBnT1KLB/Rrbi7d/+CD9Lp/P6moz5pF6hBbt9Jh2n79dJlIhNz3P/pIu+wXFFCZ9et1GBKfD1i3juocNAh4801StzjpJLvL/siR1DeAxrZuHZVdvJgU6i2Lyh44QPkLFwI7dlC/nPzqV1Q3q9lv3UpG6dlnaT9szBjdt1mzyDiyCz8fZj50CJgyhfpkqoekSjzDZH52e7gn2ldyqyeeIfHav45o28u4+e/RDhk1aF3J7Nmz0dLS0pb27dvX1V0ShM6hb1/9S7a4mFzZe/Wia051jaIiuzzUHXfoMmY9AIniOsuYZffuJRd3/qW9fj2waZNdismsxyQvj857XXYZGa/LLrM/4DZtor7xg/HOO8kIvfyybvOyy7SDBtfDxpPLXHihvR4ez8sv6/7eeqv9+vPPx/aXz7DV18fGJROOH5LelHIBjj20v//97wqAev/99233jR49Wt19991KKaV+97vfqaKiItv1o0ePqp49e6oVK1YopZS66aab1He/+13bPWvXrlUAVHNzc1J9kz00Sd0mAeQk4XTbDwZj98mczhLO5FYmECDx4Hhl6up0PwClKipoLywYjK2HXe+5vooK+3WzHoDq4T2vV17R+evX0+u997q346wnGNRlzHra6y/vA5rj37rV/t2br8523fajktkfSya5tef2OV79zn9DifbHMt22x3F3yR5a//79UVZWhtdee60tLxgMYtOmTRg1ahQAYNSoUQgEAtiyZUvbPWvXroVlWRg5cmTbPevXr8fRo0fb7lmzZg3OOeccnHrqqZnssiCc+FRW0jLfKafY8zmIIwDwvjaH6AD0q3ndLMP4fHQ42lnml7/UkZUBfUCZg0Q61Tt8Pp3H9ZniwG6ain4/6RoCFPqEGTSI2ps9O7Ydt3ry86mMWQ/fF6+/5ti4v/xdO/eYnPtOZnLur5lwXrxlwPbc7tsr61z6M/vF5Z35zv0sZ5lU23a7nuq44+DZoIVCIdTV1aGurg4AOYLU1dWhoaEBPXr0wIwZM/DYY49h1apV2LZtG6ZMmYLy8nJMOHYOZfDgwRg3bhzuuOMObN68GW+//TamT5+OSZMmofyYWvP3v/995OTk4LbbbsOOHTvwxz/+Eb/+9a8xkze7BUHQbN9O+1U9e5KTA6Djie3ZQ595rys/X6vNc9wpvu4sw+TmAo8+Glumb1+SkSovp+XAJUv0NXayAICnn9Z5LJfF/QRIQR+gB5hzfyoc1ob6ued0vt9P7T3/PNXLxtRZD9fd2qoNFtcTCtn75pTLsizaFzT7y9GjnQ4VfH97jiIpPKTj4mYs2ju75dY3Z77T8LrtoaXSdmeQ1Pqdweuvv65wbPpnpptvvlkppZRlWerhhx9WpaWlqlevXuryyy9Xu3fvttXx+eefqxtuuEHl5+ergoICdcstt6gvv/zSds8HH3ygLrnkEtWrVy/19a9/Xc2bN89TP2XJUVK3Sc4lnFmzlHr1VXJV5/NhNTX0GokotXev+9LO4MF0nZcca2ooVlUkomOIOd3qAaqvqYlikc2dSy7+Ztv19fTa2qrf791Ldb/6qr1vZn8HDqQ8XibdvFmXaWxUyu+nfrW2Upv19UpNnUpl+LuoqaGzZuaRAB7L4MH0Xd1+u458zf3lMps328cweDDV4/zu21tyTHd5McNLeJ77m+m2PfazU86hHc+IQZPUbRKg1PLl9PBmOSk2Pm6Hrflg8PLlsQ+PUCh2ny0cVmrOHHrv3H8ClJoyRZ/xYqmoQEAbp4ce0gaLjcmUKfTq9+t6zIPMnCIRHaST98D8fnpfW6vUE09oYwPo82xOOaxoVBunJ56IHUMkog+Fc38DAX14nPMAvadnPuj57xDv72O+70qD5uyXW5+dbbmVOU4N2nEwRxQEIW0aGujcl3msxS3GmM8HHDsT2ranZNLURPWYRKPa1d+sjyWyli4Fzj2X3t9+u77+7rv0+thj9NraSvJUgF66NF31QyFyvXe2/frr9J73vvhs2gUX0J6W6dK9ZIk+rmASCunlTjcP6NZWraLP/QV0sM0LLrB/ZpzLcW7nvcx8xrk/5cS5h+VWh5NklvzM/riNwVwWdRuTW38T7YM53ycjWCxu+4LQzbnkktj/9Dk5sftCOTnATTfRe95LMykpAX78Y3rPkZZZYd9ZxjR8XIZxc8yIV48Jj4G1Xc3+fvGFNqjvvUevO3fa2xo82D0YaF6ejiB9zEHNht+v61m0KLbMgAGkAdnQYD+snopTQzIOEUBih4qObLs9w2n2sSvaToAYNEHIBiIROujMAsGjR9ODwVTecAoEu8UMs6zkBY1ZIGHxYrug8fDhVM/QoVpo2KwH0P3yKmjMosLjxlH7LB7M4sRPPUX382Hr+nq7ODG3uXChd0FjFlOuqGj/sHF719w8Hs3rQGKVjo5sOxkPw65sOxGdtKXV6cgemqRuk3jvyk1ct70ozG57aMkKGpuRr90EjduLfO0M4ZKMoLFTILi2Njlx4niRrxOVcQoacz7Xk+z+kfl3SnXfzXkt1bYztffl1p/2/o2m0bZErBaE7kJ+Pi0P5ubSa1kZ5Zsu7/n5tL+Wl0dSTwDw1Ve6Dj6HFgrFho/x+fRszPz1fM011F5Bgb0uZz1cd24uleF6WlupXzwDM/vLIV5yckhOi/vb2EiKIGPGkKu+c+nyvfeov257XXy+zjkzjVfGHKtZxjyn57aM5nyfzHmsRHhZwkul7QyfBWsj0bg6qG1/+7cIgnBcM2YMGZbWVjonNX8+PehNoWGAzl/5/WQ4WCA4EABmzAAmTqSzaKagMUDv3QSNy8tJnHj7dqqPlw35QcX1zJtH9zc1kbPGZ5/peiwLGD8eWLWK5KdMQeNhw4BXXqF7TUHjpUvJSSQc1kLBLFQMkC5kNEp1jxkDPPEELTECWsg4EqF6hg2jzw8+qMsAVJdSdkHjSIRely2jehjnMprbGS43JwvGefjazE90IDvVthOV5zr4mlu95nWnUXIbV7x2Umk7CWSGJggnOqwW7/ORl2FdnT7kzOLEDz5I4rqRCKV16+j6pZemLmh8xhn0ysYToHYA8ihkceKrriJBYEDvkQUCtPfmVdCYRYWbmkjQmB+CBw/SzI0PkANUZscOaocNls9HgsaPPeZd0JjFlA8csN+b7GHmdInnAZhO285rbvfH29tKpt5Mtd0VavuCIHQBpkBwfb1dXJedHUyBYBbXtSy7OLFXQeNwOLGgsRt795KjiM9nFwhOVtCYyxQV2cWJb72V6iku1v1hceKXX7Y7eOTn24WRkxU05jJuHpzC8UEn+Wh0OuIUIqnbpIoKOuybjEivKRDsDOQJeBM0ZsHe9evdBY2dYslmPU7HCi+CxmbfV6yIHaNbPaY48b33xo4lWUFj/q7bc5hIxqkikdOGmZ9Mfam0nehzvHrMf3ft3e+8J422xSlEELoLDQ10Rsvv15qGprMFoB0zfD6taejmtu9F0JgFewcNsjtKVFZSO06xZLMe7kMqgsamaPCIEfb7q6vjCw3zQfLZs93H7ZbnFDTm7zrds2D86jyw7NxTM/ep3FIqbWfqLJjZ13hjcL6Xc2iCILTL4cP0kOe9s1CIHhCsfsF7Sz6fVsw4JgaesqDxo49SmV27qIwp4JubqwN0MmY9BQX0moqgcShkFw2ORLRhXLKExux8KJrixLwkm4qgMRBrqJ0k80A2DZb5IHcaMtMQJPOwT8UYpGpAnIYsntNHIiOWib1Fs7qM1iYIQudz++20x8P7VvX1sTOhuXPpNSeHVEUA/ZC54AJyxGDnCHZfZ8/BnBw6VOxk6VLg7LMpinVREc2Wamq0ITz7bHqdNYtem5uBSZPoPRu222+nvv3yl9Qf3t9jhQ6/XyuS8Gxs8GDtfMJHFHJyqO2pU6kO3gesrwfOP5/qYYN13XWkBlJZSdenTtV1AMA559B1v187u9x6K9Xz6qs6oneih7XTOJn3tOcwkayjRyba9upkYt7XXtvO+jPVdiI6aUur05E9NEndJgEknusmrssivSwaHInoPSAWCE5X0PiVV9wFjd3q4fdmPV4FjQE9hvXr7YLG8cSJIxG9H+YUJ/YiaMzCyF72rcy/k9s+VLy/abx70mk7lT03r3tp8caTRtuyhyYI3YmCAuD99+n9Y4/RLCsnR89KWDQ4HAYOHaL3rNXoFDQ2gu8CoF/SPPsbMiS27SuucBc0Zj1GM48PW/NMzewboGdJLBAcjZILPQCY8RCbm+m1tZXu4VklLzk6PTWjUb0PxuNmvAgam2LKQPyzVCbOWYvbDMvMc1uuc5u9pNK22/VkDjC77Y85lxyd4zA/x+tbsm2L274gdCNMceJQiJwXcnLszhG5uZTHwr689GjiRVTYDNLpJmjsNGj5+bGGz8SLmDLvDbI4cUODXVTYrKesjIxVMKjLmHgVNDaNutOgtOf0kOgBnowzh9MwZqLteOfMErXthURjTrZtUdsXhG5CWRnNJE4+mT4vX66vmQ92dnLYu5c+8+zDxJyNmXl8LxsSwH527Lzz6HX7dn1I2m02w84cbnA5J2wYzbbZOJm/3M3xmA9fnoHy/pzz1/7f/ha/T2zkzMPUGXZkEDKH/GUE4UTHqWdoLuE546EBJHMFaLkqQM+sfD79wA+FyFjyAWpAS2EB2q2/thb44ANdVzRKZY4csbcbjeq4Yhs3xvYrEgHmzInN45kgGyRAzySHDNFLpCtX6nbYMC5apB1HNm+mV+f3VVVF9TkVQABt/G+8Ued9+mnsfUByzh5Ohwi35TnG7R4TNzd/Z76zHvNzor65OXPE60s8D0y3ZUdn3c738e6RJUdB6CYsXkyzk2g09vyV21kzfkibS4Lbt9OrOUNbvlzPbvhhby7n8Yypvl7P0Bi/X88YTfjBxDMsJ856zDL8gOQZKUAzPu4vj8Gc6e3fHzsGHj9/V4sXu88ozXvN5cjDh937LnQ5YtAE4UTnwQdJrqmignQOa2qAW27Rew/mr16/n5w4xoyh+9lYvPSSPpfVrx/de+ut5JpfUECxycaMIdFhyyLVfD5M3dQEXHstvWdDkpOjr9fUkBCw3w9cfjmJDxcXx84g8vKonnnzqD9mGb4eCFAMtL59qb933qn1IV96ifrl99PYx4whF31zDJZFxwiGDdPfFR8BqKigMrW19u/Ksmg8S5eSTqbTqcTEOftpb9/MrUx79yRyzGjPuSTdtpO9x23c5owt3vk7817zepJ7aOK2L0nSiZ44PpjT7dx0nV+0SLuvc2yvrVtjY5IFAvbYZhyTjF3rzWu1tfTa3Bwrc2XGQzPjs7H7v+nmz9fdYqi5lamq0nmHDsUeDzDHYMZa4/5u26aPKvD34jxmwLHjOGYcy2/deGNsPLVELujx8p3XE5V3XnPmmf8W2rsv3bYzOe54fXZpW9z2BaG78NRTFCLF5/MehZkjQPPBaTNq9IABdN3n05Gh+/enmc769bTvNHw47akNHWrvk2UlH/malzDNMmY9JSW6zMKF5NE4bBj1u7WV2vca+ZqjT598sv274gjbF11E91dU6O+qtpZmgG6zkUT7Zsl4GCaz7+ZGe2X5nq5oO5nZaaqHy+PRSROmTkdmaJK6TeJZQzKRm51RmDkCtNtMxZxZ8eHlp5+OjRrtNkvyEvnabWZl1sPvN27U7zny9c6dembqJfK1s+1oNHYM5vfHsztztpbKzCSdxH/veLMct38byc60vLadyrid/XLrs7OtY+9bAgGZoQlCt+C55/RMhUk2CjNHgOYZUE6OrseMfM2OFZWV9qjRXCfPcFKJfM31+nzuka9Zxov1HwEd+fqrr6gur5GvTaqr43v+cZ2mZ6QzYrX5PtWzYG7efc7riWZMiWZLbn1Nt+1MjTvDiEEThBOdmhodjbmujpbGxo0jrz323KupIacGMwpznz6U16MHleFAoa2t5JgxcyY5YeTmkpGwLDI8XCYcpnoLCqid6mqt5mFGvma3a7fI1+PGUR0saMwRtjnxcuC8edQel/nsMxrrN75B5caMofurq+2Rr2fO1A9cM/K1ZVG7lkVjMXUb2YklGqV+jhmjx8/fNeN0aY/nQs/XEyl+uClvOOt2uuo73e0TOYW44bXtdMadCSeWdhCDJggnOhxF2efzHoWZI0Dn59ODmyNEz5pFBsOUvPL5SDZryhRS0o9EgNNP156SqUa+BvQrC/8++CC97t8P3H039aeqivr88su0R8ZlIhHvka85+vSyZRT52nTZHzlSRwoIBKiMz0fu/QcPJn7Qxjuj5SSecTA/uxmWVPeWOrrtVMadbP88eDmKQROEEx0zirLXKMxm1Ohbb7VfNyNfM3wuq76enC/MiNWpRr6+8MLEka+5jFl27157pG6vka/NiNWXXWZ/+G7aZP+u7ryTDP/LL+s2heOTTvLR6HTEKURSt0kARWFOJmK1WxRmTomiRnOEaDNqNDtHbN0a67bvJfJ1ojKBgL1NZ6qrs4/RS+TriorE31U4rFX9ze/KVNs3/wbxHCTc7vHqUBGvPvPVrR63e9NpO1Pjdv77davDeC9u+4LQXaiupijMblGanbhFYeb74kWNNiNEc9Toykq6j1+dQS+9RL7m62YZxufTbZplfvlLe79SiXzd0GDvrxO/n6JTA/q7AvT3Z9adrjOHF4cK5x6Tc9/Juc9m3pNu285+eHUkce6tOfOd+3XmHmwSiEEThBOdJUtoedDnSy0KcyhkjxDtlMuyLHuEaICcOPbvp9eCAtpzSjXy9auvxpZhcnOBRx+NLdO3L/W7vJyWA1OJfG1+L+Z3xZjekmbEatNYuhmqTOxzJcLpUOGW5+Yoksml0lTG7dY3Z77T8MoemiB0M/x++8zJSxRmgJw4PvqIRI2jUXvkaw7JMn48vQ4frstw+Jm+fYFzz0098jWgo1tz2zU1FCEa0LHRTLV9jqdmWZTvNfI1R58+55zYvtTUAAMHUh380L3qKl3GafiAxM4UieDr8e5zM1rx6kg2vyPa9jru9u5zzuySnKGhk7a0Oh3ZQ5PUbVJtLUVUrq/X+w9eojCbKRJxj3zNB5k5D9D7S8uXK1VTk37k61DIPfL1nDmx+1icpkzRh7G9Rr72++3fixn5msusWmXfN3OLWJ2JPalU9q3MfwPOz275mWw7lXE7++XWZ2dbx97LwWpB6C5ccAHNzMxlGS9RmE1aW/VMiGdGgJ5tcfgX88B2QwPNfpyRr52ha3w+YMMGeu/chwLojJpb5Gt29Tfr40PbS5fS7BCwh8159137GFpbSdIK0EuXpqt+KETHHZxtv/46vec9ND6b5jZjSbSf5Hzf3vJfor0z52dzpuVMbuWc+1OJ2m7vDJ3Zl/ZwmxG6nUNz3itLjoLQzeDIzYzXKMyM36/rMSNAc5kBA0h9o6FBh4Axo2UzXqJPMyUl7pGv3aJlm4aPyzBeom6bmBG/nf394gttUDniN5CaQ0UyzhjtOZK0V386bQNd13aaZ+3EoAnCiU5JiRYPZnHip56iB7vPR4et6+vt4sQA7YctXOhd0JiFfXmWFonQQWcWCB49msqYyhtOgWC3OG2WlbygMUtoLV5sl9gaPpzqGTpUCw2b9QC6X14FjVnImb9rILn9J7drbl6HzuvxHvzOGVim2wa6ru32PCvbo5O2tDod2UOT1G0SQPtoyYgTRyL6PJZTIDhZQWPO53qeftpd0JjPpoVCOhwL76G5CQR7ETTmPb2NG90FjZ31hMO6zKFDsWFzkhE0dooyJ7sv5HZPpvbd2iubqG/J7rvF67/XttMYt5xDE4TuQnV17DJaPHFin0+f9XLOkpIVNGaKi2kGU1lJs57KSrugMbu85+fT/lpenhY0NgWCUxE0vuYaaq+gwF3QmOvhunNztaCxZdE4vAoaO0WZkyXRElo6Z8HcyjrfJ3MGLhEd3XaqZ+Di4G//FkEQjmtYKLhPH+0iv2wZOTBYFj2An3hCu9yzkHEkQk4Vw4bR5wcf1GUAqkspu6BxJEKvy5ZRPWPGkGFpbaUzafPnk3E1hYYBOsfl91M/WSA4EABmzAAmTiR3eFPQGKD3boLG5eUkTrx9O9XHy4b8kOR65s2j+5uaaFyffabrsSw6irBqFR1fMAWNhw0DXnmF7jUFjZcuJScRFioG7MtobvtPzoPEbuesnHlujhhuxsdZNl7dZp/c+uasN1Gd6bSdqLyXcSdAZmiCcKJz0kn6YXDwIM0i+DAzQOK6O3bQfhMbLJ+PBI0fe8y7oDEL+x44oBX6fT7yMqyr04ecWZz4wQdJ0DgSobRuHV2/9NLUBY3POINe2XgC1A5AXpwsTnzVVdRnQO+RBQL0XXgVNGYhZzawTDLef148BN1mNvH2l5I5a5aGk0UbHdG2l3GLUoggdBNMceJbbyVx3eJi/TBgceKXX7Y7eOTn20V6kxU05jL5+XaB4Pp6u6AxO5iYAsF84Nmy7OLEXgWNw+HEgsZu7N1L4/L57KLMyQoac5kkXciFLqCTfDQ6HXEKkdRtEqDUihX6vekI4RTpNcWJ7703dgM+WUHjigo6WM2vyQgjmwLBLHbsbCdZQWMWGF6/3l3Q2CmWbNbjjDjtRdCY++78/hM5QTjvcXOIcPvsVle6TiLOPsTrb7x+Zqptj+MWpxBB6E6MGGH/XF0dX2iYDzXPnh1bT7KCxg0NdDaLX/1+rSNpOlsA2jHD59P9cXPb9yJozFJfgwbZBY058rVTLNmsh/uQiqCxKYjMmHtR5tKgme9835Xn0JxtuPXXvMd5Xc6hCYLQodTW0v4TP6SXLKFlOefDwRQn5uXBVASNAW00Dh+me3jvLBSielhxhPfzfD6tUlJervsNeBc0fvRRKrNrF5UxRZNzc3WATsasp6BAf0eMz5ecoHEopIWazbJOo2VecxoPNzKxz5VsPabBMvvmNGTOviezZ9YR/fVSXUZrEwSha2B3+Zwc8k6cOpVmJbwnVV9P4rp+vzZY111HaiBeBY1ZpDc3lxxBiorsgsbOmdDcubpeFjTmB3uqgsZLl5KgcVUVtT1iBJVhQ8hix7Nm0WtzsxY0ZsPmVdAYIOcVdj4B4hsppwEw73E6iqTqbJFM284ZU3tOKp3ZttdxJ0MnbWl1OrKHJqnbJN5LMsV144kTRyJ6P8wpTuxF0NgU6X3oIXdBY66HRYMjES1ozALB6Qoav/KKu6CxWz383qzHq6AxoMdg7vnw38GZ5/a3SmXvKdV9q0R9i9dXt300t/57bTuNccsemiB0J1pbacmOZzi85Oj0GoxG9T4Y6yUyXgSNTWHfggLg/ffp/WOPUR9ycnQ9LBocDgOHDtnbdgoab9lib9uy9OxvyJDYcV9xhbugMesxmnl82JpnambfAP1dsaBxNErHFgBg5kx9X3Oz+3Kd88yZOVtpb1aTzCFi54wn3jkuZ93OJVBnX8w8tyVSt5lTKm27XU923OK2LwjdCBYnbmiwiwqbDgxlZWQ0WFR45057HV4FjdnAmOLEoRD1ISfH7mSRm0t5LOzLS48mXkSFzSCdboLGToOWnx9r+Ey8iCmbcdm8kOjhHe+cmVnWzTg5r6eqvNFVbSc7blHbF4RuhPkLlmdTgP3Bw7Mh3ity/ur929/i189G7sABe91lZdTeySdT3vLl+rppINixZO/e2D4y5mzMzON7TUNinh077zx63b5dH5I2Z5Bcj9OZw4TLOWHDaLbNPwiE4w7PBm39+vW49tprUV5ejh49emDlypVt144ePYoHHngAQ4cOxcknn4zy8nJMmTIFB8z/BACam5sxefJkFBQUoKioCLfddhtCjl9UW7duxbe//W3k5uaiX79+mO/UeBMEQTNkiF6u4/+TpozVokXacWTzZnp16j9WVdFMxvH/FYA2RDfeqPM+/TS2DnMJzxkPDSCZK0DLVQF6ZuXzaSMbClF/+QA1oKWwAO3WX1sLfPCBrisapTJHjtjbjUZ1LLeNG2P7FYkAc+bE5vFMkH8EADqKQTylC7d887qb67vzfbx72vOSTOTs4XTGcFsSdfYn3rKhm5u/M99Zj3Oc8frm5kiSJJ4N2j//+U+cd955ePLJJ2OuhcNh1NbW4uGHH0ZtbS1WrFiB3bt3YzyHbz/G5MmTsWPHDqxZswarV6/G+vXrMZU9rAAEg0FcccUVOOOMM7Blyxb84he/wJw5c7DYVAwQBEGzerWe3WzfTq/mrGP/fj1DY4PFRoqXBhcvdp/dmPeay5GHD1OZ/fupjPMcl9tZM67H/AHL/TVnaMuXx/bXXELlGVN9vZ6hMX6/njGasLGMN8Ny1mOW4Qcrz0iF45N0PAkBqBdeeCHhPZs3b1YA1CeffKKUUmrnzp0KgHr33Xfb7vnrX/+qevTooT799FOllFILFy5Up556qjpy5EjbPQ888IA655xzku6beDlK6japuppCrAQC5BX20ktKXXMNXQuHlRozhsLLLF1KeXv30msopNSwYUrV1FAaN057OXIZbqO1Vb8uXUpqGeEwlXnzTcrnem65JX5fue69e+39ZQ9L8/+r2d8xY6g9y6KxsdfiggW6HsvS3omcV1NDY4xEqJ/z5in13nv6XvM1EKDrL71kLwNQvwMB/V0n67nHY4l3n9t18/vy4g3ote14dTvLOd+n006KbSfr5djhBm3NmjWqR48ebR353e9+p4qKimz3HD16VPXs2VOtWLFCKaXUTTfdpL773e/a7lm7dq0CoJqbm13baW1tVS0tLW1p3759YtAkdY8EUNwup6t6IKBjhZlxv2pr6f22bdptftEienW6vHMcM45fxlJQN95IbXJ8MKerv1kP1x2J6PhiW7fGxiQz+wvomGTsWm9e4zE0N8fKXJnx0Mz4bOz+b7r583W3GGpuZaqq7PHQEj2ckzFGzr9jMgYvk223V96tX2Zeov4770uj7ePCbb+1tRUPPPAAbrjhBhQcO53f2NiIEo5vdAy/34/i4mI0HltiaGxsRGlpqe0e/txouvkaPP744ygsLGxL/VhZWxCyndGjaSkuN9d7FGaOPn3yyfaI1Rzt+aKL6P6KCh2xuraWYotZFkXGHj6cluS8Rr7mCNB8cNrs74ABdN3n0xGi+/enMaxfT30aPpzGPHSo/fuwrOQjX/MSplnGrKekRJdZuJA8ODnytXlfor2r9jwM+T4v+ZloO5FbPl9P1H57Zfmejmg7Dh1m0I4ePYqJEydCKYXf/OY3HdVMG7Nnz0ZLS0tb2sfnZQQh21m/nvaFIhEK31JXR/k+H7Btm77v5ZfpHtY+bGggB5LiYnJPN8OiLFpEOonsYNLUBPzgB7R/dMEFdJarsZH20IqLaQ9t0CBKfBaOf3zedJN2yDh4kOphD8l//AOYPp2uRaPaELLzh9+vHTKWLyeHjv/zf/Q43c7a+Xzalf+jj6i9UIgM96JF9gclB/C0LAqhYxKJUHicRYtIXusHP6DvwRlmx+t5LKdDhdNRxKmhGM+RJJW23a4ng1t/Od/p0OF0FolnuLy03ZXn0NiYffLJJ1izZk3b7AwAysrK0OSIJxSNRtHc3IyyY15YZWVlOHjwoO0e/sz3OOnVqxcKCgpsSRC6DV99Rf/xvUZhNqmujv9Q5DpNr8biYtJ25Nkhk2zka44AzTOgnBxdjxn5mg1jZWVs1GjL0gYtlcjXXK/P5x75mmW8zOcJR7521pXuWTCvZKrteMbSWXc8A5RopuXW11Ta7qpzaGzMPvzwQ7z66qs47bTTbNdHjRqFQCCALYYiwNq1a2FZFkaOHNl2z/r163H06NG2e9asWYNzzjkHp556aqa7LAgnNnV1FFE5N5ce9JZFxsmMwjxzpn44mFGYLYs0EC2LIl+buo01NbS8Fo3SA33MGB2tuaZGR8jmCNh1dbS0OG4clWFvyZoa0l40I1/36UN5PXpQGQ4U2tqq+xsIUF+4zcpKXSYcpnoLCqid6mqt5mFGvuZf926Rr8eNozpY0JgjbHPipdN586g9LsORr4HYw8bx3Nj5upsqh2kw4ql1uJFK206cbcebBbrNvpzu9onG4IaXtjtqhhYKhVBXV4e6Y8saH3/8Merq6tDQ0ICjR4/i3/7t3/Dee+9h2bJl+Oqrr9DY2IjGxkZEjsngDB48GOPGjcMdd9yBzZs34+2338b06dMxadIklB9T4P7+97+PnJwc3HbbbdixYwf++Mc/4te//jVmmvIzgiAQLMgbiXiPwszRp5cto8jXpsv+yJFatT4QoDI+Hy0zHjxI9XPkap/Pe+RrjgCdn08Gjfs2axYZR1Pyyucj2awpU6hPkQhw+un6QZdq5Gvz+8vNpfsffJBe9+8H7r6b+lNVRX1++WW9z+dGvLNpTpKZqTnPY3ldPkymbbcybv1LcU8rY20nG1Q1ocuIC6+//rrCMY8TM918883q448/dr0GQL3++uttdXz++efqhhtuUPn5+aqgoEDdcsst6ssvv7S188EHH6hLLrlE9erVS339619X8+bN89RPcduX1G1SczN56bHwcE2NUqNHa1d7QKkBA7SnIXsf7t9P3oLLlytVX0+v0aguU1Oj1MKFlNfUpNSddyq1a5dSEyboNpcvp2uWpetZv57KRCK6jFkPQPdNmKD729ysy5jeh2Z/162j19Gjqe+7dlF94bBSJSX0efhwu9s+p2BQqcWLtQfmwoW6zK5d1Da3w9+VWYZfzX6kk/g75r9hIi8/t+uZaDvR9Xj3uF1LNIZkvRrbqatT3PaPZ8SgSeo2CdBRlPkh4CUKc0WF/bpZDxsAVpjniNWAPfJ1MhGr3SJfJ9Nfjm5tRo3mMWzdGuu27yXydaIygYC9TS8PeK8GIlEd7ZVNte1kDEyiNs1Xt3rc7k2x7ePCbV8QhE7AjKKcShTmhgZ7XU78fopKDWilfoCWK6urKfK1W2Rst346I1/zffH6a46N+1tZSffxqzM6tZfI13zdLMP4fLpNs4z5nu/z4lDhdIRw8xJ07ikl8nJM15nDiyNJPC/HePts5j3ptp0EYtAE4UQnFKII0OFwalGYAR2d2rJiw8eY3pJmxGq/n9p7/nmqN5XI16GQvW9OuSzL0qLCZlTq/fvptaCA9sNSjXz96quxZZjcXODRR2PLmJ6eqbijuzlUOPOdRsBtDy0dV/hUcTqFuOW5OYpk0rszAWLQBCEbsCwyPF6jMHP06XPOia2zpgYYOJDq4AfSVVfpMpEI1WfOnLxEvgbIieOjj6jf0ag98jWHwWEt2OHDdRkOP9O3L3DuualHvgZ0dGtuu6aGxgjo2Gim2r4ZT41J5NCQDPHui+e1mIm2+Xqitp1GK1H/OrLtJL0c0UlbWp2O7KFJ6jYJoIjKLA3lNQqz36/3K6JRe+RrLrNqlX3fjCNW19ZSFOv6en2/l8jXznbcIl//+7/b8wC9p7d8OTmVpBv5OhRyj3w9Zw69d+75pbMn5fzbOfM43+19ovqTaTvVlKi/bv13G0MabbcEArKHJgjdhqVLaaYC2EO4vPsuvfJMo7WV5KkAvYxmuuqHQrGKGdEo8Prr9J73vvhs2gUX0MzMdKv2EvnapLVVz4S4v4CebbHaiHlgu6GBZpzOyNfO0DU+H7BhA73nfTyTpib3yNfs6m/WlyiUVTIzMrfZidsZLue9ic5rJdqLcr5vb/kv0d6ZW3/d+u/Md+uDWz+c+4ZchwT4FIRuBss9MV4iQJuY0acBe+TmL77QD3eOPs3Rshmvka8Zv1/XY0bd5jIDBpCCSEODDgFjRstmvESfZkpK3CNfu31XzgPYbiSjfpGqM4d5X2e33dHjBtLaBxSDJgjZwOLF9pnD8OH0gBg6VAsNmyK9gDZMXgWNWVR43DgyBCwezOLETz2lg2AuXEj7YaY4Mbe5cKF3QWMWU+ZZWiRC/eUxjB5NZUy1E+cY3OK0WVbygsYlJe0fdG7vmpvnn/N6ezOprmi7o8cNpOdA0klbWp2O7KFJ6jYJUGrjRnt4Fd4XcoZECYf1ntShQ7EhXKJRfa6rslKHcOGwLzt30n4Vh40BaB/N3LOqqqJ6nHtSkYg+P8b1tVcmGtXjMstwPU8/rds2Q8XwGEIhHQKH+8uhcNr7rgIBHfvMbJu/v2T3rsy/Uyb3vpz/BuLtZSVqP9W2Uxm3sy/x+ujyXvbQBKG7UFlJ+1lu4ro86+DzVrm5WlzXsmg24lXQ2BQIrq6OXcKLJ07s8+mzXs5ZUrKCxkxxMfW5spLGVFlpFzTmMeTnU3/z8rSgsfk9pSJo7BQndi6jOd9n+iyYFxIt36XTdqbGnYgUxu1v/xZBEI5rtm+nhzgvG/LDgkV6582jpbOmJnJ0YHHdggK6d/x4YNUqcqU3BY2HDQNeeYXuNQWNly4lJ5FwWAsFs1AxQLqQ0SjVPWYM8MQT2uWehYwjEapn2DD6/OCDugxAdSllFzSOROh12TKqZ8wY6ldrK30H8+eTcTWFhgE68+b3Uz95DIEAMGMGMHEiHUEwBY0Beu8maFxeHitOnOgQsZuDh4nbMpzzXJqzXvO607DEc/OPd7bNa9vpjNutb8562zuQ3Q4yQxOEE51XX9UPcoCMA0AehSxOfNVVJK4L6D2yQID23rwKGrOocFMTCRrzg+vgQZq58WFmgMrs2EHtsMHy+UjQ+LHHvAsas5jygQNaod/nI8/Oujp9sJzFiR98kASNIxFK69bR9UsvTV3Q+Iwz7P1M5sxVonvcjEU8A+LlrFl7Z706su14daWCh3NoYtAE4USnb1/7L+K//IVe47k6791Ljg4+HxmS9euBTZvIecOE6zHJy9NlioqAyy7Ty5W33kr1FBfr/tx5J3DhhVTGdPDIz6eAnZddRkaQjafPp8vccYf9oVhUpMvk51N7bCDr60mxhGEHE3MMfMjcsqju4mJqp1cvexmmqIgMMcNlzEPWwvFFJ/lodDriFCKp2ySADjm7ies6hXtNpwt2rODkRdAY0KLBK1bofpjOJ856THHie++NdSJIVtC4ooIOVvNrMsLI5hi43852khU0Tkaw2KtDhdvnePU4//aJ7nfek27bmRp3ov669FPEiQWhOzFokF1cl6MwO4V7TZFe1lVMRdDYFA0eMcJ+f3V1fKFhPtQ8e3bsGJIVNG5ooPNw/Or36zGYDi6Advowx+Dmtu9F0Pi6646Pc2hmO3y/2S+zHfN9V55Dc7bh1l/zHs6XJUdB6Cb4/XSWa88eu4Bvbq4O0MmYIr0FBfSaiqBxKGQXDY5EtFFZsoSW5ZwPRVOcmJcHUxE0BrShPnyY7uExhEJUDy8L8n6eOYZjgYRTFjRmweJ4pLJvlOpek9MwxHP6SGTEMrHPlWw9psEy++Y0ZM6+i1KIIHQTzj6bIioXFdFsqaZGP5RZeHfWLHptbtbiuvyw9ipoDJAjBTufsLt8Tg61PXUq1cF7UvX1JPbr92uDdd11pAbiVdCYhZFzc6nfRUV2QWPn7HPuXF0vCxrzgz1VQeOlS+0PYjdD4XxIm/c4nTW8OluY97XXtrP+dNvO5LjdyqZLJ21pdTqyhyap2yTeX3IT13UT6eX3pkivV0FjQAsEr19vFzSOJ07MEbWBWHFiL4LGLIzMfXQTNOZ6eFyRiO4vjyFdQeNk947Mv1Oqe09e99Li/TvJVNupjNvZl3h9dHkvB6sFoTtxxRXu4rqsx2jm8QFiMwyKKWjMsyQWCI5GyYUeAGbO1Pc1N9NrayvdwzMcXnJ0eg1Go3ofjPUSGS+CxqaYckEB8P77ur8VFdQu18PjCoeBQ4fsbTsFjbdssbdtWXr2N2QIXIl3jsskGbf9ZA4Ru+03OZcczfbMGVGiGZWXts3PXsftNrsz89yWSOUcmiB0U9zEdZ0GLT8/1vCZeBH25X0qFiduaLCLCpv1lJWR0WBR4Z077W14FTRmA2OKE4dC1IecHLtDSm4u5bGYMi89mngRcjYDozof6u05XCTax0rGkSTVZblERiuVtjM17vauc91J7qFlrVKIUgoAEOT/QIKQ7QwcSK/bt5MzSCAQ6x0WCAArVsSvIxyOnSUFg3RoGtCvgFbMaGzUhurvf9dlzIcU38OyU+bsCAD+9je67tY2G0YzonQoROoevD8H0GFvLmP+v+ewNLt302fneTuAvhenV2YgoCW/zHHX1cWW53bN13jX45HounnN7b722sxU26m00V5/k3jP7/i5Ho8eqr07TlD27NmDb3zjG13dDUEQBCFD7Nu3D33jhT1CFs/Qio+dKWloaEBhYWEX96ZjCAaD6NevH/bt24cCdsHOQrrDOLvDGIHuMU4ZY+ZRSuHLL79EOR+5iEPWGjTfsTXewsLCrP1HxRQUFGT9GIHuMc7uMEage4xTxphZkpmYiFOIIAiCkBWIQRMEQRCygqw1aL169cJPf/pT9GIl7SykO4wR6B7j7A5jBLrHOGWMXUfWejkKgiAI3YusnaEJgiAI3QsxaIIgCEJWIAZNEARByArEoAmCIAhZgRg0QRAEISsQgyYIgiBkBWLQBEEQhKxADJogCIKQFfx/4ZUjT6KERkMAAAAASUVORK5CYII=", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAbQAAAGkCAYAAABZ4tDdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAACNR0lEQVR4nO2de3xV1bXvB9sYIOYkUSFE1Ki0PsCIqBRE8c0JqFRFOahXfCDWj89zEK2F1iLX2sqhXmpLFcpHMYq1HuQoBerlIqQYURAjRkSIloM0IoY0pmEbY9hs97x/DH+ZY8291to7Ibx2xvfzWZ+19lprvtZO1thzzjF/o4sxxpCiKIqiHORE9ncFFEVRFKUjUIOmKIqiZARq0BRFUZSMQA2aoiiKkhGoQVMURVEyAjVoiqIoSkagBk1RFEXJCNSgKYqiKBmBGjRFURQlI1CDpiiKomQEGWnQnnzySTr++OOpW7duNHjwYFq7du3+rtIBzdSpU6lLly6e7ZRTTmm93tLSQnfffTcdeeSRlJubS9dccw3t2LHDk0dNTQ1dfvnllJOTQ4WFhfTjH/+Y4vH4vm7KfqWiooJ++MMfUu/evalLly60cOFCz3VjDE2ZMoWOOuoo6t69Ow0bNoz+9re/ee5paGigG264gfLy8qigoIDGjx9PTU1NnnvWr19P5513HnXr1o2OPfZYmj59+t5u2n4l1XO95ZZbkv5+R4wY4blHn2syjz32GP3gBz+gf/mXf6HCwkK66qqr6OOPP/bc01H/+ytXrqQzzzyTunbtSt///veprKxs7zTKZBgvvfSSyc7ONnPnzjUfffSR+dGPfmQKCgrMjh079nfVDlgefvhhc+qpp5ovvviidfvHP/7Rev2OO+4wxx57rFmxYoWprKw0Z599tjnnnHNar8fjcVNSUmKGDRtm3n//ffPaa6+ZHj16mMmTJ++P5uw3XnvtNfOzn/3MvPLKK4aIzKuvvuq5Pm3aNJOfn28WLlxoPvjgA3PFFVeYE044wXzzzTet94wYMcKcfvrpZs2aNebNN9803//+983111/fen3nzp2mV69e5oYbbjAbNmwwf/rTn0z37t3NH/7wh33VzH1Oqud68803mxEjRnj+fhsaGjz36HNNZvjw4ebZZ581GzZsMFVVVeayyy4zxcXFpqmpqfWejvjf37Jli8nJyTETJ040GzduNDNnzjSHHHKIWbp0aYe3KeMM2qBBg8zdd9/d+vnbb781vXv3No899th+rNWBzcMPP2xOP/1032uNjY3m0EMPNS+//HLruU2bNhkiMqtXrzbG8AsnEomY2tra1ntmzZpl8vLyzK5du/Zq3Q9U3BdvIpEwRUVF5te//nXrucbGRtO1a1fzpz/9yRhjzMaNGw0RmXfffbf1nv/7f/+v6dKli/n888+NMcY89dRT5vDDD/c815/85Cfm5JNP3sstOjAIMmhXXnllYBp9rulRV1dniMi88cYbxpiO+99/8MEHzamnnuop69prrzXDhw/v8DZk1JBjLBaj9957j4YNG9Z6LhKJ0LBhw2j16tX7sWYHPn/729+od+/e1KdPH7rhhhuopqaGiIjee+892r17t+eZnnLKKVRcXNz6TFevXk2nnXYa9erVq/We4cOHUzQapY8++mjfNuQA5dNPP6Xa2lrPc8zPz6fBgwd7nmNBQQENHDiw9Z5hw4ZRJBKhd955p/We888/n7Kzs1vvGT58OH388cf0z3/+cx+15sBj5cqVVFhYSCeffDLdeeed9OWXX7Ze0+eaHjt37iQioiOOOIKIOu5/f/Xq1Z48cM/eeCdnlEGrr6+nb7/91vNwiYh69epFtbW1+6lWBz6DBw+msrIyWrp0Kc2aNYs+/fRTOu+88+irr76i2tpays7OpoKCAk8a+Uxra2t9nzmuKfY5hP1t1tbWUmFhoed6VlYWHXHEEfqsQxgxYgQ9//zztGLFCvrP//xPeuONN+jSSy+lb7/9loj0uaZDIpGgCRMm0LnnnkslJSVERB32vx90TzQapW+++aZD25HVobkpByWXXnpp63H//v1p8ODBdNxxx9H8+fOpe/fu+7FmipKa6667rvX4tNNOo/79+9P3vvc9WrlyJV1yySX7sWYHD3fffTdt2LCBVq1atb+rskdkVA+tR48edMghhyR54ezYsYOKior2U60OPgoKCuikk06izZs3U1FREcViMWpsbPTcI59pUVGR7zPHNcU+h7C/zaKiIqqrq/Ncj8fj1NDQoM+6DfTp04d69OhBmzdvJiJ9rqm45557aMmSJfTXv/6VjjnmmNbzHfW/H3RPXl5eh/9gziiDlp2dTWeddRatWLGi9VwikaAVK1bQkCFD9mPNDi6amprof/7nf+ioo46is846iw499FDPM/3444+ppqam9ZkOGTKEPvzwQ89L4/XXX6e8vDzq16/fPq//gcgJJ5xARUVFnucYjUbpnXfe8TzHxsZGeu+991rvKS8vp0QiQYMHD269p6Kignbv3t16z+uvv04nn3wyHX744fuoNQc227Ztoy+//JKOOuooItLnGoQxhu655x569dVXqby8nE444QTP9Y763x8yZIgnD9yzV97JHe5msp956aWXTNeuXU1ZWZnZuHGjuf32201BQYHHC0fxcv/995uVK1eaTz/91Lz11ltm2LBhpkePHqaurs4Yw667xcXFpry83FRWVpohQ4aYIUOGtKaH625paampqqoyS5cuNT179ux0bvtfffWVef/99837779viMjMmDHDvP/+++bvf/+7MYbd9gsKCsyf//xns379enPllVf6uu2fccYZ5p133jGrVq0yJ554ose9vLGx0fTq1cvceOONZsOGDeall14yOTk5Ge1eHvZcv/rqK/PAAw+Y1atXm08//dQsX77cnHnmmebEE080LS0trXnoc03mzjvvNPn5+WblypWeJQ/Nzc2t93TE/z7c9n/84x+bTZs2mSeffFLd9tvCzJkzTXFxscnOzjaDBg0ya9as2d9VOqC59tprzVFHHWWys7PN0Ucfba699lqzefPm1uvffPONueuuu8zhhx9ucnJyzKhRo8wXX3zhyWPr1q3m0ksvNd27dzc9evQw999/v9m9e/e+bsp+5a9//ashoqTt5ptvNsaw6/7Pf/5z06tXL9O1a1dzySWXmI8//tiTx5dffmmuv/56k5uba/Ly8sy4cePMV1995bnngw8+MEOHDjVdu3Y1Rx99tJk2bdq+auJ+Iey5Njc3m9LSUtOzZ09z6KGHmuOOO8786Ec/SvoBq881Gb9nSkTm2Wefbb2no/73//rXv5oBAwaY7Oxs06dPH08ZHUmX7xqmKIqiKAc1GTWHpiiKonRe1KApiqIoGYEaNEVRFCUjUIOmKIqiZARq0BRFUZSMQA2aoiiKkhFkrEHbtWsXTZ06lXbt2rW/q5JR6HPdO+hz3Tvoc907HKjP9YBeh/bkk0/Sr3/9a6qtraXTTz+dZs6cSYMGDUorbTQapfz8fNq5cyfl5eXt5Zp2HvS57h30ue4d9LnuHQ7U53rA9tD+67/+iyZOnEgPP/wwrVu3jk4//XQaPnx4ksiooiiKohAdwAZt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", "text/plain": [ "
" ] @@ -602,7 +483,7 @@ }, { "data": { - "image/png": "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7ne/U0VFRbbrR48eVT179lQrVqxQSil10003qe9+97u2e9auXasAqObm5qT6JntokrpNAshJwum2HwzG7pM5nSWcya1MIEDiwfHK1NXpfgBKVVTQXlgwGFsPu95zfRUV9utmPQDVw3ter7yi89evp9d773Vvx1lPMKjLmPW011/eBzTHv3Wr/bs3X53tuu1HJbM/lkxya8/tc7z6nf+GEu2PZbptj+Pukj20/v37o6ysDK+99lpbXjAYxKZNmzBq1CgAwKhRoxAIBLBly5a2e9auXQvLsjBy5Mi2e9avX4+jR4+23bNmzRqcc845OPXUUzPZZUE48amspGW+U06x53MQRwDgfW0O0QHoV/O6WYbx+ehwtLPML3+pIysD+oAyB4l0qnf4fDqP6zPFgd00Ff1+0jUEKPQJM2gQtTd7dmw7bvXk51MZsx6+L15/zbFxf/m7du4xOfedzOTcXzPhvHjLgO253bdX1rn0Z/aLyzvznftZzjKptu12PdVxx8GzQQuFQqirq0NdXR0AcgSpq6tDQ0MDevTogRkzZuCxxx7DqlWrsG3bNkyZMgXl5eWYcOwcyuDBgzFu3Djccccd2Lx5M95++21Mnz4dkyZNQvkxtebvf//7yMnJwW233YYdO3bgj3/8I379619jJm92C4Kg2b6d9qt69iQnB0DHE9uzhz7zXld+vlab57hTfN1ZhsnNBR59NLZM374kI1VeTsuBS5boa+xkAQBPP63zWC6L+wmQgj5ADzDn/lQ4rA31c8/pfL+f2nv+eaqXjamzHq67tVUbLK4nFLL3zSmXZVm0L2j2l6NHOx0q+P72HEVSeEjHxc1YtHd2y61vznyn4XXbQ0ul7c4gqfU7g9dff13h2PTPTDfffLNSSinLstTDDz+sSktLVa9evdTll1+udu/ebavj888/VzfccIPKz89XBQUF6pZbblFffvml7Z4PPvhAXXLJJapXr17q61//upo3b56nfsqSo6Ruk5xLOLNmKfXqq+SqzufDamroNRJRau9e96WdwYPpOi851tRQrKpIRMcQc7rVA1RfUxPFIps7l1z8zbbr6+m1tVW/37uX6n71VXvfzP4OHEh5vEy6ebMu09iolN9P/WptpTbr65WaOpXK8HdRU0NnzcwjATyWwYPpu7r9dh35mvvLZTZvto9h8GCqx/ndt7fkmO7yYoaX8Dz3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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAbQAAAGkCAYAAABZ4tDdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAACNaElEQVR4nO2dfXxU1bX3F2MMEHOTqBAiVVRaX8CIqBRE8Z0bUKmKclEf8QWxfny9F9FaaC3yWFu51IdqqUL5KEax1otcpUB9eBBSjCiIESMiRMtFGhEDjWkYYwzDOPv5Y/nLXmfPOWdmQnibrO/ncz7nzDlnv51Jzpq999q/1ckYY0hRFEVRDnIi+7sCiqIoitIeqEFTFEVRsgI1aIqiKEpWoAZNURRFyQrUoCmKoihZgRo0RVEUJStQg6YoiqJkBWrQFEVRlKxADZqiKIqSFahBUxRFUbKCrDRoTz75JB133HHUpUsXGjRoEK1Zs2Z/V+mAZsqUKdSpUyfPdvLJJ7deb2lpobvuuouOPPJIys/Pp6uvvpq2b9/uyaO2tpYuu+wyysvLo+LiYvrJT35C8Xh8Xzdlv1JZWUk/+tGPqGfPntSpUydasGCB57oxhiZPnkxHHXUUde3alYYOHUp/+9vfPPc0NDTQ9ddfTwUFBVRUVETjxo2jpqYmzz3r1q2jc889l7p06ULHHHMMTZs2bW83bb+S6rnefPPNSX+/w4cP99yjzzWZRx99lH74wx/Sv/zLv1BxcTFdeeWV9PHHH3vuaa///RUrVtAZZ5xBnTt3ph/84AdUXl6+dxplsoyXXnrJ5Obmmjlz5piPPvrI/PjHPzZFRUVm+/bt+7tqBywPPfSQOeWUU8wXX3zRuv3jH/9ovX777bebY445xixfvtxUVVWZs846y5x99tmt1+PxuCktLTVDhw4177//vnnttddMt27dzKRJk/ZHc/Ybr732mvn5z39uXnnlFUNE5tVXX/Vcnzp1qiksLDQLFiwwH3zwgbn88svN8ccfb7755pvWe4YPH25OO+00s3r1avPmm2+aH/zgB+a6665rvb5z507To0cPc/3115v169ebP/3pT6Zr167mD3/4w75q5j4n1XO96aabzPDhwz1/vw0NDZ579LkmM2zYMPPss8+a9evXm+rqanPppZeaXr16maamptZ72uN/f/PmzSYvL89MmDDBbNiwwcyYMcMccsghZsmSJe3epqwzaAMHDjR33XVX6+dvv/3W9OzZ0zz66KP7sVYHNg899JA57bTTfK81NjaaQw891Lz88sut5zZu3GiIyKxatcoYwy+cSCRi6urqWu+ZOXOmKSgoMLt27dqrdT9QcV+8iUTClJSUmN/85jet5xobG03nzp3Nn/70J2OMMRs2bDBEZN59993We/7v//2/plOnTubzzz83xhjz1FNPmcMPP9zzXH/605+ak046aS+36MAgyKBdccUVgWn0uabHjh07DBGZN954wxjTfv/7DzzwgDnllFM8ZV1zzTVm2LBh7d6GrBpyjMVi9N5779HQoUNbz0UiERo6dCitWrVqP9bswOdvf/sb9ezZk3r37k3XX3891dbWEhHRe++9R7t37/Y805NPPpl69erV+kxXrVpFp556KvXo0aP1nmHDhlE0GqWPPvpo3zbkAOXTTz+luro6z3MsLCykQYMGeZ5jUVERDRgwoPWeoUOHUiQSoXfeeaf1nvPOO49yc3Nb7xk2bBh9/PHH9M9//nMftebAY8WKFVRcXEwnnXQS3XHHHfTll1+2XtPnmh47d+4kIqIjjjiCiNrvf3/VqlWePHDP3ngnZ5VBq6+vp2+//dbzcImIevToQXV1dfupVgc+gwYNovLyclqyZAnNnDmTPv30Uzr33HPpq6++orq6OsrNzaWioiJPGvlM6+rqfJ85rin2OYT9bdbV1VFxcbHnek5ODh1xxBH6rEMYPnw4Pf/887R8+XL6z//8T3rjjTfokksuoW+//ZaI9LmmQyKRoPHjx9M555xDpaWlRETt9r8fdE80GqVvvvmmXduR0665KQcll1xySetxv379aNCgQXTsscfSvHnzqGvXrvuxZoqSmmuvvbb1+NRTT6V+/frR97//fVqxYgVdfPHF+7FmBw933XUXrV+/nlauXLm/q7JHZFUPrVu3bnTIIYckeeFs376dSkpK9lOtDj6KioroxBNPpE2bNlFJSQnFYjFqbGz03COfaUlJie8zxzXFPoewv82SkhLasWOH53o8HqeGhgZ91hnQu3dv6tatG23atImI9Lmm4u6776bFixfTX//6Vzr66KNbz7fX/37QPQUFBe3+gzmrDFpubi6deeaZtHz58tZziUSCli9fToMHD96PNTu4aGpqov/5n/+ho446is4880w69NBDPc/0448/ptra2tZnOnjwYPrwww89L43XX3+dCgoKqG/fvvu8/gcixx9/PJWUlHieYzQapXfeecfzHBsbG+m9995rvaeiooISiQQNGjSo9Z7KykravXt36z2vv/46nXTSSXT44Yfvo9Yc2GzdupW+/PJLOuqoo4hIn2sQxhi6++676dVXX6WKigo6/vjjPdfb639/8ODBnjxwz155J7e7m8l+5qWXXjKdO3c25eXlZsOGDea2224zRUVFHi8cxct9991nVqxYYT799FPz1ltvmaFDh5pu3bqZHTt2GGPYdbdXr16moqLCVFVVmcGDB5vBgwe3pofrbllZmamurjZLliwx3bt373Bu+1999ZV5//33zfvvv2+IyEyfPt28//775u9//7sxht32i4qKzJ///Gezbt06c8UVV/i67Z9++unmnXfeMStXrjQnnHCCx728sbHR9OjRw9xwww1m/fr15qWXXjJ5eXlZ7V4e9ly/+uorc//995tVq1aZTz/91CxbtsycccYZ5oQTTjAtLS2teehzTeaOO+4whYWFZsWKFZ4lD83Nza33tMf/Ptz2f/KTn5iNGzeaJ598Ut32M2HGjBmmV69eJjc31wwcONCsXr16f1fpgOaaa64xRx11lMnNzTXf+973zDXXXGM2bdrUev2bb74xd955pzn88MNNXl6eGTlypPniiy88eWzZssVccsklpmvXrqZbt27mvvvuM7t3797XTdmv/PWvfzVElLTddNNNxhh23f/FL35hevToYTp37mwuvvhi8/HHH3vy+PLLL811111n8vPzTUFBgRk7dqz56quvPPd88MEHZsiQIaZz587me9/7npk6deq+auJ+Iey5Njc3m7KyMtO9e3dz6KGHmmOPPdb8+Mc/TvoBq881Gb9nSkTm2Wefbb2nvf73//rXv5r+/fub3Nxc07t3b08Z7Umn7xqmKIqiKAc1WTWHpiiKonRc1KApiqIoWYEaNEVRFCUrUIOmKIqiZAVq0BRFUZSsQA2aoiiKkhVkrUHbtWsXTZkyhXbt2rW/q5JV6HPdO+hz3Tvoc907HKjP9YBeh/bkk0/Sb37zG6qrq6PTTjuNZsyYQQMHDkwrbTQapcLCQtq5cycVFBTs5Zp2HPS57h30ue4d9LnuHQ7U53rA9tD+67/+iyZMmEAPPfQQrV27lk477TQaNmxYksiooiiKohAdwAZt+vTp9OMf/5jGjh1Lffv2pVmzZlFeXh7NmTNnf1dNURRFOQA5IOOhIfL0pEmTWs+lijy9a9cuz3guQh4gCqvSPkSjUc9eaR/0ue4d9LnuHfb1czXG0FdffUU9e/akSCS4H3ZAGrSwyNM1NTW+aR599FH63//7fyed79Wr116pY0fnmGOO2d9VyEr0ue4d9LnuHfb1c/3ss888MdtcDkiD1hYmTZpEEyZMaP28c+dO6tWrF31WW3tATVoqiqIomRGNRumYXr3oX/7lX0LvOyANWlsiT3fu3Jk6d+6cdL6goEANmqIoShbQqVOn0OsHpFOIRp5WFEVRMuWA7KEREU2YMIFuuukmGjBgAA0cOJAef/xx+vrrr2ns2LH7u2qKoijKAcgBa9CuueYa+sc//kGTJ0+muro66t+/Py1ZsiTJUURRFEVRiA5wpZA9oXUle2OjzqEpiqIcxESjUSosKkqpTHJAzqEpiqIoSqaoQVMURVGyAjVoiqIoSlagBk1RFEXJCtSgKYqiKFmBGjRFURQlK1CDpiiKomQFatAURVGUrEANmqIoipIVqEFTFEVRsgI1aIqiKEpWoAZNURRFyQrUoCmKoihZgRo0RVEUJStQg6YoiqJkBWrQFEVRlKxADZqiKIqSFahBUxRFUbICNWiKoihKVqAGTVEURckK1KApiqIoWYEaNEVRFCUrUIOmKIqiZAVq0BRFUZSsQA2aoiiKkhWoQVMURVGyAjVoiqIoSlagBk1RFEXJCtSgKYqiKFmBGjRFURQlK1CDpiiKomQFatAURVGUrEANmqIoipIVqEFTFEVRsgI1aIqiKEpWoAZNURRFyQrUoCmKoihZgRo0RVEUJStQg6YoiqJkBWrQFEVRlKxADZqiKIqSFahBUxRFUbICNWiKoihKVqAGTVEURckK1KApiqIoWYEaNEVRFCUrUIOmKIqiZAVq0BRFUZSsQA2aoiiKkhWoQVMURVGyAjVoiqIoSlagBk1RFEXJCtSgKYqiKFmBGjRFURQlK1CDpiiKomQFatAURVGUrEANmqIoipIVqEFTFEVRsgI1aIqiKEpWoAZNURRFyQrUoCmKoihZgRo0RVEUJStQg6YoiqJkBWrQFEVRlKxADZqiKIqSFahBUxRFUbICNWiKoihKVqAGTVEURckK1KApiqIoWYEaNEVRFCUrUIOmKIqiZAVq0BRFUZSsQA2aoiiKkhWoQVMURVGyAjVoiqIoSlbQ7gZtypQp1KlTJ8928sknt15vaWmhu+66i4488kjKz8+nq6++mrZv3+7Jo7a2li677DLKy8uj4uJi+slPfkLxeLy9q6ooiqJkETl7I9NTTjmFli1bZgvJscXce++99Je//IVefvllKiwspLvvvpuuuuoqeuutt4iI6Ntvv6XLLruMSkpK6O2336YvvviCbrzxRjr00EPp17/+9d6orqIoipIF7BWDlpOTQyUlJUnnd+7cSc888wy9+OKLdNFFFxER0bPPPkt9+vSh1atX01lnnUVLly6lDRs20LJly6hHjx7Uv39/+uUvf0k//elPacqUKZSbm7s3qqwoiqIc5OyVObS//e1v1LNnT+rduzddf/31VFtbS0RE7733Hu3evZuGDh3aeu/JJ59MvXr1olWrVhER0apVq+jUU0+lHj16tN4zbNgwikaj9NFHHwWWuWvXLopGo55NURRF6Ti0u0EbNGgQlZeX05IlS2jmzJn06aef0rnnnktfffUV1dXVUW5uLhUVFXnS9OjRg+rq6oiIqK6uzmPMcB3Xgnj00UepsLCwdTvmmGPat2GKoijKAU27Dzlecsklrcf9+vWjQYMG0bHHHkvz5s2jrl27tndxrUyaNIkmTJjQ+jkajapRUxRF6UDsdbf9oqIiOvHEE2nTpk1UUlJCsViMGhsbPfds3769dc6tpKQkyesRn/3m5UDnzp2poKDAsymKoigdh71u0Jqamuh//ud/6KijjqIzzzyTDj30UFq+fHnr9Y8//phqa2tp8ODBREQ0ePBg+vDDD2nHjh2t97z++utUUFBAffv23dvVVRRFUQ5S2n3I8f7776cf/ehHdOyxx9K2bdvooYceokMOOYSuu+46KiwspHHjxtGECRPoiCOOoIKCArrnnnto8ODBdNZZZxERUVlZGfXt25duuOEGmjZtGtXV1dGDDz5Id911F3Xu3Lm9q6soiqJkCe1u0LZu3UrXXXcdffnll9S9e3caMmQIrV69mrp3705ERL/97W8pEonQ1VdfTbt27aJhw4bRU0891Zr+kEMOocWLF9Mdd9xBgwcPpsMOO4xuuukmevjhh9u7qoqiKEoW0ckYY/Z3JfYG0WiUCgsLaWdjo86nKYqiHMREo1EqLCqinTt3hr7PVctRURRFyQrUoCmKoihZgRo0RVEUJStQg6YoiqJkBWrQFEVRlKxADZqiKIqSFahBUxRFUbICNWiKoihKVqAGTVEURckK1KApiqIoWYEaNEVRFCUrUIOmKIqiZAVq0BRFUZSsQA2aoiiKkhWoQVMURVGyAjVoiqIoSlagBk1RFEXJCtSgKYqiKFmBGjRFURQlK1CDpiiKomQFatAURVGUrEANmqIoipIVqEFTFEVRsgI1aIqiKEpWoAZNURRFyQrUoCmKoihZgRo0RVEUJStQg6YoiqJkBWrQFEVRlKxADZqiKIqSFahBUxRFUbICNWiKoihKVqAGTVEURckK1KApiqIoWYEaNEVRFCUrUIOmKIqiZAVq0BRFUZSsQA2aoiiKkhWoQVMURVGyAjVoiqIoSlagBk1RFEXJCtSgKYqiKFmBGjRFURQlK1CDpiiKomQFatAURVGUrEANmqIoipIVqEFTFEVRsgI1aIqiKEpWoAZNURRFyQrUoCmKoihZgRo0RVEUJStQg6YoiqJkBWrQFEVRlKxADZqiKIqSFahBUxRFUbICNWiKoihKVqAGTVEURckK1KApiqIoWYEaNEVRFCUrUIOmKIqiZAVq0BRFUZSsQA2aoiiKkhWoQVMURVGyAjVoiqIoSlagBk1RFEXJCtSgKYqiKFmBGjRFURQlK1CDpiiKomQFatAURVGUrEANmqIoipIVqEFTFEVRsgI1aIqiKEpWoAZNURRFyQrUoCmKoihZgRo0RVEUJStQg6YoiqJkBRkbtMrKSvrRj35EPXv2pE6dOtGCBQs8140xNHnyZDrqqKOoa9euNHToUPrb3/7muaehoYGuv/56KigooKKiIho3bhw1NTV57lm3bh2de+651KVLFzrmmGNo2rRpmbdOURRF6TBkbNC+/vprOu200+jJJ5/0vT5t2jT63e9+R7NmzaJ33nmHDjvsMBo2bBi1tLS03nP99dfTRx99RK+//jotXryYKisr6bbbbmu9Ho1GqaysjI499lh677336De/+Q1NmTKFZs+e3YYmKoqiKB2BTsYY0+bEnTrRq6++SldeeSURce+sZ8+edN9999H9999PREQ7d+6kHj16UHl5OV177bW0ceNG6tu3L7377rs0YMAAIiJasmQJXXrppbR161bq2bMnzZw5k37+859TXV0d5ebmEhHRxIkTacGCBVRTU+Nbl127dtGuXbta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", "text/plain": [ "
" ] @@ -612,7 +493,7 @@ }, { "data": { - "image/png": "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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAbQAAAGkCAYAAABZ4tDdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAe/UlEQVR4nO3df2yV5f3/8VcL9ADiOQcs7aHaMhDl948JUjqFxNnQImMyWQJIFB1ihi0LgowRJ7D5iVVY3JQAzmTSmUxQk4ETla0WSyeWH3ZUoGADDi1MTmHU9rQIpcD1/cNv73nkhxRbWt48H8mJ9L6vc851X+k5T885d9sY55wTAABXuNiWngAAAE2BoAEATCBoAAATCBoAwASCBgAwgaABAEwgaAAAEwgaAMAEggYAMIGgAQBMMBm0ZcuW6Xvf+57at2+v1NRUbd26taWn1KotWrRIMTExUZc+ffp4+0+cOKGsrCxdd9116tSpkyZMmKCKioqo2ygvL9fYsWPVsWNHJSQkaO7cuTp16tTlPpQWVVhYqHHjxikpKUkxMTFau3Zt1H7nnBYsWKBu3bqpQ4cOSk9P1969e6PGVFZWasqUKfL7/QoGg5o2bZpqa2ujxuzYsUMjR45U+/btlZycrMWLFzf3obWob1vXBx544Kzv38zMzKgxrOvZcnJydOutt+raa69VQkKCxo8fr7KysqgxTfXYLygo0C233CKfz6devXopNze3eQ7KGbN69WoXFxfnXnrpJVdaWuqmT5/ugsGgq6ioaOmptVoLFy50/fv3d4cOHfIuR44c8fb//Oc/d8nJyS4/P999+OGHbsSIEe4HP/iBt//UqVNuwIABLj093W3fvt29/fbbLj4+3s2fP78lDqfFvP322+7xxx93f/3rX50kt2bNmqj9Tz/9tAsEAm7t2rXuo48+cj/+8Y9djx493PHjx70xmZmZbvDgwW7z5s3un//8p+vVq5ebPHmyt7+6utolJia6KVOmuF27drlVq1a5Dh06uD/+8Y+X6zAvu29b16lTp7rMzMyo79/KysqoMazr2TIyMtzKlSvdrl27XElJibvrrrtcSkqKq62t9cY0xWP/3//+t+vYsaObPXu22717t1u6dKlr06aNW79+fZMfk7mgDR8+3GVlZXlfnz592iUlJbmcnJwWnFXrtnDhQjd48OBz7quqqnLt2rVzr7/+urdtz549TpIrKipyzn31hBMbG+vC4bA3ZsWKFc7v97u6urpmnXtr9c0n3jNnzrhQKOSWLFnibauqqnI+n8+tWrXKOefc7t27nSS3bds2b8w777zjYmJi3H/+8x/nnHPLly93nTt3jlrXefPmud69ezfzEbUO5wva3Xfffd7rsK4X5/Dhw06S27hxo3Ou6R77v/zlL13//v2j7mvixIkuIyOjyY/B1FuOJ0+eVHFxsdLT071tsbGxSk9PV1FRUQvOrPXbu3evkpKS1LNnT02ZMkXl5eWSpOLiYtXX10etaZ8+fZSSkuKtaVFRkQYOHKjExERvTEZGhiKRiEpLSy/vgbRS+/fvVzgcjlrHQCCg1NTUqHUMBoMaNmyYNyY9PV2xsbHasmWLN2bUqFGKi4vzxmRkZKisrExffPHFZTqa1qegoEAJCQnq3bu3ZsyYoaNHj3r7WNeLU11dLUnq0qWLpKZ77BcVFUXdRsOY5nhONhW0//73vzp9+nTU4kpSYmKiwuFwC82q9UtNTVVubq7Wr1+vFStWaP/+/Ro5cqRqamoUDocVFxenYDAYdZ2vr2k4HD7nmjfsw//W4ULfm+FwWAkJCVH727Ztqy5durDWF5CZmamXX35Z+fn5euaZZ7Rx40aNGTNGp0+flsS6XowzZ85o1qxZuu222zRgwABJarLH/vnGRCIRHT9+vEmPo22T3hquSGPGjPH+PWjQIKWmpqp79+567bXX1KFDhxacGfDtJk2a5P174MCBGjRokG688UYVFBTozjvvbMGZXTmysrK0a9cuvf/++y09le/E1Cu0+Ph4tWnT5qyzcCoqKhQKhVpoVleeYDCom2++Wfv27VMoFNLJkydVVVUVNebraxoKhc655g378L91uND3ZigU0uHDh6P2nzp1SpWVlax1I/Ts2VPx8fHat2+fJNb122RnZ2vdunV67733dMMNN3jbm+qxf74xfr+/yf+H2VTQ4uLiNHToUOXn53vbzpw5o/z8fKWlpbXgzK4stbW1+uSTT9StWzcNHTpU7dq1i1rTsrIylZeXe2ualpamnTt3Rj1p5OXlye/3q1+/fpd9/q1Rjx49FAqFotYxEoloy5YtUetYVVWl4uJib8yGDRt05swZpaamemMKCwtVX1/vjcnLy1Pv3r3VuXPny3Q0rdvBgwd19OhRdevWTRLrej7OOWVnZ2vNmjXasGGDevToEbW/qR77aWlpUbfRMKZZnpOb/DSTFrZ69Wrn8/lcbm6u2717t3v44YddMBiMOgsH0ebMmeMKCgrc/v373aZNm1x6erqLj493hw8fds59depuSkqK27Bhg/vwww9dWlqaS0tL867fcOru6NGjXUlJiVu/fr3r2rXrVXfafk1Njdu+fbvbvn27k+SeffZZt337dvfZZ5855746bT8YDLo33njD7dixw919993nPG3/+9//vtuyZYt7//333U033RR1enlVVZVLTEx09913n9u1a5dbvXq169ixo+nTyy+0rjU1Ne6xxx5zRUVFbv/+/e7dd991t9xyi7vpppvciRMnvNtgXc82Y8YMFwgEXEFBQdSPPHz55ZfemKZ47Dectj937ly3Z88et2zZMk7bb4ylS5e6lJQUFxcX54YPH+42b97c0lNq1SZOnOi6devm4uLi3PXXX+8mTpzo9u3b5+0/fvy4e+SRR1znzp1dx44d3U9+8hN36NChqNv49NNP3ZgxY1yHDh1cfHy8mzNnjquvr7/ch9Ki3nvvPSfprMvUqVOdc1+duv/EE0+4xMRE5/P53J133unKysqibuPo0aNu8uTJrlOnTs7v97sHH3zQ1dTURI356KOP3O233+58Pp+7/vrr3dNPP325DrFFXGhdv/zySzd69GjXtWtX165dO9e9e3c3ffr0s/4HlnU927nWVJJbuXKlN6apHvvvvfeeGzJkiIuLi3M9e/aMuo+mFPP/DwwAgCuaqc/QAABXL4IGADCBoAEATCBoAAATCBoAwASCBgAwwWzQ6urqtGjRItXV1bX0VExhXZsH69o8WNfm0VrXtVX/HNqyZcu0ZMkShcNhDR48WEuXLtXw4cMv6rqRSESBQEDV1dXy+/3NPNOrB+vaPFjX5sG6No/Wuq6t9hXaq6++qtmzZ2vhwoX617/+pcGDBysjI+OsXzIKAIDUioP27LPPavr06XrwwQfVr18/vfDCC+rYsaNeeumllp4aAKAVapV/D63hL0/Pnz/f2/Ztf3m6rq4u6v3chj950PBXWNE0IpFI1H/RNFjX5sG6No/Lva7OOdXU1CgpKUmxsed/HdYqg3ahvzz98ccfn/M6OTk5+s1vfnPW9pSUlGaZ49UuOTm5padgEuvaPFjX5nG51/XAgQNRf7Ptm1pl0C7F/PnzNXv2bO/r6upqpaSk6EB5eav60BIA0DiRSETJKSm69tprLziuVQbtUv7ytM/nk8/nO2u73+8naABgQExMzAX3t8qTQvjL0wCAxmqVr9Akafbs2Zo6daqGDRum4cOH6w9/+IOOHTumBx98sKWnBgBohVpt0CZOnKgjR45owYIFCofDGjJkiNavX3/WiSIAAEit/DeFfBfeT7JXVfEZGgBcwSKRiALB4Lf+ZpJW+RkaAACNRdAAACYQNACACQQNAGACQQMAmEDQAAAmEDQAgAkEDQBgAkEDAJhA0AAAJhA0AIAJBA0AYAJBAwCYQNAAACYQNACACQQNAGACQQMAmEDQAAAmEDQAgAkEDQBgAkEDAJhA0AAAJhA0AIAJBA0AYAJBAwCYQNAAACYQNACACQQNAGACQQMAmEDQAAAmEDQAgAkEDQBgAkEDAJhA0AAAJhA0AIAJBA0AYAJBAwCYQNAAACYQNACACQQNAGACQQMAmEDQAAAmEDQAgAkEDQBgAkEDAJhA0AAAJhA0AIAJBA0AYAJBAwCYQNAAACYQNACACQQNAGACQQMAmEDQAAAmEDQAgAkEDQBgAkEDAJhA0AAAJhA0AIAJBA0AYAJBAwCYQNAAACYQNACACQQNAGACQQMAmEDQAAAmEDQAgAkEDQBgAkEDAJhA0AAAJhA0AIAJBA0AYAJBAwCYQNAAACYQNACACU0etEWLFikmJibq0qdPH2//iRMnlJWVpeuuu06dOnXShAkTVFFREXUb5eXlGjt2rDp27KiEhATNnTtXp06dauqpAgAMadscN9q/f3+9++67/7uTtv+7m0cffVRvvfWWXn/9dQUCAWVnZ+uee+7Rpk2bJEmnT5/W2LFjFQqF9MEHH+jQoUO6//771a5dOz311FPNMV0AgAHNErS2bdsqFAqdtb26ulp/+tOf9Morr+iHP/yhJGnlypXq27evNm/erBEjRugf//iHdu/erXfffVeJiYkaMmSInnzySc2bN0+LFi1SXFxcc0wZAHCFa5bP0Pbu3aukpCT17NlTU6ZMUXl5uSSpuLhY9fX1Sk9P98b26dNHKSkpKioqkiQVFRVp4MCBSkxM9MZkZGQoEomotLT0vPdZV1enSCQSdQEAXD2aPGipqanKzc3V+vXrtWLFCu3fv18jR45UTU2NwuGw4uLiFAwGo66TmJiocDgsSQqHw1Exa9jfsO98cnJyFAgEvEtycnLTHhgAoFVr8rccx4wZ4/170KBBSk1NVffu3fXaa6+pQ4cOTX13nvnz52v27Nne15FIhKgBwFWk2U/bDwaDuvnmm7Vv3z6FQiGdPHlSVVVVUWMqKiq8z9xCodBZZz02fH2uz+Ua+Hw++f3+qAsA4OrR7EGrra3VJ598om7dumno0KFq166d8vPzvf1lZWUqLy9XWlqaJCktLU07d+7U4cOHvTF5eXny+/3q169fc08XAHCFavK3HB977DGNGzdO3bt31+eff66FCxeqTZs2mjx5sgKBgKZNm6bZs2erS5cu8vv9mjlzptLS0jRixAhJ0ujRo9WvXz/dd999Wrx4scLhsH79618rKytLPp+vqacLADCiyYN28OBBTZ48WUePHlXXrl11++23a/Pmzeratask6fe//71iY2M1YcIE1dXVKSMjQ8uXL/eu36ZNG61bt04zZsxQWlqarrnmGk2dOlW//e1vm3qqAABDYpxzrqUn0RwikYgCgYCqq6r4PA0ArmCRSESBYFDV1dUXfD7ndzkCAEwgaAAAEwgaAMAEggYAMIGgAQBMIGgAABMIGgDABIIGADCBoAEATCBoAAATCBoAwASCBgAwgaABAEwgaAAAEwgaAMAEggYAMIGgAQBMIGgAABMIGgDABIIGADCBoAEATCBoAAATCBoAwASCBgAwgaABAEwgaAAAEwgaAMAEggYAMIGgAQBMIGgAABMIGgDABIIGADCBoAEATCBoAAATCBoAwASCBgAwgaABAEwgaAAAEwgaAMAEggYAMIGgAQBMIGgAABMIGgDABIIGADCBoAEATCBoAAATCBoAwASCBgAwgaABAEwgaAAAEwgaAMAEggYAMIGgAQBMIGgAABMIGgDABIIGADCBoAEATCBoAAATCBoAwASCBgAwgaABAEwgaAAAEwgaAMAEggYAMIGgAQBMIGgAABMIGgDABIIGADCBoAEATCBoAAATCBoAwASCBgAwgaABAEwgaAAAEwgaAMAEggYAMKHRQSssLNS4ceOUlJSkmJgYrV27Nmq/c04LFixQt27d1KFDB6Wnp2vv3r1RYyorKzVlyhT5/X4Fg0FNmzZNtbW1UWN27NihkSNHqn379kpOTtbixYsbf3QAgKtGo4N27NgxDR48WMuWLTvn/sWLF+v555/XCy+8oC1btuiaa65RRkaG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", "text/plain": [ "
" ] @@ -752,97 +633,287 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "\n", + "f = torch.load('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/negf_out/HS_lead_L.pth')\n", + "\n", + "kpoints = f[\"kpoints\"]\n", + "kpoints_bloch = f[\"kpoints_bloch\"]\n", + "bloch_factor = f[\"bloch_factor\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "dict_keys(['kpoints', 'HL', 'SL', 'HDL', 'SDL', 'HLL', 'SLL'])" + "array([[0. , 0. , 0. ],\n", + " [0.33333333, 0. , 0. ],\n", + " [0.66666667, 0. , 0. ],\n", + " [0. , 0.33333333, 0. ],\n", + " [0.33333333, 0.33333333, 0. ],\n", + " [0.66666667, 0.33333333, 0. ],\n", + " [0. , 0.66666667, 0. ],\n", + " [0.33333333, 0.66666667, 0. ],\n", + " [0.66666667, 0.66666667, 0. ]])" ] }, - "execution_count": 2, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "hs_lead.keys()" + "from dptb.negf.bloch import Bloch\n", + "import numpy as np\n", + "bloch_factor = [3,3,1]\n", + "bloch_unfolder = Bloch(bloch_factor)\n", + "k_unfolds_list = []\n", + "kpoints = [[0,0,0]]\n", + "for kp in kpoints:\n", + " k_unfolds_list.append(bloch_unfolder.unfold_points(kp))\n", + "\n", + "k_unfolds_list\n", + "k_unfolds = np.concatenate(k_unfolds_list,axis=0)\n", + "k_unfolds\n", + "# kpoints_bloch = np.unique(k_unfolds,axis=0)" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "torch.Size([36, 2160, 2160])" + "tensor(0)" ] }, - "execution_count": 3, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "hs_lead['SLL'].shape" + "import torch\n", + "import matplotlib.pyplot as plt\n", + "bloch_indice_L= torch.load('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/negf_out/bloch_sorted_indice_lead_L.pth')\n", + "bloch_indice_R= torch.load('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/negf_out/bloch_sorted_indice_lead_R.pth')\n", + "# abs(sorted_basis_indices-bloch_indice_R).max()\n", + "# plt.plot(sorted_basis_indices-bloch_indice_R)\n", + "# plt.show()\n", + "abs(bloch_indice_R-bloch_indice_L).max()" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "import torch\n", + "import matplotlib.pyplot as plt\n", "\n", - "f = torch.load('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/negf_out/HS_lead_L.pth')\n", + "negf_out_bloch = torch.load('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/negf_out/negf.out.pth')\n", + "negf_out = torch.load('/personal/DFTB/Au_Ag/Au_TS/TS/negf_dptb_sub/sub_Au150/negf_out/negf.out.pth')\n", + "negf_out.keys()\n", "\n", - "kpoints = f[\"kpoints\"]\n", - "kpoints_bloch = f[\"kpoints_bloch\"]\n", - "bloch_factor = f[\"bloch_factor\"]" + "plt.plot(negf_out['uni_grid'],negf_out['T_k']['[0. 0.5 0. ]'])\n", + "plt.plot(negf_out_bloch['uni_grid'],negf_out_bloch['T_k']['[0. 0.5 0. ]'])\n", + "plt.xlabel('Energy (eV)')\n", + "plt.ylabel('Transmission')\n", + "plt.show()" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { + "image/png": "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", "text/plain": [ - "torch.Size([4, 6840, 2160])" + "
" ] }, - "execution_count": 8, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(negf_out['uni_grid'],negf_out['T_avg'])\n", + "plt.plot(negf_out_bloch['uni_grid'],negf_out_bloch['T_avg'])\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "ename": "IndexError", + "evalue": "list index out of range", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[10], line 7\u001b[0m\n\u001b[1;32m 5\u001b[0m bloch_sorted_indice \u001b[38;5;241m=\u001b[39m []\n\u001b[1;32m 6\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m ia \u001b[38;5;129;01min\u001b[39;00m sorted_indices:\n\u001b[0;32m----> 7\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m k \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(\u001b[43mh2k\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43matom_norbs\u001b[49m\u001b[43m[\u001b[49m\u001b[43mia\u001b[49m\u001b[43m]\u001b[49m): \n\u001b[1;32m 8\u001b[0m bloch_sorted_indice\u001b[38;5;241m.\u001b[39mappend(expand_basis_index[ia]\u001b[38;5;241m-\u001b[39mh2k\u001b[38;5;241m.\u001b[39matom_norbs[ia]\u001b[38;5;241m+\u001b[39mk)\n\u001b[1;32m 9\u001b[0m bloch_sorted_indice \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mstack(bloch_sorted_indice)\n", + "\u001b[0;31mIndexError\u001b[0m: list index out of range" + ] + } + ], + "source": [ + "expand_pos = origin_pos\n", + "sorted_indices = np.lexsort((expand_pos.numpy()[:, 0], expand_pos.numpy()[:, 1], expand_pos.numpy()[:, 2]))\n", + "\n", + "expand_basis_index = np.cumsum(h2k.atom_norbs)\n", + "bloch_sorted_indice = []\n", + "for ia in sorted_indices:\n", + " for k in range(h2k.atom_norbs[ia]): \n", + " bloch_sorted_indice.append(expand_basis_index[ia]-h2k.atom_norbs[ia]+k)\n", + "bloch_sorted_indice = np.stack(bloch_sorted_indice)\n", + "bloch_sorted_indice = torch.from_numpy(bloch_sorted_indice)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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+ctfuPPOO7nppptYunQpc+fOZf78+ce+9SIi4hjNCrTS0lKuv/569uzZQ3R0NP3792fRokVceOGFADz66KO4XC5GjRpFTU0N6enpPPXUU/b2ISEhvP3224wbN47U1FRatWrFmDFjuO++++yapKQk5s+fz8SJE3n88cfp3LkzzzzzDOnp6ceoySIi4kQ/+D60HyvdhyYi4gzH/T40ERGRHxMFmoiIOIICTUREHEGBJiIijqBAExERR1CgiYiIIyjQRETEERRoIiLiCAo0ERFxBAWaiIg4ggJNREQcQYEmIiKOoEATERFHUKCJiIgjKNBERMQRFGgiIuIICjQREXEEBZqIiDiCAk1ERBxBgSYiIo6gQBMREUdQoImIiCMo0ERExBEUaCIi4ggKNBERcQQFmoiIOIICTUREHEGBJiIijqBAExERR1CgiYiIIyjQRETEERRoIiLiCAo0ERFxBAWaiIg4ggJNREQcQYEmIiKOoEATERFHUKCJiIgjKNBERMQRFGgiIuIICjQREXEEBZqIiDiCAk1ERBxBgSYiIo7wgwLtgQcewLIsJkyYYC+rrq4mOzubdu3aERUVxahRoygpKQnarqioiIyMDCIjI4mNjWXy5MnU19cH1SxfvpxBgwbh8XhITk5m5syZP+RQRUTE4Y460D788EOefvpp+vfvH7R84sSJvPXWW7z66qusWLGC3bt3M3LkSHt9Q0MDGRkZ1NbW8sEHHzBr1ixmzpzJtGnT7Jrt27eTkZHBsGHDyM/PZ8KECdx8880sWrToaA9XREQc7qgCrbKyktGjR/O3v/2NNm3a2MvLy8t59tlneeSRRzj//PNJSUnh+eef54MPPmDVqlUAvPPOO2zcuJEXX3yR008/nREjRvD73/+eJ598ktraWgBycnJISkri4YcfplevXowfP54rr7ySRx999Bg0WUREnOioAi07O5uMjAzS0tKClufl5VFXVxe0vGfPnnTp0oXc3FwAcnNz6devH3FxcXZNeno6Pp+PDRs22DXf3Hd6erq9jyOpqanB5/MFvURE5OThbu4Gs2fP5uOPP+bDDz88bF1xcTFhYWHExMQELY+Li6O4uNiuaRpmjesb131Xjc/no6qqioiIiMM+e/r06fzud79rbnNERMQhmtVD27FjB7/61a946aWXCA8PP17HdFSmTp1KeXm5/dqxY8eJPiQR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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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+ctfuPPOO7nppptYunQpc+fOZf78+ce+9SIi4hjNCrTS0lKuv/569uzZQ3R0NP3792fRokVceOGFADz66KO4XC5GjRpFTU0N6enpPPXUU/b2ISEhvP3224wbN47U1FRatWrFmDFjuO++++yapKQk5s+fz8SJE3n88cfp3LkzzzzzDOnp6ceoySIi4kQ/+D60HyvdhyYi4gzH/T40ERGRHxMFmoiIOIICTUREHEGBJiIijqBAExERR1CgiYiIIyjQRETEERRoIiLiCAo0ERFxBAWaiIg4ggJNREQcQYEmIiKOoEATERFHUKCJiIgjKNBERMQRFGgiIuIICjQREXEEBZqIiDiCAk1ERBxBgSYiIo6gQBMREUdQoImIiCMo0ERExBEUaCIi4ggKNBERcQQFmoiIOIICTUREHEGBJiIijqBAExERR1CgiYiIIyjQRETEERRoIiLiCAo0ERFxBAWaiIg4ggJNREQcQYEmIiKOoEATERFHUKCJiIgjKNBERMQRFGgiIuIICjQREXEEBZqIiDiCAk1ERBxBgSYiIo7wgwLtgQcewLIsJkyYYC+rrq4mOzubdu3aERUVxahRoygpKQnarqioiIyMDCIjI4mNjWXy5MnU19cH1SxfvpxBgwbh8XhITk5m5syZP+RQRUTE4Y460D788EOefvpp+vfvH7R84sSJvPXWW7z66qusWLGC3bt3M3LkSHt9Q0MDGRkZ1NbW8sEHHzBr1ixmzpzJtGnT7Jrt27eTkZHBsGHDyM/PZ8KECdx8880sWrToaA9XREQc7qgCrbKyktGjR/O3v/2NNm3a2MvLy8t59tlneeSRRzj//PNJSUnh+eef54MPPmDVqlUAvPPOO2zcuJEXX3yR008/nREjRvD73/+eJ598ktraWgBycnJISkri4YcfplevXowfP54rr7ySRx999Bg0WUREnOioAi07O5uMjAzS0tKClufl5VFXVxe0vGfPnnTp0oXc3FwAcnNz6devH3FxcXZNeno6Pp+PDRs22DXf3Hd6erq9jyOpqanB5/MFvURE5OThbu4Gs2fP5uOPP+bDDz88bF1xcTFhYWHExMQELY+Li6O4uNiuaRpmjesb131Xjc/no6qqioiIiMM+e/r06fzud79rbnNERMQhmtVD27FjB7/61a946aWXCA8PP17HdFSmTp1KeXm5/dqxY8eJPiQR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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAbQAAAGkCAYAAABZ4tDdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAowUlEQVR4nO3de3TU9Z3/8VdCmEkAZ8ItM6QkNC4WiAIKSJj1sqtkiRq7tcYesFFZRT2wiRWiXLKyyFLXcPAoBeVStTWcUynCnkK5lGAWJFQJAaLRABKxpBsUJ3GLmQGWJEA+vz968v0xCtRAMOTD83HO9xTm+57vfD6HmucZ8h0SZYwxAgCgg4tu7wUAANAWCBoAwAoEDQBgBYIGALACQQMAWIGgAQCsQNAAAFYgaAAAKxA0AIAVCBoAwApWBm3RokX6/ve/r9jYWKWlpWnnzp3tvaQI27Zt0w9/+EMlJiYqKipKa9asiThvjNGsWbPUp08fxcXFKT09XQcOHIiYOXLkiLKzs+XxeBQfH68JEybo2LFjETMfffSRbrnlFsXGxiopKUnz5s27pPsqKCjQjTfeqKuuukoJCQm65557VFVVFTHT0NCgnJwc9ezZU926dVNWVpZqa2sjZmpqapSZmakuXbooISFBU6dO1alTpyJmtm7dqmHDhsntdqt///4qLCy8pHtbsmSJhgwZIo/HI4/Ho0AgoI0bN3b4fX3d3LlzFRUVpcmTJzuPdcS9zZ49W1FRURHHwIEDO/SezvT555/rgQceUM+ePRUXF6fBgwdr9+7dzvmO+jXkohnLrFixwrhcLvPrX//a7N271zz22GMmPj7e1NbWtvfSHH/4wx/MM888Y373u98ZSWb16tUR5+fOnWu8Xq9Zs2aN+fDDD80///M/m5SUFHPixAln5o477jBDhw41O3bsMH/84x9N//79zf333++cD4VCxufzmezsbLNnzx7z29/+1sTFxZlf/vKXl2xfGRkZ5o033jB79uwxFRUV5q677jLJycnm2LFjzszEiRNNUlKS2bx5s9m9e7cZNWqU+fu//3vn/KlTp8x1111n0tPTzQcffGD+8Ic/mF69epn8/Hxn5uDBg6ZLly4mLy/P7Nu3z7z88sumU6dOpqio6JLtbe3atWbDhg3mk08+MVVVVebf/u3fTOfOnc2ePXs69L7OtHPnTvP973/fDBkyxDz55JPO4x1xb88++6y59tprzRdffOEcX375ZYfeU4sjR46Yfv36mX/5l38xZWVl5uDBg2bTpk3m008/dWY66teQi2Vd0EaOHGlycnKc358+fdokJiaagoKCdlzVuX09aM3Nzcbv95sXXnjBeay+vt643W7z29/+1hhjzL59+4wks2vXLmdm48aNJioqynz++efGGGMWL15sunfvbhobG52Z6dOnmwEDBlziHf1/dXV1RpIpKSlx9tG5c2ezatUqZ+bjjz82kkxpaakx5q+xj46ONsFg0JlZsmSJ8Xg8zl6mTZtmrr322ojXGjt2rMnIyLjUW4rQvXt38/rrr1uxr6NHj5prrrnGFBcXm3/4h39wgtZR9/bss8+aoUOHnvVcR91Ti+nTp5ubb775nOdt+hrSWlb9lWNTU5PKy8uVnp7uPBYdHa309HSVlpa248q+verqagWDwYg9eL1epaWlOXsoLS1VfHy8RowY4cykp6crOjpaZWVlzsytt94ql8vlzGRkZKiqqkpfffXVd7KXUCgkSerRo4ckqby8XCdPnozY28CBA5WcnByxt8GDB8vn80WsOxwOa+/evc7Mmddomfmu/oxPnz6tFStW6Pjx4woEAlbsKycnR5mZmd94/Y68twMHDigxMVFXX321srOzVVNT0+H3JElr167ViBEj9JOf/EQJCQm64YYb9Nprrznnbfoa0lpWBe1///d/dfr06Yj/E0qSz+dTMBhsp1W1Tss6z7eHYDCohISEiPMxMTHq0aNHxMzZrnHma1xKzc3Nmjx5sm666SZdd911zuu6XC7Fx8d/Y12tWfe5ZsLhsE6cOHEptiNJqqysVLdu3eR2uzVx4kStXr1aqampHX5fK1as0Pvvv6+CgoJvnOuoe0tLS1NhYaGKioq0ZMkSVVdX65ZbbtHRo0c77J5aHDx4UEuWLNE111yjTZs2adKkSfrZz36mZcuWRayvo38NuRAx7b0A2CknJ0d79uzRu+++295LaTMDBgxQRUWFQqGQ/uu//kvjx49XSUlJey/rohw6dEhPPvmkiouLFRsb297LaTN33nmn8+shQ4YoLS1N/fr108qVKxUXF9eOK7t4zc3NGjFihJ5//nlJ0g033KA9e/Zo6dKlGj9+fDuvrn1Z9Q6tV69e6tSp0zfuVqqtrZXf72+nVbVOyzrPtwe/36+6urqI86dOndKRI0ciZs52jTNf41LJzc3V+vXr9c4776hv377O436/X01NTaqvr//Gulqz7nPNeDyeS/rFyuVyqX///ho+fLgKCgo0dOhQLViwoEPvq7y8XHV1dRo2bJhiYmIUExOjkpISLVy4UDExMfL5fB12b2eKj4/XD37wA3366acd+s9Lkvr06aPU1NSIxwYNGuT8laoNX0MulFVBc7lcGj58uDZv3uw81tzcrM2bNysQCLTjyr69lJQU+f3+iD2Ew2GVlZU5ewgEAqqvr1d5ebkzs2XLFjU3NystLc2Z2bZtm06ePOnMFBcXa8CAAerevfslWbsxRrm5uVq9erW2bNmilJSUiPPDhw9X586dI/ZWVVWlmpqaiL1VVlZG/MdWXFwsj8fj/EccCAQirtEy813/GTc3N6uxsbFD72v06NGqrKxURUWFc4wYMULZ2dnOrzvq3s507Ngx/elPf1KfPn069J+XJN10003f+DjMJ598on79+knq2F9DLlp735XS1lasWGHcbrcpLCw0+/btM48//riJj4+PuFupvR09etR88MEH5oMPPjCSzEsvvWQ++OAD8z//8z/GmL/echsfH29+//vfm48++sj86Ec/OusttzfccIMpKysz7777rrnmmmsibrmtr683Pp/PPPjgg2bPnj1mxYoVpkuXLpf0lttJkyYZr9drtm7dGnG79P/93/85MxMnTjTJyclmy5YtZvfu3SYQCJhAIOCcb7ldesyYMaaiosIUFRWZ3r17n/V26alTp5qPP/7YLFq06JLfLj1jxgxTUlJiqqurzUcffWRmzJhhoqKizNtvv92h93U2Z97laEzH3NtTTz1ltm7daqqrq817771n0tPTTa9evUxdXV2H3VOLnTt3mpiYGPOf//mf5sCBA+bNN980Xbp0Mb/5zW+cmY76NeRiWRc0Y4x5+eWXTXJysnG5XGbkyJFmx44d7b2kCO+8846R9I1j/Pjxxpi/3nb77//+78bn8xm3221Gjx5tqqqqIq7xl7/8xdx///2mW7duxuPxmIcfftgcPXo0YubDDz80N998s3G73eZ73/uemTt37iXd19n2JMm88cYbzsyJEyfMv/7rv5ru3bubLl26mB//+Mfmiy++iLjOn//8Z3PnnXeauLg406tXL/PUU0+ZkydPRsy888475vrrrzcul8tcffXVEa9xKTzyyCOm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1VeVk5MjSYqKitLkyZP13HPPae3ataqsrNRDDz2kxMRE3XPPPZL++o7ujjvu0GOPPaadO3fqvffeU25ursaNG6fExERJ0k9/+lO5XC5NmDBBe/fu1VtvvaUFCxYoLy+vbXcPALBGq76HduONN2r16tXKz8/XnDlzlJKSol/84hfKzs52ZqZNm6bjx4/r8ccfV319vW6++WYVFRUp9oxb5998803l5uZq9OjRio6OVlZWlhYuXOic93q9evvtt5WTk6Phw4erV69emjVrVsRn1QAAOFOrPofWkYTDYXm9Xj6HBgAd3CX5HBoAAJcrggYAsAJBAwBYgaABAKxA0AAAViBoAAArEDQAgBUIGgDACgQNAGAFggYAsAJBAwBYgaABAKxA0AAAViBoAAArEDQAgBUIGgDACgQNAGAFggYAsAJBAwBYgaABAKxA0AAAViBoAAArEDQAgBUIGgDACgQNAGAFggYAsAJBAwBYgaABAKxA0AAAViBoAAArEDQAgBUIGgDACgQNAGAFggYAsAJBAwBYgaABAKxA0AAAVohp7wVcKsYYSVJzQ0M7rwQAcDFavo63fF0/lyjztyY6qIMHD+rv/u7v2nsZAIA2cujQIfXt2/ec5619h9ajRw9JUk1Njbxebzuvpu2Ew2ElJSXp0KFD8ng87b2cNmXr3thXx2Pr3jrqvowxOnr0qBITE887Z23QoqP/+u1Br9fbof7gvi2Px2PlviR798a+Oh5b99YR9/Vt3phwUwgAwAoEDQBgBWuD5na79eyzz8rtdrf3UtqUrfuS7N0b++p4bN2brftqYe1djgCAK4u179AAAFcWggYAsAJBAwBYgaABAKxA0AAAViBoAAArEDQAgBUIGgDACv8PIFge2Nsbt14AAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import torch\n", + "se_bloch = torch.load('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/negf_out/se_bloch_k0.0_0.0_0.0_2.299999952316284.pth')\n", + "se_nobloch = torch.load('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/negf_out/se_nobloch_k0.0_0.0_0.0_2.299999952316284.pth')\n", + "\n", + "abs(se_bloch- se_nobloch)\n", + "import matplotlib.pyplot as plt\n", + "#可能是排序问题\n", + "plt.matshow(abs(se_bloch),vmin=-0.05,vmax=0.05)\n", + "plt.show()\n", + "plt.matshow(abs(se_nobloch),vmin=-0.05,vmax=0.05)\n", + "plt.show()\n", + "plt.matshow(abs(se_bloch-se_nobloch),vmin=-0.05,vmax=0.05)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[ 3.7597e+00-2.1603e+00j, 3.4871e+00-2.4462e+00j,\n", + " -2.2610e-05+3.9787e-05j, ...,\n", + " 0.0000e+00+0.0000e+00j, 0.0000e+00+0.0000e+00j,\n", + " 0.0000e+00+0.0000e+00j],\n", + " [ 3.4871e+00-2.4462e+00j, 3.0542e+00-2.7755e+00j,\n", + " -1.4149e-05+4.2846e-05j, ...,\n", + " 0.0000e+00+0.0000e+00j, 0.0000e+00+0.0000e+00j,\n", + " 0.0000e+00+0.0000e+00j],\n", + " [-2.2610e-05+3.9787e-05j, -1.4149e-05+4.2846e-05j,\n", + " 9.3115e-01-4.0130e-01j, ...,\n", + " 0.0000e+00+0.0000e+00j, 0.0000e+00+0.0000e+00j,\n", + " 0.0000e+00+0.0000e+00j],\n", + " ...,\n", + " [ 0.0000e+00+0.0000e+00j, 0.0000e+00+0.0000e+00j,\n", + " 0.0000e+00+0.0000e+00j, ...,\n", + " 0.0000e+00+0.0000e+00j, 0.0000e+00+0.0000e+00j,\n", + " 0.0000e+00+0.0000e+00j],\n", + " [ 0.0000e+00+0.0000e+00j, 0.0000e+00+0.0000e+00j,\n", + " 0.0000e+00+0.0000e+00j, ...,\n", + " 0.0000e+00+0.0000e+00j, 0.0000e+00+0.0000e+00j,\n", + " 0.0000e+00+0.0000e+00j],\n", + " [ 0.0000e+00+0.0000e+00j, 0.0000e+00+0.0000e+00j,\n", + " 0.0000e+00+0.0000e+00j, ...,\n", + " 0.0000e+00+0.0000e+00j, 0.0000e+00+0.0000e+00j,\n", + " 0.0000e+00+0.0000e+00j]], dtype=torch.complex128)" + ] + }, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "f['HDL'].shape" + "se_nobloch" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "torch.Size([36, 240, 240])" + "tensor(30)" ] }, - "execution_count": 9, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "f['HL'].shape" + "import torch\n", + "\n", + "bloch_leadL_indice = torch.load('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/negf_out/bloch_sorted_indice_lead_L.pth')\n", + "bloch_leadR_indice = torch.load('/personal/DeepTB/dptb_Zjj/DeePTB/dptb/negf/bloch_files/negf_out/bloch_sorted_indice_lead_R.pth')\n", + "abs(bloch_leadL_indice-bloch_leadR_indice).max()" ] } ], From 3157cbd8e8b3c51d0fb72aed19dce95e73cea861 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 26 Jun 2024 17:43:04 +0800 Subject: [PATCH 158/209] support useBloch and bloch_factor in json format --- dptb/postprocess/NEGF.py | 10 ++++++---- dptb/utils/argcheck.py | 7 +++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 97798074..40ccd9ff 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -54,10 +54,7 @@ def __init__(self, # self.jdata = jdata self.cdtype = torch.complex128 self.torch_device = torch_device - - self.useBloch = True - self.bloch_factor = [3,3,1] - + # get the parameters self.ele_T = ele_T self.kBT = Boltzmann * self.ele_T / eV2J # change to eV @@ -68,6 +65,11 @@ def __init__(self, self.sgf_solver = sgf_solver self.pbc = self.stru_options["pbc"] + if self.stru_options["lead_L"]["useBloch"] or self.stru_options["lead_R"]["useBloch"]: + assert self.stru_options["lead_L"]["bloch_factor"] == self.stru_options["lead_R"]["bloch_factor"], "bloch_factor should be the same for both leads in this version" + self.useBloch = True + self.bloch_factor = self.stru_options["lead_L"]["bloch_factor"] + # check the consistency of the kmesh and pbc assert len(self.pbc) == 3, "pbc should be a list of length 3" for i in range(3): diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 732c1199..99abeef0 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -995,10 +995,13 @@ def device(): def lead(): doc_id="" doc_voltage="" - + doc_useBloch="" + doc_bloch_factor="" return [ Argument("id", str, optional=False, doc=doc_id), - Argument("voltage", [int, float], optional=False, doc=doc_voltage) + Argument("voltage", [int, float], optional=False, doc=doc_voltage), + Argument("useBloch", bool, optional=True, default=False, doc=doc_useBloch), + Argument("bloch_factor", list, optional=True, default=[1,1,1], doc=doc_bloch_factor) ] def scf_options(): From 4395019e03a70f7062291486e594867b8a201e43 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 27 Jun 2024 11:13:44 +0800 Subject: [PATCH 159/209] add onsite_shift in loss.py HamilLossAnalysis --- dptb/nnops/loss.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dptb/nnops/loss.py b/dptb/nnops/loss.py index 7eb6b808..afaa6082 100644 --- a/dptb/nnops/loss.py +++ b/dptb/nnops/loss.py @@ -459,6 +459,7 @@ def __init__( overlap: bool=False, dtype: Union[str, torch.dtype] = torch.float32, decompose: bool = False, + onsite_shift: bool=False, device: Union[str, torch.device] = torch.device("cpu"), **kwargs, ): @@ -469,6 +470,7 @@ def __init__( self.decompose = decompose self.dtype = dtype self.device = device + self.onsite_shift = onsite_shift if basis is not None: self.idp = OrbitalMapper(basis, method="e3tb", device=self.device) @@ -485,6 +487,16 @@ def __init__( self.e3s = E3Hamiltonian(idp=self.idp, decompose=decompose, overlap=True, device=device, dtype=dtype) def __call__(self, data: AtomicDataDict, ref_data: AtomicDataDict, running_avg: bool=False): + + if self.onsite_shift: + data[AtomicDataDict.NODE_FEATURES_KEY][self.idp.mask_to_ndiag[data[AtomicDataDict.ATOM_TYPE_KEY].flatten()]] = \ + data[AtomicDataDict.NODE_FEATURES_KEY][self.idp.mask_to_ndiag[data[AtomicDataDict.ATOM_TYPE_KEY].flatten()]] - \ + data[AtomicDataDict.NODE_FEATURES_KEY][self.idp.mask_to_ndiag[data[AtomicDataDict.ATOM_TYPE_KEY].flatten()]].min() + + ref_data[AtomicDataDict.NODE_FEATURES_KEY][self.idp.mask_to_ndiag[ref_data[AtomicDataDict.ATOM_TYPE_KEY].flatten()]] = \ + ref_data[AtomicDataDict.NODE_FEATURES_KEY][self.idp.mask_to_ndiag[ref_data[AtomicDataDict.ATOM_TYPE_KEY].flatten()]] - \ + ref_data[AtomicDataDict.NODE_FEATURES_KEY][self.idp.mask_to_ndiag[ref_data[AtomicDataDict.ATOM_TYPE_KEY].flatten()]].min() + if self.decompose: data = self.e3h(data) ref_data = self.e3h(ref_data) From b124a930af12da10bb82c1b342b326442e29a3b6 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 27 Jun 2024 11:59:15 +0800 Subject: [PATCH 160/209] fix useBloch bug in NEGF.py --- dptb/postprocess/NEGF.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 40ccd9ff..8fac8170 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -69,6 +69,9 @@ def __init__(self, assert self.stru_options["lead_L"]["bloch_factor"] == self.stru_options["lead_R"]["bloch_factor"], "bloch_factor should be the same for both leads in this version" self.useBloch = True self.bloch_factor = self.stru_options["lead_L"]["bloch_factor"] + else: + self.useBloch = False + self.bloch_factor = [1,1,1] # check the consistency of the kmesh and pbc assert len(self.pbc) == 3, "pbc should be a list of length 3" From 174f0a241029a9c5acb25c9cb496266b4edc5048 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 28 Jun 2024 16:38:59 +0800 Subject: [PATCH 161/209] fix sort_indices bug in get_lead_structure --- dptb/negf/lead_property.py | 1 - dptb/negf/negf_hamiltonian_init.py | 27 +++++++++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 10bc6080..d8dcde45 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -119,7 +119,6 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S self.voltage_old = self.voltage self.kpoint = torch.tensor(kpoint) - self.se, _ = selfEnergy( ee=energy, hL=self.HL, diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 03d5918c..259b6e40 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -27,6 +27,7 @@ from dptb.negf.bloch import Bloch from dptb.negf.sort_btd import sort_lexico, sort_projection, sort_capacitance from dptb.negf.split_btd import show_blocks,split_into_subblocks,split_into_subblocks_optimized +from scipy.spatial import KDTree ''' a Hamiltonian object that initializes and manipulates device and lead Hamiltonians for NEGF ''' @@ -72,7 +73,7 @@ def __init__(self, ) -> None: # TODO: add dtype and device setting to the model - torch.set_default_dtype(torch.float64) + # torch.set_default_dtype(torch.float64) if isinstance(torch_device, str): torch_device = torch.device(torch_device) @@ -283,7 +284,7 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor nL = int(HK_lead.shape[1] / 2) - HLL, SLL = HK_lead[:, :nL, nL:], S_lead[:, :nL, nL:] # H_{L_first2L_second} + hLL, sLL = HK_lead[:, :nL, nL:], S_lead[:, :nL, nL:] # H_{L_first2L_second} hL, sL = HK_lead[:,:nL,:nL], S_lead[:,:nL,:nL] # lead hamiltonian in one principal layer if not useBloch: err_l_HK = (hL - HL).abs().max() @@ -298,7 +299,7 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor log.info(msg="The lead's hamiltonian or overlap attained from device and lead calculation does not match. \ The error is {:.7f}.".format(max(err_l_HK,err_l_SK))) log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") - raise RuntimeError + # raise RuntimeError elif 1e-7 <= max(err_l_HK,err_l_SK) <= 1e-4: log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(max(err_l_HK,err_l_SK))) @@ -307,8 +308,8 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor "SL":sL.cdouble(), "HDL":HDL.cdouble()*self.h_factor, "SDL":SDL.cdouble(), - "HLL":HLL.cdouble()*self.h_factor, - "SLL":SLL.cdouble() + "HLL":hLL.cdouble()*self.h_factor, + "SLL":sLL.cdouble() }) torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) @@ -397,7 +398,8 @@ def get_lead_structure(self,kk,natom,useBloch=False,bloch_factor=None,lead_id=No write(os.path.join(self.results_path, "stru_lead_fold_"+kk+".xyz"),stru_lead_fold,format='extxyz') log.info(msg="The lead structure is folded by Bloch theorem!") - stru_lead_fold_1PL = stru_lead_fold[:int(len(stru_lead_fold)/2)] + stru_lead_fold_1PL = stru_lead_fold[:int(len(stru_lead_fold)/2)] + stru_lead_fold_minz = stru_lead_fold_1PL.positions[:,2].min() bloch_R_list = []; expand_pos = [] for rz in range(bloch_factor[2]): for ry in range(bloch_factor[1]): @@ -406,13 +408,18 @@ def get_lead_structure(self,kk,natom,useBloch=False,bloch_factor=None,lead_id=No bloch_R_list.append(R) for id in range(len(stru_lead_fold_1PL)): pos = torch.tensor(stru_lead_fold_1PL.positions[id]) + \ - R[0]*bloch_reduce_cell[0] + R[1]*bloch_reduce_cell[1] + R[0]*bloch_reduce_cell[0] + R[1]*bloch_reduce_cell[1] - stru_lead_fold_minz*torch.tensor([0,0,1]) expand_pos.append(pos) - expand_pos = torch.stack(expand_pos) # expand_pos is for 1 PL + expand_pos = np.stack(expand_pos) # expand_pos is for 1 PL assert len(expand_pos) == int(len(stru_lead)/2) - sorted_indices = np.lexsort((expand_pos.numpy()[:, 0], expand_pos.numpy()[:, 1], expand_pos.numpy()[:, 2])) - # np.save(os.path.join(self.results_path, "sorted_indices_"+kk+".npy"), sorted_indices) + # get the corresponding indices of the expanded structure in the original structure by KD tree + struct_lead_minz = stru_lead.positions[:int(len(stru_lead)/2),2].min() + struct_lead_pos = np.array([pos - np.array([0,0,1])*struct_lead_minz for pos in stru_lead.positions[:int(len(stru_lead)/2)]]) + kdtree = KDTree(expand_pos) + _, sorted_indices = kdtree.query(struct_lead_pos,k=1,eps=1e-3) + + self.model.idp.get_orbital_maps() orb_dict = self.model.idp.norbs orb_list = np.array([ orb_dict[el] for el in stru_lead_fold_1PL.get_chemical_symbols()]*len(bloch_R_list)) From ac3f2ef0038432047547c3eb95ca86acf1709a63 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 27 Aug 2024 14:03:39 +0800 Subject: [PATCH 162/209] update files for bloch-self-energy in unittest --- dptb/entrypoints/run.py | 2 +- dptb/negf/lead_property.py | 13 ++++++++++--- dptb/negf/negf_hamiltonian_init.py | 2 +- dptb/postprocess/NEGF.py | 1 + dptb/tests/test_negf_device_property.py | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/dptb/entrypoints/run.py b/dptb/entrypoints/run.py index 11516c69..5019178e 100644 --- a/dptb/entrypoints/run.py +++ b/dptb/entrypoints/run.py @@ -12,7 +12,7 @@ from dptb.postprocess.NEGF import NEGF from dptb.postprocess.tbtrans_init import TBTransInputSet,sisl_installed -from dptb.postprocess.write_ham import write_ham +# from dptb.postprocess.write_ham import write_ham from dptb.postprocess.write_block import write_block import torch import h5py diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index d8dcde45..9454be64 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -8,7 +8,7 @@ import numpy as np from dptb.negf.bloch import Bloch import torch.profiler - +import ase log = logging.getLogger(__name__) @@ -67,7 +67,8 @@ class LeadProperty(object): ''' def __init__(self, tab, hamiltonian, structure, results_path, voltage,\ - structure_leads_fold,bloch_sorted_indice, useBloch: bool=False, bloch_factor: List[int]=[1,1,1],bloch_R_list:List=None,\ + structure_leads_fold:ase.Atoms=None,bloch_sorted_indice:torch.Tensor=None, useBloch: bool=False, \ + bloch_factor: List[int]=[1,1,1],bloch_R_list:List=None,\ e_T=300, efermi=0.0) -> None: self.hamiltonian = hamiltonian self.structure = structure @@ -80,12 +81,18 @@ def __init__(self, tab, hamiltonian, structure, results_path, voltage,\ self.mu = self.efermi - self.voltage self.kpoint = None self.voltage_old = None - self.structure_leads_fold = structure_leads_fold + self.useBloch = useBloch self.bloch_factor = bloch_factor self.bloch_sorted_indice = bloch_sorted_indice self.bloch_R_list = bloch_R_list + self.structure_leads_fold = structure_leads_fold + if self.useBloch: + assert self.bloch_sorted_indice is not None + assert self.bloch_R_list is not None + assert self.bloch_factor is not None + assert self.structure_leads_fold is not None def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-Sancho", \ ): diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 259b6e40..2ec2746c 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -73,7 +73,7 @@ def __init__(self, ) -> None: # TODO: add dtype and device setting to the model - # torch.set_default_dtype(torch.float64) + torch.set_default_dtype(torch.float64) if isinstance(torch_device, str): torch_device = torch.device(torch_device) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 8fac8170..508c40a3 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -113,6 +113,7 @@ def __init__(self, results_path=self.results_path, torch_device = self.torch_device) with torch.no_grad(): + # if useBloch is None, structure_leads_fold,bloch_sorted_indices,bloch_R_lists = None,None,None struct_device, struct_leads,structure_leads_fold,bloch_sorted_indices,bloch_R_lists,subblocks = \ self.negf_hamiltonian.initialize(kpoints=self.kpoints,block_tridiagnal=self.block_tridiagonal,\ useBloch=self.useBloch,bloch_factor=self.bloch_factor) diff --git a/dptb/tests/test_negf_device_property.py b/dptb/tests/test_negf_device_property.py index a3b8b13a..45271472 100644 --- a/dptb/tests/test_negf_device_property.py +++ b/dptb/tests/test_negf_device_property.py @@ -44,7 +44,7 @@ def test_negf_Device(root_directory): # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) with torch.no_grad(): - struct_device, struct_leads, _ = hamiltonian.initialize(kpoints=kpoints) + struct_device, struct_leads, _,_,_,_ = hamiltonian.initialize(kpoints=kpoints) deviceprop = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=negf_json['task_options']['e_fermi']) From 3ecc157e58304f80aedc4d46a221f3d54ec51f70 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 27 Aug 2024 14:09:52 +0800 Subject: [PATCH 163/209] update unittest files for hamiltonian.initialize's update --- dptb/tests/test_negf_density_Ozaki.py | 2 +- dptb/tests/test_negf_negf_hamiltonian_init.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dptb/tests/test_negf_density_Ozaki.py b/dptb/tests/test_negf_density_Ozaki.py index 3e011940..20e36d13 100644 --- a/dptb/tests/test_negf_density_Ozaki.py +++ b/dptb/tests/test_negf_density_Ozaki.py @@ -48,7 +48,7 @@ def test_negf_density_Ozaki(root_directory): # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) with torch.no_grad(): - struct_device, struct_leads, _ = hamiltonian.initialize(kpoints=kpoints) + struct_device, struct_leads, _,_,_,_ = hamiltonian.initialize(kpoints=kpoints) deviceprop = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=negf_json['task_options']['e_fermi']) diff --git a/dptb/tests/test_negf_negf_hamiltonian_init.py b/dptb/tests/test_negf_negf_hamiltonian_init.py index 32bdceb4..65a96294 100644 --- a/dptb/tests/test_negf_negf_hamiltonian_init.py +++ b/dptb/tests/test_negf_negf_hamiltonian_init.py @@ -46,7 +46,7 @@ def test_negf_Hamiltonian(root_directory): # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) with torch.no_grad(): - struct_device, struct_leads, _ = hamiltonian.initialize(kpoints=kpoints) + struct_device, struct_leads, _,_,_,_ = hamiltonian.initialize(kpoints=kpoints) deviceprop = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=negf_json['task_options']['e_fermi']) From dd4acfbb4c3c4ef5a2ea615d9b852665ec13a185 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 28 Aug 2024 11:29:44 +0800 Subject: [PATCH 164/209] use rmse as check standard in comparing HK_lead --- dptb/negf/negf_hamiltonian_init.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 2ec2746c..5c782b6a 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -80,6 +80,7 @@ def __init__(self, self.torch_device = torch_device self.model = model self.AtomicData_options = AtomicData_options + log.info(msg="The AtomicData_options is {}".format(AtomicData_options)) self.model.eval() # get bondlist with pbc in all directions for complete chemical environment @@ -289,19 +290,24 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor if not useBloch: err_l_HK = (hL - HL).abs().max() err_l_SK = (sL - SL).abs().max() + rmse_l_HK = abs(torch.sqrt(torch.mean((hL - HL) ** 2))) + rmse_l_SK = abs(torch.sqrt(torch.mean((sL - SL) ** 2))) + else: #TODO: add err_l_Hk and err_l_SK check in bloch case err_l_HK = 0 err_l_SK = 0 - if max(err_l_HK,err_l_SK) >= 1e-3: + + # if max(err_l_HK,err_l_SK) >= 1e-3: + if max(rmse_l_HK,rmse_l_SK) >= 1e-4: # check the lead hamiltonian get from device and lead calculation matches each other # a standard check to see the lead environment is bulk-like or not - log.info(msg="The lead's hamiltonian or overlap attained from device and lead calculation does not match. \ - The error is {:.7f}.".format(max(err_l_HK,err_l_SK))) + # Here we use RMSE to check the difference between the lead's hamiltonian and overlap + log.info(msg="The lead's hamiltonian or overlap attained from device and lead calculation does not match. RMSE for HK is {:.7f} and for SK is {:.7f} ".format(rmse_l_HK,rmse_l_SK)) log.error(msg="ERROR, the lead's hamiltonian attained from diffferent methods does not match.") - # raise RuntimeError - elif 1e-7 <= max(err_l_HK,err_l_SK) <= 1e-4: - log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences {:.7f}.".format(max(err_l_HK,err_l_SK))) + # elif 1e-7 <= max(err_l_HK,err_l_SK) <= 1e-4: + elif 1e-7 <= max(rmse_l_HK,rmse_l_SK) <= 1e-4: + log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences RMSE = {:.7f}.".format(max(rmse_l_HK,rmse_l_SK))) HS_leads.update({ "HL":hL.cdouble()*self.h_factor, From 482e7e1339acfdbc4a05e015ed83d0de6196e7a4 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 28 Aug 2024 11:31:21 +0800 Subject: [PATCH 165/209] remove self energy saver temporarily --- dptb/negf/lead_property.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 9454be64..b2c9d534 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -185,8 +185,12 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S else: eeshifted = energy + self.efermi self.se = (eeshifted*self.SDL-self.HDL) @ sgf_k[:b,:b] @ (eeshifted*self.SDL.conj().T-self.HDL.conj().T) - # torch.save(self.se, os.path.join(self.results_path, f"se_bloch_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_{energy}.pth")) - + + + # if self.useBloch: + # torch.save(self.se, os.path.join(self.results_path, f"se_bloch_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_{energy}.pth")) + # else: + # torch.save(self.se, os.path.join(self.results_path, f"se_nobloch_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_{energy}.pth")) def sigmaLR2Gamma(self, se): '''calculate the Gamma function from the self energy. From 06d5443433e031b712e7f1312357cab7ac99dd9d Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 28 Aug 2024 12:47:37 +0800 Subject: [PATCH 166/209] fix pbc bug to accomdate the removing of pbc in AtomicData_options --- dptb/negf/negf_hamiltonian_init.py | 7 +++++-- .../tests/data/test_negf/test_negf_run/negf_chain_new.json | 3 +-- .../data/test_negf/test_negf_run/negf_graphene_new.json | 3 +-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 5c782b6a..756f8770 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -172,11 +172,14 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor # device_id[0] = n_proj_atom_pre # device_id[1] = n_proj_atom_pre + n_proj_atom_device # self.device_id = device_id - + self.structase.set_pbc(self.pbc_negf) + self.structase.pbc[2] = True alldata = AtomicData.from_ase(self.structase, **self.AtomicData_options) + print('alldata[AtomicDataDict.PBC_KEY]',alldata[AtomicDataDict.PBC_KEY]) alldata[AtomicDataDict.PBC_KEY][2] = True # force pbc in z-axis to get reasonable chemical environment in two ends - + print('alldata[AtomicDataDict.PBC_KEY]',alldata[AtomicDataDict.PBC_KEY]) + alldata = AtomicData.to_AtomicDataDict(alldata.to(self.torch_device)) self.alldata = self.model.idp(alldata) self.alldata[AtomicDataDict.KPOINT_KEY] = \ diff --git a/dptb/tests/data/test_negf/test_negf_run/negf_chain_new.json b/dptb/tests/data/test_negf/test_negf_run/negf_chain_new.json index 0ad717d3..d8140fbd 100644 --- a/dptb/tests/data/test_negf/test_negf_run/negf_chain_new.json +++ b/dptb/tests/data/test_negf/test_negf_run/negf_chain_new.json @@ -52,8 +52,7 @@ "out_current_nscf": true }, "AtomicData_options" :{ - "r_max": 2.0, - "pbc": true + "r_max": 2.0 }, "structure":"./chain.vasp" } \ No newline at end of file diff --git a/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json b/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json index 5ce5d1c2..269bdba1 100644 --- a/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json +++ b/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json @@ -55,8 +55,7 @@ "out_lcurrent": false }, "AtomicData_options" :{ - "r_max": 2.0, - "pbc": true + "r_max": 2.0 }, "structure":"./graphene.xyz" } \ No newline at end of file From 8748b24b8090506848add3b0a084e30df33f6b00 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 28 Aug 2024 17:41:39 +0800 Subject: [PATCH 167/209] remove pbc output --- dptb/negf/negf_hamiltonian_init.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 756f8770..8e1cc2e0 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -176,9 +176,8 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor self.structase.set_pbc(self.pbc_negf) self.structase.pbc[2] = True alldata = AtomicData.from_ase(self.structase, **self.AtomicData_options) - print('alldata[AtomicDataDict.PBC_KEY]',alldata[AtomicDataDict.PBC_KEY]) alldata[AtomicDataDict.PBC_KEY][2] = True # force pbc in z-axis to get reasonable chemical environment in two ends - print('alldata[AtomicDataDict.PBC_KEY]',alldata[AtomicDataDict.PBC_KEY]) + alldata = AtomicData.to_AtomicDataDict(alldata.to(self.torch_device)) self.alldata = self.model.idp(alldata) From 1dbdbecbdc53303bc2f3b30461b198c74194e5e3 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 28 Aug 2024 17:42:51 +0800 Subject: [PATCH 168/209] add overlap output in feature_to_block --- dptb/data/interfaces/ham_to_feature.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/dptb/data/interfaces/ham_to_feature.py b/dptb/data/interfaces/ham_to_feature.py index cc39b982..49a109a2 100644 --- a/dptb/data/interfaces/ham_to_feature.py +++ b/dptb/data/interfaces/ham_to_feature.py @@ -319,16 +319,25 @@ def block_to_feature(data, idp, blocks=False, overlap_blocks=False, orthogonal=F # if overlap_blocks: # data[_keys.EDGE_OVERLAP_KEY] = torch.as_tensor(np.array(edge_overlap), dtype=torch.get_default_dtype()) -def feature_to_block(data, idp): +def feature_to_block(data, idp, mode="H"): idp.get_orbital_maps() idp.get_orbpair_maps() has_block = False - if data.get(_keys.NODE_FEATURES_KEY, None) is not None: - node_features = data[_keys.NODE_FEATURES_KEY] - edge_features = data[_keys.EDGE_FEATURES_KEY] - has_block = True - blocks = {} + if mode == "H": + if data.get(_keys.NODE_FEATURES_KEY, None) is not None: + node_features = data[_keys.NODE_FEATURES_KEY] + edge_features = data[_keys.EDGE_FEATURES_KEY] + has_block = True + blocks = {} + elif mode == "S": + if data.get(_keys.NODE_OVERLAP_KEY, None) is not None: + node_features = data[_keys.NODE_OVERLAP_KEY] + edge_features = data[_keys.EDGE_OVERLAP_KEY] + has_block = True + blocks = {} + else: + raise ValueError("Mode should be either 'H' or 'S'.") idp.get_orbital_maps() idp.get_orbpair_maps() From 633d5406c830518121951e7e850748e2a1431e4d Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 29 Aug 2024 10:37:49 +0800 Subject: [PATCH 169/209] set rmse_l_HK/SK as 0 when using Block --- dptb/negf/negf_hamiltonian_init.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 8e1cc2e0..dd1cc00b 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -298,7 +298,8 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor else: #TODO: add err_l_Hk and err_l_SK check in bloch case err_l_HK = 0 err_l_SK = 0 - + rmse_l_HK = 0 + rmse_l_SK = 0 # if max(err_l_HK,err_l_SK) >= 1e-3: if max(rmse_l_HK,rmse_l_SK) >= 1e-4: From cdc7fd5872b8abaf46cad03007b4048a5a5f9fb2 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 28 Sep 2024 11:14:58 +0800 Subject: [PATCH 170/209] add block_tridiagonal in compute_density_Ozaki --- dptb/negf/density.py | 4 ++-- dptb/postprocess/NEGF.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index 7416d12c..84f442c6 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -140,7 +140,7 @@ def __init__(self, R, M_cut, n_gauss): self.R = R self.n_gauss = n_gauss - def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.,Vbias=None): + def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.,Vbias=None,block_tridiagonal=False): '''calculates the equilibrium and non-equilibrium density matrices for a given k-point. Parameters @@ -166,7 +166,7 @@ def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.,Vbias=None) poles = 1j* self.poles * kBT + deviceprop.lead_L.mu - deviceprop.mu # left lead expression for rho_eq deviceprop.lead_L.self_energy(kpoint=kpoint, energy=1j*self.R-deviceprop.mu) deviceprop.lead_R.self_energy(kpoint=kpoint, energy=1j*self.R-deviceprop.mu) - deviceprop.cal_green_function(energy=1j*self.R-deviceprop.mu, kpoint=kpoint, block_tridiagonal=False, + deviceprop.cal_green_function(energy=1j*self.R-deviceprop.mu, kpoint=kpoint, block_tridiagonal=block_tridiagonal, Vbias = Vbias) g0 = deviceprop.grd[0] DM_eq = 1.0j * self.R * g0 diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 508c40a3..10ce3366 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -568,7 +568,7 @@ def compute_current_nscf(self, ee, tc): return self.deviceprop._cal_current_nscf_(ee, tc) def compute_density_Ozaki(self, kpoint,Vbias): - DM_eq, DM_neq = self.density.integrate(deviceprop=self.deviceprop, kpoint=kpoint, Vbias=Vbias) + DM_eq, DM_neq = self.density.integrate(deviceprop=self.deviceprop, kpoint=kpoint, Vbias=Vbias, block_tridiagonal=self.block_tridiagonal) return DM_eq, DM_neq From 6cf54c2524b1997d4698746f145c9bf25fd3d87f Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 11 Oct 2024 19:29:59 +0800 Subject: [PATCH 171/209] add block_tri in deviceprop.cal_green_function --- dptb/negf/density.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index 84f442c6..49a4e0ce 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -173,7 +173,7 @@ def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.,Vbias=None, for i, e in enumerate(poles): deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) - deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device,\ + deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=block_tridiagonal, eta_device=eta_device,\ Vbias = Vbias) term = ((-4 * 1j * kBT) * deviceprop.grd[0] * self.residues[i]).imag DM_eq -= term @@ -189,7 +189,7 @@ def integrate(self, deviceprop, kpoint, eta_lead=1e-5, eta_device=0.,Vbias=None, for i, e in enumerate(xs): deviceprop.lead_L.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) deviceprop.lead_R.self_energy(kpoint=kpoint, energy=e, eta_lead=eta_lead) - deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=False, eta_device=eta_device) + deviceprop.cal_green_function(energy=e, kpoint=kpoint, block_tridiagonal=block_tridiagonal, eta_device=eta_device) gr_gamma_ga = torch.mm(torch.mm(deviceprop.grd[0], deviceprop.lead_R.gamma), deviceprop.grd[0].conj().T).real gr_gamma_ga = gr_gamma_ga * (deviceprop.lead_R.fermi_dirac(e+deviceprop.mu) - deviceprop.lead_L.fermi_dirac(e+deviceprop.mu)) DM_neq = DM_neq + wlg[i] * gr_gamma_ga From 24b6f8740102cc70aec6c32af9e0d6e9cc08edba Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sun, 20 Oct 2024 20:04:24 +0800 Subject: [PATCH 172/209] update NEGF.py for poisson-NEGF SCF --- dptb/postprocess/NEGF.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 10ce3366..b0983dae 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -118,8 +118,6 @@ def __init__(self, self.negf_hamiltonian.initialize(kpoints=self.kpoints,block_tridiagnal=self.block_tridiagonal,\ useBloch=self.useBloch,bloch_factor=self.bloch_factor) self.subblocks = subblocks # for not block_tridiagonal case, subblocks is [HD.shape[1]] - self.left_connected = abs(struct_device.positions[:,2]-min(struct_device.positions[:,2]))<1e-6 - self.right_connected = abs(struct_device.positions[:,2]-max(struct_device.positions[:,2]))<1e-6 self.deviceprop = DeviceProperty(self.negf_hamiltonian, struct_device, results_path=self.results_path, efermi=self.e_fermi) self.deviceprop.set_leadLR( @@ -169,6 +167,14 @@ def __init__(self, # number of orbitals on atoms in device region self.device_atom_norbs = self.negf_hamiltonian.h2k.atom_norbs[self.negf_hamiltonian.device_id[0]:self.negf_hamiltonian.device_id[1]] + left_connected_atom_mask = abs(struct_device.positions[:,2]-min(struct_device.positions[:,2]))<1e-6 + right_connected_atom_mask = abs(struct_device.positions[:,2]-max(struct_device.positions[:,2]))<1e-6 + + self.left_connected_orb_mask = torch.tensor( [p for p, norb in zip(left_connected_atom_mask, self.device_atom_norbs) \ + for _ in range(norb)],dtype=torch.bool) + self.right_connected_orb_mask = torch.tensor( [p for p, norb in zip(right_connected_atom_mask, self.device_atom_norbs) \ + for _ in range(norb)],dtype=torch.bool) + # np.save(self.results_path+"/device_atom_norbs.npy",self.device_atom_norbs) # geting the output settings @@ -377,10 +383,10 @@ def negf_compute(self,scf_require=False,Vbias=None): if Vbias is not None and self.density_options["method"] == "Fiori": # set voltage as -1*potential_at_orb[0] and -1*potential_at_orb[-1] for self-energy same as in NanoTCAD if ll == 'lead_L' : - getattr(self.deviceprop, ll).voltage = Vbias[self.left_connected].mean() + getattr(self.deviceprop, ll).voltage = Vbias[self.left_connected_orb_mask].mean() # getattr(self.deviceprop, ll).voltage = Vbias[0] else: - getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected].mean() + getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected_orb_mask].mean() # getattr(self.deviceprop, ll).voltage = Vbias[-1] self.density.density_integrate_Fiori( @@ -416,10 +422,10 @@ def negf_compute(self,scf_require=False,Vbias=None): if Vbias is not None and self.density_options["method"] == "Fiori": # set voltage as -1*potential_at_orb[0] and -1*potential_at_orb[-1] for self-energy same as in NanoTCAD if ll == 'lead_L': - getattr(self.deviceprop, ll).voltage = self.potential_at_atom[self.left_connected].mean() + getattr(self.deviceprop, ll).voltage = Vbias[self.left_connected_orb_mask].mean() else: - getattr(self.deviceprop, ll).voltage = self.potential_at_atom[self.right_connected].mean() + getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected_orb_mask].mean() getattr(self.deviceprop, ll).self_energy( From c89f2fba0de71f2c28b4d4cd86a00a5ecb9a80c8 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 28 Oct 2024 11:19:02 +0800 Subject: [PATCH 173/209] add dope region in poisson-NEGF SCF --- dptb/negf/poisson_init.py | 27 +++++++++++++++++++++------ dptb/postprocess/NEGF.py | 18 +++++++++++++----- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 44afdb57..4c6414f0 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -53,7 +53,7 @@ def __init__(self,xg,yg,zg,xa,ya,za): # print('Number of grid points: ',self.Np,' grid shape: ',self.grid_coord.shape,' Number of atoms: ',self.Na) # find the index of the atoms in the grid - self.atom_index_dict = self.find_atom_index(xa,ya,za) + self.atom_index_dict = self.get_atom_index(xa,ya,za) # create surface area for each grid point along x,y,z axis @@ -75,7 +75,7 @@ def __init__(self,xg,yg,zg,xa,ya,za): self.surface_grid = surface_grid # grid points order are the same as that of self.grid_coord - def find_atom_index(self,xa,ya,za): + def get_atom_index(self,xa,ya,za): # find the index of the atoms in the grid swap = {} for atom_index in range(self.Na): @@ -142,14 +142,29 @@ def __init__(self,grid,gate_list,dielectric_list): # store the boundary information: xmin,xmax,ymin,ymax,zmin,zmax,gate self.boudnary_points = {i:"in" for i in range(self.grid.Np)} # initially set all points as internal - self.boundary_points_get() + self.get_boundary_points() self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially, all grid points are set to zero - self.potential_eps_get(gate_list+dielectric_list) + self.get_potential_eps(gate_list+dielectric_list) + def get_fixed_charge(self,x_range,y_range,z_range,molar_fraction,atom_gridpoint_index): + # set the fixed charge density + mask = ( + (float(x_range[0]) <= self.grid.grid_coord[:, 0]) & + (float(x_range[1]) >= self.grid.grid_coord[:, 0]) & + (float(y_range[0]) <= self.grid.grid_coord[:, 1]) & + (float(y_range[1]) >= self.grid.grid_coord[:, 1]) & + (float(z_range[0]) <= self.grid.grid_coord[:, 2]) & + (float(z_range[1]) >= self.grid.grid_coord[:, 2]) + ) + index = np.nonzero(mask)[0] + valid_indices = index[np.isin(index, atom_gridpoint_index)] + self.fixed_charge[valid_indices] = molar_fraction + + - def boundary_points_get(self): + def get_boundary_points(self): # set the boundary points xmin,xmax = np.min(self.grid.xall),np.max(self.grid.xall) ymin,ymax = np.min(self.grid.yall),np.max(self.grid.yall) @@ -166,7 +181,7 @@ def boundary_points_get(self): self.internal_NP = internal_NP - def potential_eps_get(self,region_list): + def get_potential_eps(self,region_list): # set the gate potential # ingore the lead potential temporarily gate_point = 0 diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index b0983dae..43846743 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -196,6 +196,7 @@ def __init__(self, self.free_charge = {} # net charge: hole - electron self.gate_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("gate")] self.dielectric_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("dielectric")] + self.doped_region = [self.poisson_options[i] for i in self.poisson_options if i.startswith("doped")] @@ -270,19 +271,27 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # create dielectric Dielectric_list = [] for dd in range(len(self.dielectric_region)): - dielectric_init = Dielectric(self.dielectric_region[dd].get("x_range",None).split(':'),\ - self.dielectric_region[dd].get("y_range",None).split(':'),\ - self.dielectric_region[dd].get("z_range",None).split(':')) + dielectric_init = Dielectric( self.dielectric_region[dd].get("x_range",None).split(':'),\ + self.dielectric_region[dd].get("y_range",None).split(':'),\ + self.dielectric_region[dd].get("z_range",None).split(':')) dielectric_init.eps = float(self.dielectric_region[dd].get("relative permittivity",None)) Dielectric_list.append(dielectric_init) # create interface interface_poisson = Interface3D(grid,Gate_list,Dielectric_list) + atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) # atomic site index in the grid + + for dp in range(len(self.doped_region)): + interface_poisson.get_fixed_charge( self.doped_region[dp].get("x_range",None).split(':'),\ + self.doped_region[dp].get("y_range",None).split(':'),\ + self.doped_region[dp].get("z_range",None).split(':'),\ + self.doped_region[dp].get("charge",None),\ + atom_gridpoint_index) #initial guess for electrostatic potential log.info(msg="-----Initial guess for electrostatic potential----") interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) - atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) + log.info(msg="-------------------------------------------\n") max_diff_phi = 1e30; max_diff_list = [] @@ -290,7 +299,6 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # Gummel type iteration while max_diff_phi > err: # update Hamiltonian by modifying onsite energy with potential - atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) self.potential_at_atom = interface_poisson.phi[atom_gridpoint_index] self.potential_at_orb = torch.cat([torch.full((norb,), p) for p, norb\ in zip(self.potential_at_atom, self.device_atom_norbs)]) From 0766934cb8e15050b29a501a8d57d2e83ee99a9e Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Thu, 14 Nov 2024 15:14:35 +0800 Subject: [PATCH 174/209] add dope region and I-V curve calculation --- dptb/negf/density.py | 1 + dptb/negf/negf_hamiltonian_init.py | 2 +- dptb/negf/poisson_init.py | 2 +- dptb/postprocess/NEGF.py | 92 ++++++++++++++---------------- dptb/utils/argcheck.py | 27 +++++++-- 5 files changed, 67 insertions(+), 57 deletions(-) diff --git a/dptb/negf/density.py b/dptb/negf/density.py index 49a4e0ce..653389b9 100644 --- a/dptb/negf/density.py +++ b/dptb/negf/density.py @@ -286,6 +286,7 @@ def density_integrate_Fiori(self,e_grid,kpoint,Vbias,block_tridiagonal,subblocks A_Rd = [torch.mm(torch.mm(deviceprop.gr_lc[i],gammaR[-x1:, -x1:]),deviceprop.gr_lc[i].conj().T) for i in range(len(deviceprop.gr_lc))] A_Ld = [1j*(deviceprop.grd[i]-deviceprop.grd[i].conj().T)-A_Rd[i] for i in range(len(A_Rd))] + # the chemical potential in fermi_dirac is always set as lead_L.mu, representing the source and drain fermi level gnd = [A_Ld[i]*deviceprop.lead_L.fermi_dirac(e+deviceprop.lead_L.efermi) \ +A_Rd[i]*deviceprop.lead_R.fermi_dirac(e+deviceprop.lead_R.efermi) for i in range(len(A_Ld))] gpd = [A_Ld[i] + A_Rd[i] - gnd[i] for i in range(len(A_Ld))] diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index dd1cc00b..80d68947 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -73,7 +73,7 @@ def __init__(self, ) -> None: # TODO: add dtype and device setting to the model - torch.set_default_dtype(torch.float64) + # torch.set_default_dtype(torch.float64) if isinstance(torch_device, str): torch_device = torch.device(torch_device) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 4c6414f0..e3a766a0 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -145,7 +145,7 @@ def __init__(self,grid,gate_list,dielectric_list): self.get_boundary_points() self.lead_gate_potential = np.zeros(grid.Np) # no gate potential initially, all grid points are set to zero - self.get_potential_eps(gate_list+dielectric_list) + def get_fixed_charge(self,x_range,y_range,z_range,molar_fraction,atom_gridpoint_index): diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 43846743..fe7b0778 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -244,56 +244,54 @@ def generate_energy_grid(self): def compute(self): if self.scf: - # if not self.out_density: - # self.out_density = True - # raise UserWarning("SCF is required, but out_density is set to False. Automatically Setting out_density to True.") - self.poisson_negf_scf(err=self.poisson_options['err'],tolerance=self.poisson_options['tolerance'],\ - max_iter=self.poisson_options['max_iter'],mix_rate=self.poisson_options['mix_rate']) + + # create real-space grid + grid = self.get_grid(self.poisson_options["grid"],self.deviceprop.structure) + + # create gate + Gate_list = [] + for gg in range(len(self.gate_region)): + gate_init = Gate(self.gate_region[gg].get("x_range",None).split(':'),\ + self.gate_region[gg].get("y_range",None).split(':'),\ + self.gate_region[gg].get("z_range",None).split(':')) + gate_init.Ef = float(self.gate_region[gg].get("voltage",None)) # in unit of volt + Gate_list.append(gate_init) + + # create dielectric + Dielectric_list = [] + for dd in range(len(self.dielectric_region)): + dielectric_init = Dielectric( self.dielectric_region[dd].get("x_range",None).split(':'),\ + self.dielectric_region[dd].get("y_range",None).split(':'),\ + self.dielectric_region[dd].get("z_range",None).split(':')) + dielectric_init.eps = float(self.dielectric_region[dd].get("relative permittivity",None)) + Dielectric_list.append(dielectric_init) + + # create interface + interface_poisson = Interface3D(grid,Gate_list,Dielectric_list) + atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) # atomic site index in the grid + interface_poisson.get_potential_eps(Gate_list+Dielectric_list) + for dp in range(len(self.doped_region)): + interface_poisson.get_fixed_charge( self.doped_region[dp].get("x_range",None).split(':'),\ + self.doped_region[dp].get("y_range",None).split(':'),\ + self.doped_region[dp].get("z_range",None).split(':'),\ + self.doped_region[dp].get("charge",None),\ + atom_gridpoint_index) + + #initial guess for electrostatic potential + log.info(msg="-----Initial guess for electrostatic potential----") + interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=self.poisson_options['tolerance']) + log.info(msg="-------------------------------------------\n") + + self.poisson_negf_scf( interface_poisson=interface_poisson,atom_gridpoint_index=atom_gridpoint_index,\ + err=self.poisson_options['err'],max_iter=self.poisson_options['max_iter'],\ + mix_rate=self.poisson_options['mix_rate'],tolerance=self.poisson_options['tolerance']) else: self.negf_compute(scf_require=False,Vbias=None) - def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): + def poisson_negf_scf(self,interface_poisson,atom_gridpoint_index,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # profiler.start() - # create real-space grid - grid = self.get_grid(self.poisson_options["grid"],self.deviceprop.structure) - - # create gate - Gate_list = [] - for gg in range(len(self.gate_region)): - gate_init = Gate(self.gate_region[gg].get("x_range",None).split(':'),\ - self.gate_region[gg].get("y_range",None).split(':'),\ - self.gate_region[gg].get("z_range",None).split(':')) - gate_init.Ef = float(self.gate_region[gg].get("voltage",None)) # in unit of volt - Gate_list.append(gate_init) - - # create dielectric - Dielectric_list = [] - for dd in range(len(self.dielectric_region)): - dielectric_init = Dielectric( self.dielectric_region[dd].get("x_range",None).split(':'),\ - self.dielectric_region[dd].get("y_range",None).split(':'),\ - self.dielectric_region[dd].get("z_range",None).split(':')) - dielectric_init.eps = float(self.dielectric_region[dd].get("relative permittivity",None)) - Dielectric_list.append(dielectric_init) - - # create interface - interface_poisson = Interface3D(grid,Gate_list,Dielectric_list) - atom_gridpoint_index = list(interface_poisson.grid.atom_index_dict.values()) # atomic site index in the grid - - for dp in range(len(self.doped_region)): - interface_poisson.get_fixed_charge( self.doped_region[dp].get("x_range",None).split(':'),\ - self.doped_region[dp].get("y_range",None).split(':'),\ - self.doped_region[dp].get("z_range",None).split(':'),\ - self.doped_region[dp].get("charge",None),\ - atom_gridpoint_index) - - #initial guess for electrostatic potential - log.info(msg="-----Initial guess for electrostatic potential----") - interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) - - log.info(msg="-------------------------------------------\n") - max_diff_phi = 1e30; max_diff_list = [] iter_count=0 # Gummel type iteration @@ -313,12 +311,6 @@ def poisson_negf_scf(self,err=1e-6,max_iter=1000,mix_rate=0.3,tolerance=1e-7): # elec_density = torch.diag(DM_eq+DM_neq) - # elec_density_per_atom = [] - # pre_atom_orbs = 0 - # for i in range(len(device_atom_norbs)): - # elec_density_per_atom.append(torch.sum(elec_density[pre_atom_orbs : pre_atom_orbs+device_atom_norbs[i]]).numpy()) - # pre_atom_orbs += device_atom_norbs[i] - # TODO: check the sign of free_charge # TODO: check the spin degenracy # TODO: add k summation operation diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 074abffc..8430fee4 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -1092,6 +1092,7 @@ def pyamg(): doc_grid="" doc_gate="" doc_dielectric="" + doc_doped="" doc_max_iter="" doc_mix_rate="" return [ @@ -1102,7 +1103,8 @@ def pyamg(): Argument("grid", dict, optional=False, sub_fields=grid(), doc=doc_grid), Argument("gate_top", dict, optional=False, sub_fields=gate(), doc=doc_gate), Argument("gate_bottom", dict, optional=False, sub_fields=gate(), doc=doc_gate), - Argument("dielectric_region", dict, optional=False, sub_fields=dielectric(), doc=doc_dielectric) + Argument("dielectric_region", dict, optional=False, sub_fields=dielectric(), doc=doc_dielectric), + Argument("doped_region", dict, optional=False, sub_fields=doped(), doc=doc_doped) ] def scipy(): @@ -1111,6 +1113,7 @@ def scipy(): doc_grid="" doc_gate="" doc_dielectric="" + doc_doped="" doc_max_iter="" doc_mix_rate="" return [ @@ -1118,10 +1121,12 @@ def scipy(): Argument("tolerance", [int, float], optional=True, default=1e-7, doc=doc_tolerance), Argument("max_iter", int, optional=True, default=100, doc=doc_max_iter), Argument("mix_rate", int, optional=True, default=0.25, doc=doc_mix_rate), - Argument("grid", dict, optional=False, sub_fields=grid(), doc=doc_grid), - Argument("gate_top", dict, optional=False, sub_fields=gate(), doc=doc_gate), - Argument("gate_bottom", dict, optional=False, sub_fields=gate(), doc=doc_gate), - Argument("dielectric_region", dict, optional=False, sub_fields=dielectric(), doc=doc_dielectric) + Argument("grid", dict, optional=True, sub_fields=grid(), doc=doc_grid), + Argument("gate_top", dict, optional=True, sub_fields=gate(), doc=doc_gate), + Argument("gate_bottom", dict, optional=True, sub_fields=gate(), doc=doc_gate), + Argument("dielectric_region", dict, optional=True, sub_fields=dielectric(), doc=doc_dielectric), + Argument("doped_region1", dict, optional=True, sub_fields=doped(), doc=doc_doped), + Argument("doped_region2", dict, optional=True, sub_fields=doped(), doc=doc_doped) ] def grid(): @@ -1158,6 +1163,18 @@ def dielectric(): Argument("relative permittivity", [int, float], optional=False, doc=doc_permittivity) ] +def doped(): + doc_xrange="" + doc_yrange="" + doc_zrange="" + doc_charge="" + return [ + Argument("x_range", str, optional=False, doc=doc_xrange), + Argument("y_range", str, optional=False, doc=doc_yrange), + Argument("z_range", str, optional=False, doc=doc_zrange), + Argument("charge", [int, float], optional=False, doc=doc_charge) + ] + def run_options(): doc_task = "the task to run, includes: band, dos, pdos, FS2D, FS3D, ifermi" doc_structure = "the structure to run the task" From 305baabdd56bbc0dcfc7f6f3b1cfabd69fe1f596 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 20 Nov 2024 15:40:34 +0800 Subject: [PATCH 175/209] set r_max oer_max and er_max default value as float type --- dptb/utils/argcheck.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 8430fee4..ddd080b3 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -345,9 +345,9 @@ def test_data_sub(): def data_options(): args = [ - Argument("r_max", [float,int], optional=True, default="5.0", doc="r_max"), - Argument("oer_max", [float,int], optional=True, default="5.0", doc="oer_max"), - Argument("er_max", [float,int], optional=True, default="5.0", doc="er_max"), + Argument("r_max", [float,int], optional=True, default=5.0, doc="r_max"), + Argument("oer_max", [float,int], optional=True, default=5.0, doc="oer_max"), + Argument("er_max", [float,int], optional=True, default=5.0, doc="er_max"), train_data_sub(), validation_data_sub(), reference_data_sub() From 88423507e4a52c4e486bb4ff217939add55304d5 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 26 Nov 2024 19:47:12 +0800 Subject: [PATCH 176/209] change remove_bonds_nonpbc as staticmethod and add h_lead_threshold to localize the lead hamiltonian --- dptb/negf/negf_hamiltonian_init.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 80d68947..d1a239b6 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -73,7 +73,7 @@ def __init__(self, ) -> None: # TODO: add dtype and device setting to the model - # torch.set_default_dtype(torch.float64) + torch.set_default_dtype(torch.float64) if isinstance(torch_device, str): torch_device = torch.device(torch_device) @@ -201,7 +201,7 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor - self.remove_bonds_nonpbc(self.alldata,self.pbc_negf) + self.remove_bonds_nonpbc(data=self.alldata,pbc=self.pbc_negf,overlap=self.overlap) self.alldata = self.h2k(self.alldata) HK = self.alldata[AtomicDataDict.HAMILTONIAN_KEY] @@ -276,7 +276,7 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor torch.nested.as_nested_tensor([torch.as_tensor(HS_leads["kpoints"], dtype=self.model.dtype, device=self.torch_device)]) lead_data = self.model(lead_data) - self.remove_bonds_nonpbc(lead_data,self.pbc_negf) + self.remove_bonds_nonpbc(data=lead_data,pbc=self.pbc_negf,overlap=self.overlap) lead_data = self.h2k(lead_data) HK_lead = lead_data[AtomicDataDict.HAMILTONIAN_KEY] if self.overlap: @@ -312,6 +312,12 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor elif 1e-7 <= max(rmse_l_HK,rmse_l_SK) <= 1e-4: log.warning(msg="WARNING, the lead's hamiltonian attained from diffferent methods have slight differences RMSE = {:.7f}.".format(max(rmse_l_HK,rmse_l_SK))) + # ensure the locality of the lead's Hamiltonian to stablize the self energy algorithm + h_lead_threshold = 1e-6 + for ik in range(HK.shape[0]): + hL[ik][torch.abs(hL[ik]) Date: Tue, 26 Nov 2024 19:48:03 +0800 Subject: [PATCH 177/209] enlarge myConvTest in surface_green to ensure the stablization of self energy algorithm --- dptb/negf/surface_green.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/negf/surface_green.py b/dptb/negf/surface_green.py index 4530e236..60f58437 100644 --- a/dptb/negf/surface_green.py +++ b/dptb/negf/surface_green.py @@ -60,7 +60,7 @@ def forward(ctx, H, h01, S, s01, ee, method='Lopez-Sancho'): test = ee * S - H - torch.mm(ee * s01 - h01, gs.mm(ee * s10 - h10)) myConvTest = torch.max((test.mm(gs) - torch.eye(H.shape[0], dtype=h01.dtype)).abs()) - if myConvTest < 1.0e-6: + if myConvTest < 3.0e-5: converged = True if myConvTest > 1.0e-8: log.warning("Lopez-scheme not-so-well converged at E = %.4f eV:" % ee.real.item() + str(myConvTest.item())) From 6599f8b2dc797bd7e7ef47da7f37a84af5043729 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 18 Dec 2024 13:49:30 +0800 Subject: [PATCH 178/209] make correction for electro-boundary condition at Gate --- dptb/negf/poisson_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index e3a766a0..db39b478 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -383,7 +383,7 @@ def NR_construct_Jac_B(self,J,B): J[gp_index,gp_index-Nx*Ny] = -1.0 B[gp_index] = (self.phi[gp_index]-self.phi[gp_index-Nx*Ny]) elif self.boudnary_points[gp_index] == "Gate": - B[gp_index] = (self.phi[gp_index]+self.lead_gate_potential[gp_index]) + B[gp_index] = (self.phi[gp_index]-self.lead_gate_potential[gp_index]) if B[gp_index]!=0: # for convenience change the sign of B in later NR iteration B[gp_index] = -B[gp_index] From 98fc782a65ba20ad5544065ce6ea388473bbc796 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 18 Dec 2024 13:51:14 +0800 Subject: [PATCH 179/209] output free charge with Fiori method and seperate negf_compute from poisson_scf --- dptb/postprocess/NEGF.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index fe7b0778..be363e89 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -285,6 +285,9 @@ def compute(self): self.poisson_negf_scf( interface_poisson=interface_poisson,atom_gridpoint_index=atom_gridpoint_index,\ err=self.poisson_options['err'],max_iter=self.poisson_options['max_iter'],\ mix_rate=self.poisson_options['mix_rate'],tolerance=self.poisson_options['tolerance']) + # calculate transport properties with converged potential + self.negf_compute(scf_require=False,Vbias=self.potential_at_orb) + else: self.negf_compute(scf_require=False,Vbias=None) @@ -350,8 +353,7 @@ def poisson_negf_scf(self,interface_poisson,atom_gridpoint_index,err=1e-6,max_it self.poisson_out['max_diff_list'] = torch.tensor(max_diff_list) torch.save(self.poisson_out, self.results_path+"/poisson.out.pth") - # calculate transport properties with converged potential - self.negf_compute(scf_require=False,Vbias=self.potential_at_orb) + # output the profile report in html format # if iter_count <= max_iter: @@ -475,7 +477,26 @@ def negf_compute(self,scf_require=False,Vbias=None): prop_DM_neq = self.out.setdefault('DM_neq', {}) prop_DM_eq[str(k)], prop_DM_neq[str(k)] = self.compute_density_Ozaki(k,Vbias) elif self.density_options["method"] == "Fiori": - log.warning("Fiori method does not support output density in this version.") + log.warning("Fiori method is under test in this version.") + try: + self.density.density_integrate_Fiori( + e_grid = self.uni_grid, + kpoint=k, + Vbias=Vbias, + block_tridiagonal=self.block_tridiagonal, + subblocks=self.subblocks, + integrate_way = self.density_options["integrate_way"], + deviceprop=self.deviceprop, + device_atom_norbs=self.device_atom_norbs, + potential_at_atom = self.potential_at_atom, + free_charge = self.free_charge, + eta_lead = self.eta_lead, + eta_device = self.eta_device + ) + prop_freecharge = self.out.setdefault('FREE_CHARGE', {}) + prop_freecharge[str(k)] = self.free_charge[str(k)] + except: + log.warning("Free charge output has some problems.") else: raise ValueError("Unknown method for density calculation.") if self.out_potential: From 43d7d0bc61b77ed86a37076116a63c9914350af6 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 5 Feb 2025 12:45:04 +0800 Subject: [PATCH 180/209] add doc to bloch --- dptb/negf/bloch.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/dptb/negf/bloch.py b/dptb/negf/bloch.py index 541a3abb..7513fcd2 100644 --- a/dptb/negf/bloch.py +++ b/dptb/negf/bloch.py @@ -2,7 +2,9 @@ import numpy as np +# The Bloch class in Python defines a method to unfold k points based on a given Bloch factor. class Bloch(object): + def __init__(self,bloch_factor) -> None: if isinstance(bloch_factor,list): @@ -12,9 +14,31 @@ def __init__(self,bloch_factor) -> None: self.bloch_factor = bloch_factor - def unfold_points(self,k): + def unfold_points(self,k:list) -> np.ndarray: + '''The `unfold_points` function generates expansion k points based on Bloch theorem and reshapes the + output into a specific format. + + Parameters + ---------- + k + The `k` parameter in the `unfold_points` method represents the original k-point in the Brillouin zone. + + Returns + ------- + The `unfold_points` method returns a reshaped array of expansion points calculated based on the + input parameter `k` and the bloch factor `B`. The expansion points are created using B-casting rules + and then reshaped into a 2D array with 3 columns. + + ''' - + # check k is a 3D vector + if isinstance(k,list): + assert len(k) == 3, "kpoint should be a 3D vector" + elif isinstance(k,np.ndarray): + assert k.shape[0] == 3, "kpoint should be a 3D vector" + else: + raise ValueError("k should be a list or numpy array") + # Create expansion points B = self.bloch_factor unfold = np.empty([B[2], B[1], B[0], 3]) From 4d10aa69738ba49cf5fc39b3af86ac685d2c6133 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 5 Feb 2025 15:32:17 +0800 Subject: [PATCH 181/209] update docstring in bloch --- dptb/negf/bloch.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/dptb/negf/bloch.py b/dptb/negf/bloch.py index 7513fcd2..01febf85 100644 --- a/dptb/negf/bloch.py +++ b/dptb/negf/bloch.py @@ -1,4 +1,3 @@ - import numpy as np @@ -6,6 +5,15 @@ class Bloch(object): def __init__(self,bloch_factor) -> None: + '''This Python function initializes an object with a Bloch factor represented as a 3D vector. + + Parameters + ---------- + bloch_factor + It is expected to be a list or numpy array representing a 3D vector to expand the provided + k points. + + ''' if isinstance(bloch_factor,list): bloch_factor = np.array(bloch_factor) @@ -26,8 +34,7 @@ def unfold_points(self,k:list) -> np.ndarray: Returns ------- The `unfold_points` method returns a reshaped array of expansion points calculated based on the - input parameter `k` and the bloch factor `B`. The expansion points are created using B-casting rules - and then reshaped into a 2D array with 3 columns. + input parameter `k` and the bloch factor `B`. ''' From d89d5c391ab814b68126b6ee2f6c72566870ea34 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Wed, 5 Feb 2025 16:22:36 +0800 Subject: [PATCH 182/209] add correct sign to Gate.Ef in unit of eV --- dptb/negf/poisson_init.py | 2 +- dptb/postprocess/NEGF.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index db39b478..6f816b35 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -196,7 +196,7 @@ def get_potential_eps(self,region_list): if region_list[i].__class__.__name__ == 'Gate': #attribute gate potential to the corresponding grid points self.boudnary_points.update({index[i]: "Gate" for i in range(len(index))}) - self.lead_gate_potential[index] = region_list[i].Ef + self.lead_gate_potential[index] = -1*region_list[i].Ef gate_point += len(index) elif region_list[i].__class__.__name__ == 'Dielectric': # attribute dielectric permittivity to the corresponding grid points diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index be363e89..98863738 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -254,7 +254,7 @@ def compute(self): gate_init = Gate(self.gate_region[gg].get("x_range",None).split(':'),\ self.gate_region[gg].get("y_range",None).split(':'),\ self.gate_region[gg].get("z_range",None).split(':')) - gate_init.Ef = float(self.gate_region[gg].get("voltage",None)) # in unit of volt + gate_init.Ef = -1*float(self.gate_region[gg].get("voltage",None)) # in unit of eV Gate_list.append(gate_init) # create dielectric From f449cfff0bd8a1b6a6f8aaa3644faf4bc5cac95b Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sun, 23 Feb 2025 11:24:35 +0800 Subject: [PATCH 183/209] add poisson_dtype to assign data type when solving Poisson --- dptb/negf/poisson_init.py | 14 +++++++++++--- dptb/postprocess/NEGF.py | 8 ++++++-- dptb/utils/argcheck.py | 4 ++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 6f816b35..385aff73 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -222,7 +222,8 @@ def to_pyamg_Jac_B(self,dtype=np.float64): def to_scipy_Jac_B(self,dtype=np.float64): # create the Jacobian and B for the Poisson equation in scipy sparse format - Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) + # Jacobian = csr_matrix(np.zeros((self.grid.Np,self.grid.Np),dtype=dtype)) + Jacobian = csr_matrix((self.grid.Np,self.grid.Np),dtype=dtype) B = np.zeros(Jacobian.shape[0],dtype=Jacobian.dtype) Jacobian_lil = Jacobian.tolil() @@ -233,7 +234,7 @@ def to_scipy_Jac_B(self,dtype=np.float64): - def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7): + def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7,dtype:str='float64'): # solve the Poisson equation with Newton-Raphson method # delta_phi: the correction on the potential @@ -241,9 +242,16 @@ def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7): norm_delta_phi = 1.0 # Euclidean norm of delta_phi in each step NR_cycle_step = 0 + if dtype == 'float64': + dtype = np.float64 + elif dtype == 'float32': + dtype = np.float32 + else: + raise ValueError('Unknown data type: ',dtype) + while norm_delta_phi > 1e-3 and NR_cycle_step < 100: # obtain the Jacobian and B for the Poisson equation - Jacobian,B = self.to_scipy_Jac_B() + Jacobian,B = self.to_scipy_Jac_B(dtype=dtype) norm_B = np.linalg.norm(B) if method == 'scipy': diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 98863738..d17930b7 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -279,7 +279,9 @@ def compute(self): #initial guess for electrostatic potential log.info(msg="-----Initial guess for electrostatic potential----") - interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=self.poisson_options['tolerance']) + interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],\ + tolerance=self.poisson_options['tolerance'],\ + dtype=self.poisson_options['poisson_dtype']) log.info(msg="-------------------------------------------\n") self.poisson_negf_scf( interface_poisson=interface_poisson,atom_gridpoint_index=atom_gridpoint_index,\ @@ -324,7 +326,9 @@ def poisson_negf_scf(self,interface_poisson,atom_gridpoint_index,err=1e-6,max_it interface_poisson.phi_old = interface_poisson.phi.copy() - max_diff_phi = interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],tolerance=tolerance) + max_diff_phi = interface_poisson.solve_poisson_NRcycle(method=self.poisson_options['solver'],\ + tolerance=tolerance,\ + dtype=self.poisson_options['poisson_dtype']) interface_poisson.phi = interface_poisson.phi + mix_rate*(interface_poisson.phi_old-interface_poisson.phi) diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 4204a53a..e550f795 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -1180,11 +1180,13 @@ def pyamg(): doc_doped="" doc_max_iter="" doc_mix_rate="" + doc_poisson_dtype="The dtype of the poisson solver" return [ Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err), Argument("tolerance", [int, float], optional=True, default=1e-7, doc=doc_tolerance), Argument("max_iter", int, optional=True, default=100, doc=doc_max_iter), Argument("mix_rate", int, optional=True, default=0.25, doc=doc_mix_rate), + Argument("poisson_dtype", str, optional=True, default='float64', doc=doc_poisson_dtype), Argument("grid", dict, optional=False, sub_fields=grid(), doc=doc_grid), Argument("gate_top", dict, optional=False, sub_fields=gate(), doc=doc_gate), Argument("gate_bottom", dict, optional=False, sub_fields=gate(), doc=doc_gate), @@ -1201,11 +1203,13 @@ def scipy(): doc_doped="" doc_max_iter="" doc_mix_rate="" + doc_poisson_dtype="The dtype of the poisson solver" return [ Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err), Argument("tolerance", [int, float], optional=True, default=1e-7, doc=doc_tolerance), Argument("max_iter", int, optional=True, default=100, doc=doc_max_iter), Argument("mix_rate", int, optional=True, default=0.25, doc=doc_mix_rate), + Argument("poisson_dtype", str, optional=True, default='float64', doc=doc_poisson_dtype), Argument("grid", dict, optional=True, sub_fields=grid(), doc=doc_grid), Argument("gate_top", dict, optional=True, sub_fields=gate(), doc=doc_gate), Argument("gate_bottom", dict, optional=True, sub_fields=gate(), doc=doc_gate), From 22b477cbcb32a0c20006c20b1626ae21bf358fb9 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 25 Feb 2025 22:49:55 +0800 Subject: [PATCH 184/209] add self energy saver --- dptb/negf/lead_property.py | 28 ++++++++++++++++++++++++++-- dptb/negf/poisson_init.py | 1 + dptb/postprocess/NEGF.py | 9 +++++++-- dptb/utils/argcheck.py | 2 ++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index b2c9d534..e7213cfe 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -95,7 +95,7 @@ def __init__(self, tab, hamiltonian, structure, results_path, voltage,\ assert self.structure_leads_fold is not None def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-Sancho", \ - ): + save: bool=False, save_path: str=None): '''calculate and loads the self energy and surface green function at the given kpoint and energy. Parameters @@ -108,7 +108,10 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S the broadening parameter for calculating lead surface green function. method : specify the method for calculating the self energy. At this stage it only supports "Lopez-Sancho". - + save : + whether to save the self energy. + save_path : + the path to save the self energy. If not specified, the self energy will be saved in the results_path. ''' assert len(np.array(kpoint).reshape(-1)) == 3 # according to given kpoint and e_mesh, calculating or loading the self energy and surface green function to self. @@ -118,6 +121,20 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S # if not hasattr(self, "HL"): #TODO: check here whether it is necessary to calculate the self energy every time + # If the file in save_path exists, then directly load the self energy from the file + if save_path is None: + save_path = os.path.join(self.results_path, \ + "self_energy",\ + f"se_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_E{energy}.pth") + parent_dir = os.path.dirname(save_path) + if not os.path.exists(parent_dir): os.makedirs(parent_dir) + + if os.path.exists(save_path): + log.info(f" Loading self energy from {save_path}") + self.se = torch.load(save_path) + return + else: + log.warning(f" Not find the stored self energy file. Calculating it at kpoint {kpoint} and energy {energy}.") if not self.useBloch: if not hasattr(self, "HL") or abs(self.voltage_old-self.voltage)>1e-6 or max(abs(self.kpoint-torch.tensor(kpoint)))>1e-6: @@ -187,6 +204,13 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S self.se = (eeshifted*self.SDL-self.HDL) @ sgf_k[:b,:b] @ (eeshifted*self.SDL.conj().T-self.HDL.conj().T) + if save: + if save_path is None: + save_path = os.path.join(self.results_path, \ + "self_energy",\ + f"se_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_E{energy}.pth") + log.info(f"Saving self energy to {save_path}") + torch.save(self.se, save_path) # if self.useBloch: # torch.save(self.se, os.path.join(self.results_path, f"se_bloch_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_{energy}.pth")) # else: diff --git a/dptb/negf/poisson_init.py b/dptb/negf/poisson_init.py index 385aff73..d6926b91 100644 --- a/dptb/negf/poisson_init.py +++ b/dptb/negf/poisson_init.py @@ -237,6 +237,7 @@ def to_scipy_Jac_B(self,dtype=np.float64): def solve_poisson_NRcycle(self,method='pyamg',tolerance=1e-7,dtype:str='float64'): # solve the Poisson equation with Newton-Raphson method # delta_phi: the correction on the potential + # It has been tested that dtype='float64' is a more stable SCF choice. norm_delta_phi = 1.0 # Euclidean norm of delta_phi in each step diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index d17930b7..393ab1e5 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -39,7 +39,8 @@ def __init__(self, unit: str, scf: bool, poisson_options: dict, stru_options: dict,eta_lead: float,eta_device: float, - block_tridiagonal: bool,sgf_solver: str, + block_tridiagonal: bool, + sgf_solver: str,self_energy_save: bool,self_energy_save_path: str, out_tc: bool=False,out_dos: bool=False,out_density: bool=False,out_potential: bool=False, out_current: bool=False,out_current_nscf: bool=False,out_ldos: bool=False,out_lcurrent: bool=False, results_path: Optional[str]=None, @@ -63,6 +64,8 @@ def __init__(self, self.emin = emin; self.emax = emax; self.espacing = espacing self.stru_options = stru_options self.sgf_solver = sgf_solver + self.self_energy_save = self_energy_save + self.self_energy_save_path = self_energy_save_path self.pbc = self.stru_options["pbc"] if self.stru_options["lead_L"]["useBloch"] or self.stru_options["lead_R"]["useBloch"]: @@ -438,7 +441,9 @@ def negf_compute(self,scf_require=False,Vbias=None): energy=e, kpoint=k, eta_lead=self.eta_lead, - method=self.sgf_solver + method=self.sgf_solver, + save=self.self_energy_save, + save_path=self.self_energy_save_path ) # self.out[str(ll)+"_se"][str(e.numpy())] = getattr(self.deviceprop, ll).se diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index e550f795..4e730977 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -1045,6 +1045,8 @@ def negf(): Argument("stru_options", dict, optional=False, sub_fields=stru_options(), doc=doc_stru_options), Argument("poisson_options", dict, optional=True, default={}, sub_fields=[], sub_variants=[poisson_options()], doc=doc_poisson_options), Argument("sgf_solver", str, optional=True, default="Sancho-Rubio", doc=doc_sgf_solver), + Argument("self_energy_save", bool, optional=True, default=False, doc="whether to save the self energy"), + Argument("self_energy_save_path", str, optional=True, default=None, doc="the path to save the self energy"), Argument("espacing", [int, float], optional=False, doc=doc_espacing), Argument("emin", [int, float], optional=False, doc=doc_emin), Argument("emax", [int, float], optional=False, doc=doc_emax), From 4c7d70fb5cf61eeeeafeff45834e2304a546dbe4 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sun, 2 Mar 2025 15:43:07 +0800 Subject: [PATCH 185/209] add correction to self energy saver --- dptb/negf/lead_property.py | 21 +++++++++++++-------- dptb/postprocess/NEGF.py | 1 - 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index e7213cfe..53e78b30 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -125,16 +125,24 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S if save_path is None: save_path = os.path.join(self.results_path, \ "self_energy",\ - f"se_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_E{energy}.pth") + f"se_{self.tab}_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_E{energy}.pth") parent_dir = os.path.dirname(save_path) - if not os.path.exists(parent_dir): os.makedirs(parent_dir) + if not os.path.exists(parent_dir): + os.makedirs(parent_dir) if os.path.exists(save_path): - log.info(f" Loading self energy from {save_path}") + log.info(f"Loading self energy from {save_path}") + if not save_path.endswith(".pth"): + # if the save_path is a directory, then the self energy file is stored in the directory + save_path = os.path.join(save_path, \ + f"se_{self.tab}_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_E{energy}.pth") + assert os.path.exists(save_path), f"Cannot find the self energy file {save_path}" self.se = torch.load(save_path) return else: - log.warning(f" Not find the stored self energy file. Calculating it at kpoint {kpoint} and energy {energy}.") + log.info("-"*50) + log.info(f"Not find stored {self.tab} self energy. Calculating it at kpoint {kpoint} and energy {energy}.") + log.info("-"*50) if not self.useBloch: if not hasattr(self, "HL") or abs(self.voltage_old-self.voltage)>1e-6 or max(abs(self.kpoint-torch.tensor(kpoint)))>1e-6: @@ -205,10 +213,7 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S if save: - if save_path is None: - save_path = os.path.join(self.results_path, \ - "self_energy",\ - f"se_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_E{energy}.pth") + assert save_path is not None, "Please specify the path to save the self energy." log.info(f"Saving self energy to {save_path}") torch.save(self.se, save_path) # if self.useBloch: diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 393ab1e5..99f66e09 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -178,7 +178,6 @@ def __init__(self, self.right_connected_orb_mask = torch.tensor( [p for p, norb in zip(right_connected_atom_mask, self.device_atom_norbs) \ for _ in range(norb)],dtype=torch.bool) - # np.save(self.results_path+"/device_atom_norbs.npy",self.device_atom_norbs) # geting the output settings self.out_tc = out_tc From 6339b1a7f213aa4f65b1a9dd5f71a522e422807d Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sun, 2 Mar 2025 16:49:17 +0800 Subject: [PATCH 186/209] self.alldata to alldata for less memory use --- dptb/negf/negf_hamiltonian_init.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index d1a239b6..94857e32 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -180,12 +180,12 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor alldata = AtomicData.to_AtomicDataDict(alldata.to(self.torch_device)) - self.alldata = self.model.idp(alldata) - self.alldata[AtomicDataDict.KPOINT_KEY] = \ + alldata = self.model.idp(alldata) + alldata[AtomicDataDict.KPOINT_KEY] = \ torch.nested.as_nested_tensor([torch.as_tensor(HS_device["kpoints"], dtype=self.model.dtype, device=self.torch_device)]) - self.alldata = self.model(self.alldata) + alldata = self.model(alldata) - if self.alldata.get(AtomicDataDict.EDGE_OVERLAP_KEY,None) is not None: + if alldata.get(AtomicDataDict.EDGE_OVERLAP_KEY,None) is not None: self.overlap = True self.s2k = HR2HK( idp=self.model.idp, @@ -201,14 +201,14 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor - self.remove_bonds_nonpbc(data=self.alldata,pbc=self.pbc_negf,overlap=self.overlap) - self.alldata = self.h2k(self.alldata) - HK = self.alldata[AtomicDataDict.HAMILTONIAN_KEY] + self.remove_bonds_nonpbc(data=alldata,pbc=self.pbc_negf,overlap=self.overlap) + alldata = self.h2k(alldata) + HK = alldata[AtomicDataDict.HAMILTONIAN_KEY] if self.overlap: - self.alldata = self.s2k(self.alldata) - SK = self.alldata[AtomicDataDict.OVERLAP_KEY] + alldata = self.s2k(alldata) + SK = alldata[AtomicDataDict.OVERLAP_KEY] else: SK = torch.eye(HK.shape[1], dtype=self.model.dtype, device=self.torch_device).unsqueeze(0).repeat(HK.shape[0], 1, 1) From 83f57307fbd50b94df9b441062161ce476575576 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sun, 2 Mar 2025 16:50:34 +0800 Subject: [PATCH 187/209] add se_info_display and self energy saver to all non-SCF cases --- dptb/negf/lead_property.py | 21 +++++++++++++-------- dptb/postprocess/NEGF.py | 12 +++++++++--- dptb/utils/argcheck.py | 1 + 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 53e78b30..dc881b1f 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -95,7 +95,7 @@ def __init__(self, tab, hamiltonian, structure, results_path, voltage,\ assert self.structure_leads_fold is not None def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-Sancho", \ - save: bool=False, save_path: str=None): + save: bool=False, save_path: str=None, se_info_display: bool=False): '''calculate and loads the self energy and surface green function at the given kpoint and energy. Parameters @@ -112,6 +112,8 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S whether to save the self energy. save_path : the path to save the self energy. If not specified, the self energy will be saved in the results_path. + se_info_display : + whether to display the information of the self energy calculation. ''' assert len(np.array(kpoint).reshape(-1)) == 3 # according to given kpoint and e_mesh, calculating or loading the self energy and surface green function to self. @@ -121,7 +123,7 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S # if not hasattr(self, "HL"): #TODO: check here whether it is necessary to calculate the self energy every time - # If the file in save_path exists, then directly load the self energy from the file + if save_path is None: save_path = os.path.join(self.results_path, \ "self_energy",\ @@ -129,9 +131,11 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S parent_dir = os.path.dirname(save_path) if not os.path.exists(parent_dir): os.makedirs(parent_dir) - + + # If the file in save_path exists, then directly load the self energy from the file if os.path.exists(save_path): - log.info(f"Loading self energy from {save_path}") + + if se_info_display: log.info(f"Loading self energy from {save_path}") if not save_path.endswith(".pth"): # if the save_path is a directory, then the self energy file is stored in the directory save_path = os.path.join(save_path, \ @@ -140,9 +144,10 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S self.se = torch.load(save_path) return else: - log.info("-"*50) - log.info(f"Not find stored {self.tab} self energy. Calculating it at kpoint {kpoint} and energy {energy}.") - log.info("-"*50) + if se_info_display: + log.info("-"*50) + log.info(f"Not find stored {self.tab} self energy. Calculating it at kpoint {kpoint} and energy {energy}.") + log.info("-"*50) if not self.useBloch: if not hasattr(self, "HL") or abs(self.voltage_old-self.voltage)>1e-6 or max(abs(self.kpoint-torch.tensor(kpoint)))>1e-6: @@ -214,7 +219,7 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S if save: assert save_path is not None, "Please specify the path to save the self energy." - log.info(f"Saving self energy to {save_path}") + if se_info_display: log.info(f"Saving self energy to {save_path}") torch.save(self.se, save_path) # if self.useBloch: # torch.save(self.se, os.path.join(self.results_path, f"se_bloch_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_{energy}.pth")) diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 99f66e09..15db641b 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -40,7 +40,8 @@ def __init__(self, scf: bool, poisson_options: dict, stru_options: dict,eta_lead: float,eta_device: float, block_tridiagonal: bool, - sgf_solver: str,self_energy_save: bool,self_energy_save_path: str, + sgf_solver: str, + self_energy_save: bool, self_energy_save_path: str, se_info_display: bool, out_tc: bool=False,out_dos: bool=False,out_density: bool=False,out_potential: bool=False, out_current: bool=False,out_current_nscf: bool=False,out_ldos: bool=False,out_lcurrent: bool=False, results_path: Optional[str]=None, @@ -66,6 +67,7 @@ def __init__(self, self.sgf_solver = sgf_solver self.self_energy_save = self_energy_save self.self_energy_save_path = self_energy_save_path + self.se_info_display = se_info_display self.pbc = self.stru_options["pbc"] if self.stru_options["lead_L"]["useBloch"] or self.stru_options["lead_R"]["useBloch"]: @@ -442,7 +444,8 @@ def negf_compute(self,scf_require=False,Vbias=None): eta_lead=self.eta_lead, method=self.sgf_solver, save=self.self_energy_save, - save_path=self.self_energy_save_path + save_path=self.self_energy_save_path, + se_info_display=self.se_info_display ) # self.out[str(ll)+"_se"][str(e.numpy())] = getattr(self.deviceprop, ll).se @@ -532,7 +535,10 @@ def negf_compute(self,scf_require=False,Vbias=None): energy=e, kpoint=k, eta_lead=self.eta_lead, - method=self.sgf_solver + method=self.sgf_solver, + save=self.self_energy_save, + save_path=self.self_energy_save_path, + se_info_display=self.se_info_display ) self.deviceprop.cal_green_function( diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index 4e730977..b5680c05 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -1047,6 +1047,7 @@ def negf(): Argument("sgf_solver", str, optional=True, default="Sancho-Rubio", doc=doc_sgf_solver), Argument("self_energy_save", bool, optional=True, default=False, doc="whether to save the self energy"), Argument("self_energy_save_path", str, optional=True, default=None, doc="the path to save the self energy"), + Argument("se_info_display", bool, optional=True, default=False, doc="whether to display the self energy information"), Argument("espacing", [int, float], optional=False, doc=doc_espacing), Argument("emin", [int, float], optional=False, doc=doc_emin), Argument("emax", [int, float], optional=False, doc=doc_emax), From 9b34d3720c33d4fa6c8b73e04501466745e58601 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sun, 2 Mar 2025 21:01:13 +0800 Subject: [PATCH 188/209] seperate initialize into structure and H parts --- dptb/negf/negf_hamiltonian_init.py | 67 +++++++++++++----------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 94857e32..30c41558 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -160,25 +160,31 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor RuntimeError: if the lead hamiltonian attained from device and lead calculation does not match. """ - assert len(np.array(kpoints).shape) == 2 - - HS_device = {} - HS_device["kpoints"] = kpoints - - # change parameters to match the structure projection - # n_proj_atom_pre = np.array([1]*len(self.structase))[:self.device_id[0]].sum() - # n_proj_atom_device = np.array([1]*len(self.structase))[self.device_id[0]:self.device_id[1]].sum() - # device_id = [0,0] - # device_id[0] = n_proj_atom_pre - # device_id[1] = n_proj_atom_pre + n_proj_atom_device - # self.device_id = device_id + self.structase.set_pbc(self.pbc_negf) self.structase.pbc[2] = True + structure_device = self.structase[self.device_id[0]:self.device_id[1]] + structure_device.pbc = self.pbc_negf + + lead_atom_range = {} + structure_leads = {};structure_leads_fold = {} + bloch_sorted_indices={};bloch_R_lists = {} + for kk in self.stru_options: + if kk.startswith("lead"): + n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]].sum() + n_proj_atom_lead = np.array([1]*len(self.structase))[self.lead_ids[kk][0]:self.lead_ids[kk][1]].sum() + lead_atom_range[kk] = [n_proj_atom_pre, n_proj_atom_pre + n_proj_atom_lead] + structure_leads[kk],structure_leads_fold[kk],\ + bloch_sorted_indices[kk],bloch_R_lists[kk] = self.get_lead_structure(kk,n_proj_atom_lead,\ + useBloch=useBloch,bloch_factor=bloch_factor) + + + HS_device = {} + assert len(np.array(kpoints).shape) == 2 + HS_device["kpoints"] = kpoints alldata = AtomicData.from_ase(self.structase, **self.AtomicData_options) alldata[AtomicDataDict.PBC_KEY][2] = True # force pbc in z-axis to get reasonable chemical environment in two ends - - alldata = AtomicData.to_AtomicDataDict(alldata.to(self.torch_device)) alldata = self.model.idp(alldata) alldata[AtomicDataDict.KPOINT_KEY] = \ @@ -199,13 +205,10 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor else: self.overlap = False - - self.remove_bonds_nonpbc(data=alldata,pbc=self.pbc_negf,overlap=self.overlap) alldata = self.h2k(alldata) HK = alldata[AtomicDataDict.HAMILTONIAN_KEY] - if self.overlap: alldata = self.s2k(alldata) SK = alldata[AtomicDataDict.OVERLAP_KEY] @@ -217,12 +220,8 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor d_end = int(np.sum(self.h2k.atom_norbs)-np.sum(self.h2k.atom_norbs[self.device_id[1]:])) HD, SD = HK[:,d_start:d_end, d_start:d_end], SK[:, d_start:d_end, d_start:d_end] Hall, Sall = HK, SK - - structure_device = self.structase[self.device_id[0]:self.device_id[1]] - structure_device.pbc = self.pbc_negf - - structure_leads = {};structure_leads_fold = {} - bloch_sorted_indices={};coupling_width = {};bloch_R_lists = {} + + coupling_width = {} for kk in self.stru_options: if kk.startswith("lead"): HS_leads = {} @@ -239,17 +238,9 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor HS_leads["kpoints"] = kpoints HS_leads["kpoints_bloch"] = None HS_leads["bloch_factor"] = None - - # update lead id - n_proj_atom_pre = np.array([1]*len(self.structase))[:self.lead_ids[kk][0]].sum() - n_proj_atom_lead = np.array([1]*len(self.structase))[self.lead_ids[kk][0]:self.lead_ids[kk][1]].sum() - lead_id = [0,0] - lead_id[0] = n_proj_atom_pre - lead_id[1] = n_proj_atom_pre + n_proj_atom_lead - - - l_start = int(np.sum(self.h2k.atom_norbs[:lead_id[0]])) - l_end = int(l_start + np.sum(self.h2k.atom_norbs[lead_id[0]:lead_id[1]]) / 2) + + l_start = int(np.sum(self.h2k.atom_norbs[:lead_atom_range[kk][0]])) + l_end = int(l_start + np.sum(self.h2k.atom_norbs[lead_atom_range[kk][0]:lead_atom_range[kk][1]]) / 2) # lead hamiltonian in the first principal layer(the layer close to the device) HL, SL = HK[:,l_start:l_end, l_start:l_end], SK[:, l_start:l_end, l_start:l_end] # device and lead's hopping @@ -259,8 +250,6 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor torch.max(nonzero_indice[:,2]).item()-torch.min(nonzero_indice[:,2]).item() +1) log.info(msg="The coupling width of {} is {}.".format(kk,coupling_width[kk])) - structure_leads[kk],structure_leads_fold[kk],bloch_sorted_indices[kk],bloch_R_lists[kk] = self.get_lead_structure(kk,n_proj_atom_lead,\ - useBloch=useBloch,bloch_factor=bloch_factor,lead_id=lead_id) # get lead_data if useBloch: lead_data = AtomicData.from_ase(structure_leads_fold[kk], **self.AtomicData_options) @@ -347,7 +336,8 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) torch.set_default_dtype(torch.float32) - return structure_device, structure_leads, structure_leads_fold, bloch_sorted_indices, bloch_R_lists,subblocks + return structure_device, structure_leads, structure_leads_fold, \ + bloch_sorted_indices, bloch_R_lists,subblocks @staticmethod def remove_bonds_nonpbc(data,pbc,overlap): @@ -361,7 +351,7 @@ def remove_bonds_nonpbc(data,pbc,overlap): if overlap: data[AtomicDataDict.EDGE_OVERLAP_KEY] = data[AtomicDataDict.EDGE_OVERLAP_KEY][mask] - def get_lead_structure(self,kk,natom,useBloch=False,bloch_factor=None,lead_id=None): + def get_lead_structure(self,kk,natom,useBloch=False,bloch_factor=None): stru_lead = self.structase[self.lead_ids[kk][0]:self.lead_ids[kk][1]] cell = np.array(stru_lead.cell)[:2] @@ -381,7 +371,6 @@ def get_lead_structure(self,kk,natom,useBloch=False,bloch_factor=None,lead_id=No write(os.path.join(self.results_path, "stru_leadall_"+kk+".xyz"),stru_lead,format='extxyz') if useBloch: - assert lead_id is not None assert bloch_factor is not None bloch_reduce_cell = np.array([ [cell[0][0]/bloch_factor[0], 0, 0], From 3c641b7d64183330c799348f6c056bf0a8b8063f Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sun, 2 Mar 2025 21:21:37 +0800 Subject: [PATCH 189/209] add function Hamiltonian_initialized --- dptb/negf/negf_hamiltonian_init.py | 100 ++++++++++++++++++++++------- 1 file changed, 77 insertions(+), 23 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 30c41558..ba4a2b3c 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -141,27 +141,33 @@ def __init__(self, raise ValueError def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor=None): - """initializes the device and lead Hamiltonians + '''This function initializes the structure and Hamiltonian for a system with optional block tridiagonal + and Bloch factor parameters. - construct device and lead Hamiltonians and return the structures respectively.The lead Hamiltonian - is k-resolved due to the transverse k point sampling. - - Args: - kpoints: k-points in the Brillouin zone with three coordinates (kx, ky, kz) - block_tridiagnal: A boolean parameter that determines whether to block-tridiagonalize the - device Hamiltonian or not. - useBloch: A boolean parameter that determines whether to unfold the lead Hamiltonian with Bloch theorem or not. - bloch_factor: A list of three integers that determines the Bloch factor for unfolding the lead Hamiltonian. - - Returns: - structure_device and structure_leads corresponding to the structure of device and leads. - - Raises: - RuntimeError: if the lead hamiltonian attained from device and lead calculation does not match. - - """ - - + Parameters + ---------- + kpoints + Kpoints in the Brillouin zone + block_tridiagnal, optional + a boolean flag that determines whether the Hamiltonian matrix should be stored in a block-tridiagonal form. + useBloch, optional + a boolean flag that determines whether Bloch boundary conditions should be used in the lead self energy calculations. + bloch_factor + a list of integers that determines the Bloch factor for the lead self energy calculations. + + Returns + ------- + The `initialize` method returns the following variables in this order: + - `structure_device` + - `structure_leads` + - `structure_leads_fold` + - `bloch_sorted_indices` + - `bloch_R_lists` + - `subblocks` + + ''' + + # structure initialization self.structase.set_pbc(self.pbc_negf) self.structase.pbc[2] = True structure_device = self.structase[self.device_id[0]:self.device_id[1]] @@ -179,7 +185,55 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor bloch_sorted_indices[kk],bloch_R_lists[kk] = self.get_lead_structure(kk,n_proj_atom_lead,\ useBloch=useBloch,bloch_factor=bloch_factor) + # Hamiltonian initialization + subblocks = self.Hamiltonian_initialized(kpoints,useBloch,bloch_factor,block_tridiagnal,\ + lead_atom_range,structure_leads,structure_leads_fold) + + torch.set_default_dtype(torch.float32) + return structure_device, structure_leads, structure_leads_fold, \ + bloch_sorted_indices, bloch_R_lists,subblocks + + def Hamiltonian_initialized(self,kpoints:List[List[float]],useBloch:bool,bloch_factor:List[int],\ + block_tridiagnal:bool,lead_atom_range:dict,structure_leads:Atoms,structure_leads_fold:Atoms): + '''This function initializes the Hamiltonian for a device with leads, handling various calculations + and checks along the way. + + Parameters + ---------- + kpoints : List[List[float]] + the k-points in Brillouin zone + useBloch : bool + The `useBloch` parameter is a boolean flag that indicates whether to use Bloch boundary conditions + for the lead self energy calculations. If `useBloch` is set to `True`, the Bloch boundary conditions + will be used. Otherwise, the calculations will be performed without Bloch boundary conditions. + bloch_factor : List[int] + The `bloch_factor` parameter is a list of integers that determines the Bloch factor for the lead + self energy calculations. The Bloch factor is used to fold the lead structures in the context of the + larger device structure. The Bloch factor specifies the number of times the lead structure is + replicated along each direction to create a periodic structure. + block_tridiagnal : bool + The `block_tridiagnal` parameter is a boolean flag that determines whether the Hamiltonian matrix + should be stored in a block-tridiagonal form. If `block_tridiagnal` is set to `True`, the Hamiltonian + matrix will be stored in a block-tridiagonal form. Otherwise, the Hamiltonian matrix will be stored in + a full matrix format. + lead_atom_range : dict + The `lead_atom_range` parameter indicates the range of leads. The key of the dictionary is the + lead name, and the value is a list containing the start and end indices of the lead atoms. + structure_leads : Atoms + The `structure_leads` parameter is an Atoms object containing the structures of the leads. + structure_leads_fold : Atoms + The `structure_leads_fold` parameter is an Atoms object containing the folded structures of the leads + by the Bloch theorem. + + + Returns + ------- + subblocks : List[int] + + ''' + + HS_device = {} assert len(np.array(kpoints).shape) == 2 HS_device["kpoints"] = kpoints @@ -335,9 +389,9 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) - torch.set_default_dtype(torch.float32) - return structure_device, structure_leads, structure_leads_fold, \ - bloch_sorted_indices, bloch_R_lists,subblocks + return subblocks + + @staticmethod def remove_bonds_nonpbc(data,pbc,overlap): From ece3ec36f272871245d28834d3436d72994bdacb Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sun, 2 Mar 2025 21:47:43 +0800 Subject: [PATCH 190/209] set subblocks as property of NEGFHamiltonianInit --- dptb/negf/negf_hamiltonian_init.py | 15 +++++++++------ dptb/postprocess/NEGF.py | 8 ++++---- dptb/tests/test_negf_density_Ozaki.py | 2 +- dptb/tests/test_negf_device_property.py | 2 +- dptb/tests/test_negf_negf_hamiltonian_init.py | 2 +- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index ba4a2b3c..b8895bc4 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -97,6 +97,7 @@ def __init__(self, self.pbc_negf = pbc_negf assert len(self.pbc_negf) == 3 self.results_path = results_path + self.subblocks = None self.h2k = HR2HK( idp=model.idp, @@ -186,12 +187,12 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor useBloch=useBloch,bloch_factor=bloch_factor) # Hamiltonian initialization - subblocks = self.Hamiltonian_initialized(kpoints,useBloch,bloch_factor,block_tridiagnal,\ + self.Hamiltonian_initialized(kpoints,useBloch,bloch_factor,block_tridiagnal,\ lead_atom_range,structure_leads,structure_leads_fold) torch.set_default_dtype(torch.float32) return structure_device, structure_leads, structure_leads_fold, \ - bloch_sorted_indices, bloch_R_lists,subblocks + bloch_sorted_indices, bloch_R_lists def Hamiltonian_initialized(self,kpoints:List[List[float]],useBloch:bool,bloch_factor:List[int],\ @@ -367,7 +368,8 @@ def Hamiltonian_initialized(self,kpoints:List[List[float]],useBloch:bool,bloch_f "HDL":HDL.cdouble()*self.h_factor, "SDL":SDL.cdouble(), "HLL":hLL.cdouble()*self.h_factor, - "SLL":sLL.cdouble() + "SLL":sLL.cdouble(), + "useBloch":useBloch }) torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) @@ -380,17 +382,18 @@ def Hamiltonian_initialized(self,kpoints:List[List[float]],useBloch:bool,bloch_f SD = torch.unsqueeze(SD,dim=1) HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) HS_device.update({"Hall":Hall.cdouble()*self.h_factor, "Sall":Sall.cdouble()}) + HS_device.update({"subblocks":subblocks, "block_tridiagonal":False}) else: leftmost_size = coupling_width['lead_L'] rightmost_size = coupling_width['lead_R'] hd, hu, hl, sd, su, sl, subblocks = self.get_block_tridiagonal(HD*self.h_factor,SD.cdouble(),self.structase,\ leftmost_size,rightmost_size) - HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl}) + HS_device.update({"hd":hd, "hu":hu, "hl":hl, "sd":sd, "su":su, "sl":sl, \ + "subblocks":subblocks, "block_tridiagonal":True}) + self.subblocks = subblocks torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) - return subblocks - @staticmethod diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 15db641b..b90e9d57 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -119,10 +119,10 @@ def __init__(self, torch_device = self.torch_device) with torch.no_grad(): # if useBloch is None, structure_leads_fold,bloch_sorted_indices,bloch_R_lists = None,None,None - struct_device, struct_leads,structure_leads_fold,bloch_sorted_indices,bloch_R_lists,subblocks = \ + struct_device, struct_leads,structure_leads_fold,bloch_sorted_indices,bloch_R_lists = \ self.negf_hamiltonian.initialize(kpoints=self.kpoints,block_tridiagnal=self.block_tridiagonal,\ useBloch=self.useBloch,bloch_factor=self.bloch_factor) - self.subblocks = subblocks # for not block_tridiagonal case, subblocks is [HD.shape[1]] + # self.subblocks = subblocks # for not block_tridiagonal case, subblocks is [HD.shape[1]] self.deviceprop = DeviceProperty(self.negf_hamiltonian, struct_device, results_path=self.results_path, efermi=self.e_fermi) self.deviceprop.set_leadLR( @@ -404,7 +404,7 @@ def negf_compute(self,scf_require=False,Vbias=None): kpoint=k, Vbias=Vbias, block_tridiagonal=self.block_tridiagonal, - subblocks=self.subblocks, + subblocks=self.negf_hamiltonian.subblocks, integrate_way = self.density_options["integrate_way"], deviceprop=self.deviceprop, device_atom_norbs=self.device_atom_norbs, @@ -495,7 +495,7 @@ def negf_compute(self,scf_require=False,Vbias=None): kpoint=k, Vbias=Vbias, block_tridiagonal=self.block_tridiagonal, - subblocks=self.subblocks, + subblocks=self.negf_hamiltonian.subblocks, integrate_way = self.density_options["integrate_way"], deviceprop=self.deviceprop, device_atom_norbs=self.device_atom_norbs, diff --git a/dptb/tests/test_negf_density_Ozaki.py b/dptb/tests/test_negf_density_Ozaki.py index 20e36d13..20334108 100644 --- a/dptb/tests/test_negf_density_Ozaki.py +++ b/dptb/tests/test_negf_density_Ozaki.py @@ -48,7 +48,7 @@ def test_negf_density_Ozaki(root_directory): # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) with torch.no_grad(): - struct_device, struct_leads, _,_,_,_ = hamiltonian.initialize(kpoints=kpoints) + struct_device, struct_leads, _,_,_ = hamiltonian.initialize(kpoints=kpoints) deviceprop = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=negf_json['task_options']['e_fermi']) diff --git a/dptb/tests/test_negf_device_property.py b/dptb/tests/test_negf_device_property.py index 45271472..45c34c95 100644 --- a/dptb/tests/test_negf_device_property.py +++ b/dptb/tests/test_negf_device_property.py @@ -44,7 +44,7 @@ def test_negf_Device(root_directory): # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) with torch.no_grad(): - struct_device, struct_leads, _,_,_,_ = hamiltonian.initialize(kpoints=kpoints) + struct_device, struct_leads, _,_,_ = hamiltonian.initialize(kpoints=kpoints) deviceprop = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=negf_json['task_options']['e_fermi']) diff --git a/dptb/tests/test_negf_negf_hamiltonian_init.py b/dptb/tests/test_negf_negf_hamiltonian_init.py index 65a96294..6b933f87 100644 --- a/dptb/tests/test_negf_negf_hamiltonian_init.py +++ b/dptb/tests/test_negf_negf_hamiltonian_init.py @@ -46,7 +46,7 @@ def test_negf_Hamiltonian(root_directory): # hamiltonian = NEGFHamiltonianInit(apiH=apiHrk, structase=structase, stru_options=task_options["stru_options"], results_path=results_path) kpoints= kmesh_sampling(negf_json['task_options']["stru_options"]["kmesh"]) with torch.no_grad(): - struct_device, struct_leads, _,_,_,_ = hamiltonian.initialize(kpoints=kpoints) + struct_device, struct_leads, _,_,_ = hamiltonian.initialize(kpoints=kpoints) deviceprop = DeviceProperty(hamiltonian, struct_device, results_path=results_path, efermi=negf_json['task_options']['e_fermi']) From 51359a8c6fbb7cef1e4409f99c66e53e4295aa43 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sun, 2 Mar 2025 22:34:45 +0800 Subject: [PATCH 191/209] enable HS read directly from saved files --- dptb/negf/negf_hamiltonian_init.py | 38 ++++++++++++++++++++++++++---- dptb/postprocess/NEGF.py | 11 +++++---- dptb/utils/argcheck.py | 2 ++ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index b8895bc4..76507fb2 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -97,6 +97,7 @@ def __init__(self, self.pbc_negf = pbc_negf assert len(self.pbc_negf) == 3 self.results_path = results_path + self.saved_HS_path = None self.subblocks = None self.h2k = HR2HK( @@ -141,7 +142,8 @@ def __init__(self, log.error("The unit name is not correct !") raise ValueError - def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor=None): + def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor=None,\ + use_saved_HS=False, saved_HS_path=None): '''This function initializes the structure and Hamiltonian for a system with optional block tridiagonal and Bloch factor parameters. @@ -187,8 +189,22 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor useBloch=useBloch,bloch_factor=bloch_factor) # Hamiltonian initialization - self.Hamiltonian_initialized(kpoints,useBloch,bloch_factor,block_tridiagnal,\ + if use_saved_HS: + if saved_HS_path is None: + saved_HS_path = self.results_path + log.warning(msg="The saved_HS_path is not provided, use the results path by default.") + self.saved_HS_path = saved_HS_path + + log.info(msg="--"*40) + log.info(msg=f"The Hamiltonian is initialized from the saved path {self.saved_HS_path}.") + log.info(msg="=="*40) + else: + self.saved_HS_path = self.results_path + self.Hamiltonian_initialized(kpoints,useBloch,bloch_factor,block_tridiagnal,\ lead_atom_range,structure_leads,structure_leads_fold) + log.info(msg="--"*40) + log.info(msg=f"The Hamiltonian has been initialized by model.") + log.info(msg="=="*40) torch.set_default_dtype(torch.float32) return structure_device, structure_leads, structure_leads_fold, \ @@ -573,7 +589,14 @@ def get_hs_device(self, kpoint, V, block_tridiagonal=False): if diagonalized, return the block tridiagonalized Hamiltonian and Overlap component hd, hu, hl, sd, su, sl. """ - f = torch.load(os.path.join(self.results_path, "HS_device.pth")) + if self.saved_HS_path is None: + self.saved_HS_path = self.results_path + + HS_device_path = os.path.join(self.saved_HS_path, "HS_device.pth") + if not os.path.exists(HS_device_path): + log.error(msg="The HS_device.pth does not exist in the saved path {}.".format(self.saved_HS_path)) + raise FileNotFoundError + f = torch.load(HS_device_path) kpoints = f["kpoints"] ik = None @@ -626,7 +649,14 @@ def get_hs_lead(self, kpoint, tab, v): if diagonalized, return the block tridiagonalized Hamiltonian and Overlap component hd, hu, hl, sd, su, sl. """ - f = torch.load(os.path.join(self.results_path, "HS_{0}.pth".format(tab))) + if self.saved_HS_path is None: + self.saved_HS_path = self.results_path + + HS_lead_path = os.path.join(self.saved_HS_path, "HS_{0}.pth".format(tab)) + if not os.path.exists(HS_lead_path): + log.error(msg="The HS_{0}.pth does not exist in the saved path {1}.".format(tab, self.saved_HS_path)) + raise FileNotFoundError + f = torch.load(HS_lead_path) kpoints = f["kpoints"] kpoints_bloch = f["kpoints_bloch"] bloch_factor = f["bloch_factor"] diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index b90e9d57..72a72c19 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -35,6 +35,7 @@ def __init__(self, structure: Union[AtomicData, ase.Atoms, str], ele_T: float,e_fermi: float, emin: float, emax: float, espacing: float, + use_saved_HS: bool, saved_HS_path: str, density_options: dict, unit: str, scf: bool, poisson_options: dict, @@ -49,11 +50,8 @@ def __init__(self, **kwargs): - # self.apiH = apiHrk - self.model = model self.results_path = results_path - # self.jdata = jdata self.cdtype = torch.complex128 self.torch_device = torch_device @@ -64,6 +62,10 @@ def __init__(self, self.eta_lead = eta_lead; self.eta_device = eta_device self.emin = emin; self.emax = emax; self.espacing = espacing self.stru_options = stru_options + + self.use_saved_HS = use_saved_HS + self.saved_HS_path = saved_HS_path + self.sgf_solver = sgf_solver self.self_energy_save = self_energy_save self.self_energy_save_path = self_energy_save_path @@ -121,7 +123,8 @@ def __init__(self, # if useBloch is None, structure_leads_fold,bloch_sorted_indices,bloch_R_lists = None,None,None struct_device, struct_leads,structure_leads_fold,bloch_sorted_indices,bloch_R_lists = \ self.negf_hamiltonian.initialize(kpoints=self.kpoints,block_tridiagnal=self.block_tridiagonal,\ - useBloch=self.useBloch,bloch_factor=self.bloch_factor) + useBloch=self.useBloch,bloch_factor=self.bloch_factor,\ + use_saved_HS=self.use_saved_HS, saved_HS_path=self.saved_HS_path) # self.subblocks = subblocks # for not block_tridiagonal case, subblocks is [HD.shape[1]] self.deviceprop = DeviceProperty(self.negf_hamiltonian, struct_device, results_path=self.results_path, efermi=self.e_fermi) diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index b5680c05..a6f32a98 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -1041,6 +1041,8 @@ def negf(): Argument("block_tridiagonal", bool, optional=True, default=False, doc=doc_block_tridiagonal), Argument("ele_T", [float, int], optional=False, doc=doc_ele_T), Argument("unit", str, optional=True, default="Hartree", doc=doc_unit), + Argument("use_saved_HS", bool, optional=True, default=False, doc="Whether to use saved Hamiltonian and overlap matrix"), + Argument("saved_HS_path", str, optional=True, default=None, doc="The path to the saved Hamiltonian and overlap matrix"), Argument("scf_options", dict, optional=True, default={}, sub_fields=[], sub_variants=[scf_options()], doc=doc_scf_options), Argument("stru_options", dict, optional=False, sub_fields=stru_options(), doc=doc_stru_options), Argument("poisson_options", dict, optional=True, default={}, sub_fields=[], sub_variants=[poisson_options()], doc=doc_poisson_options), From 4173d418dcce2f4fc22683b1cef1fd51325d6bc6 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 3 Mar 2025 15:26:04 +0800 Subject: [PATCH 192/209] add HS_inmem in cal_green_function --- dptb/negf/device_property.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index 8b5afbac..3846fa55 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -109,7 +109,8 @@ def set_leadLR(self, lead_L, lead_R): # self.mu = self.efermi - 0.5*(self.lead_L.voltage + self.lead_R.voltage) # temporarily for NanoTCAD - def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=True, Vbias=None): + def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=True, Vbias=None, + HS_inmem:bool=False): ''' computes the Green's function for a given energy and k-point in device. the tags used here to identify different Green's functions follows the NEGF theory @@ -129,7 +130,9 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr A boolean parameter that shows whether the Hamiltonian matrix is block tridiagonal or not. If set to True, the Hamiltonian matrix is assumed to have a block tridiagonal structure, which can lead to computational efficiency in certain cases. - + HS_inmem + A boolean parameter that shows whether the Hamiltonian/overlap is stored in memory after finishing + cal_green_function or not, which is important for large-scale calculations. ''' assert len(np.array(kpoint).reshape(-1)) == 3 if not isinstance(energy, torch.Tensor): @@ -230,6 +233,10 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr green_funcs[tags[t]] = ans[t] self.greenfuncs = green_funcs + + if not HS_inmem: + del self.hd, self.sd, self.hl, self.su, self.sl, self.hu + # self.green = update_temp_file(update_fn=fn, file_path=GFpath, ee=ee, tags=tags, info="Computing Green's Function") def _cal_current_(self, espacing): From 277c40ef1ae6c125a11ff8a62b9b4c56cac186b9 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 3 Mar 2025 15:27:43 +0800 Subject: [PATCH 193/209] shut down AD when using surfaceGreen.apply --- dptb/negf/surface_green.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dptb/negf/surface_green.py b/dptb/negf/surface_green.py index 60f58437..6c48e3aa 100644 --- a/dptb/negf/surface_green.py +++ b/dptb/negf/surface_green.py @@ -192,7 +192,8 @@ def selfEnergy(hL, hLL, sL, sLL, ee, hDL=None, sDL=None, etaLead=1e-8, Bulk=Fals if hDL == None: ESH = (eeshifted * sL - hL) - SGF = SurfaceGreen.apply(hL, hLL, sL, sLL, eeshifted + 1j * etaLead, method) + with torch.no_grad(): + SGF = SurfaceGreen.apply(hL, hLL, sL, sLL, eeshifted + 1j * etaLead, method) if Bulk: Sig = tLA.inv(SGF) # SGF^1 @@ -200,7 +201,8 @@ def selfEnergy(hL, hLL, sL, sLL, ee, hDL=None, sDL=None, etaLead=1e-8, Bulk=Fals Sig = ESH - tLA.inv(SGF) else: a, b = hDL.shape - SGF = SurfaceGreen.apply(hL, hLL, sL, sLL, eeshifted + 1j * etaLead, method) + with torch.no_grad(): + SGF = SurfaceGreen.apply(hL, hLL, sL, sLL, eeshifted + 1j * etaLead, method) #SGF = iterative_simple(eeshifted + 1j * etaLead, hL, hLL, sL, sLL, iter_max=1000) Sig = (eeshifted*sDL-hDL) @ SGF[:b,:b] @ (eeshifted*sDL.conj().T-hDL.conj().T) return Sig, SGF # R(nuo, nuo) From 17ede8b36b6e8db8f01fdc08977bbc9bd696909d Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 3 Mar 2025 15:29:24 +0800 Subject: [PATCH 194/209] add HS_inmem in self_energy --- dptb/negf/lead_property.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index dc881b1f..df7a9afb 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -95,7 +95,8 @@ def __init__(self, tab, hamiltonian, structure, results_path, voltage,\ assert self.structure_leads_fold is not None def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-Sancho", \ - save: bool=False, save_path: str=None, se_info_display: bool=False): + save: bool=False, save_path: str=None, se_info_display: bool=False, + HS_inmem: bool=False): '''calculate and loads the self energy and surface green function at the given kpoint and energy. Parameters @@ -113,7 +114,9 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S save_path : the path to save the self energy. If not specified, the self energy will be saved in the results_path. se_info_display : - whether to display the information of the self energy calculation. + whether to display the information of the self energy calculation. + HS_inmem : + whether to store the Hamiltonian and overlap matrix in memory. Default is False. ''' assert len(np.array(kpoint).reshape(-1)) == 3 # according to given kpoint and e_mesh, calculating or loading the self energy and surface green function to self. @@ -216,6 +219,8 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S eeshifted = energy + self.efermi self.se = (eeshifted*self.SDL-self.HDL) @ sgf_k[:b,:b] @ (eeshifted*self.SDL.conj().T-self.HDL.conj().T) + if not HS_inmem: + del self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL if save: assert save_path is not None, "Please specify the path to save the self energy." From 26abdcfa55c3504cf64078966651099c47106bba Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 3 Mar 2025 22:03:50 +0800 Subject: [PATCH 195/209] calculate self energy in reduced form to save memory --- dptb/negf/lead_property.py | 50 +++++++++++++++++-- dptb/tests/test_negf_device_property.py | 10 +--- dptb/tests/test_negf_negf_hamiltonian_init.py | 19 ++++++- 3 files changed, 65 insertions(+), 14 deletions(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index df7a9afb..754b2601 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -158,15 +158,17 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) self.voltage_old = self.voltage self.kpoint = torch.tensor(kpoint) - + + HDL_reduced, SDL_reduced = self.HDL_reduced(self.HDL, self.SDL) + self.se, _ = selfEnergy( ee=energy, hL=self.HL, hLL=self.HLL, sL=self.SL, sLL=self.SLL, - hDL=self.HDL, - sDL=self.SDL, #TODO: check chemiPot settiing is correct or not + hDL=HDL_reduced, + sDL=SDL_reduced, #TODO: check chemiPot settiing is correct or not chemiPot=self.efermi, # temmporarily change to self.efermi for the case in which applying lead bias to corresponding to Nanotcad etaLead=eta_lead, method=method @@ -212,12 +214,16 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S sgf_k = torch.sum(torch.stack(sgf_k),dim=0)/len(sgf_k) sgf_k = sgf_k[self.bloch_sorted_indice,:][:,self.bloch_sorted_indice] - b = self.HDL.shape[1] + b = self.HDL.shape[1] # size of lead hamiltonian + + # HDL_reduced, SDL_reduced = self.HDL_reduced(self.HDL, self.SDL) + HDL_reduced, SDL_reduced = self.HDL, self.SDL if not isinstance(energy, torch.Tensor): eeshifted = torch.scalar_tensor(energy, dtype=torch.complex128) + self.efermi else: eeshifted = energy + self.efermi - self.se = (eeshifted*self.SDL-self.HDL) @ sgf_k[:b,:b] @ (eeshifted*self.SDL.conj().T-self.HDL.conj().T) + # self.se = (eeshifted*self.SDL-self.HDL) @ sgf_k[:b,:b] @ (eeshifted*self.SDL.conj().T-self.HDL.conj().T) + self.se = (eeshifted*SDL_reduced-HDL_reduced) @ sgf_k[:b,:b] @ (eeshifted*SDL_reduced.conj().T-HDL_reduced.conj().T) if not HS_inmem: del self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL @@ -231,6 +237,40 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S # else: # torch.save(self.se, os.path.join(self.results_path, f"se_nobloch_k{kpoint[0]}_{kpoint[1]}_{kpoint[2]}_{energy}.pth")) + @staticmethod + def HDL_reduced(HDL: torch.Tensor, SDL: torch.Tensor) -> torch.Tensor: + '''This function takes in Hamiltonian/Overlap matrix between lead and device and reduces + it based on the non-zero range of the Hamiltonian matrix. + + When the device part has only one orbital, the Hamiltonian matrix is not reduced. + + Parameters + ---------- + HDL : torch.Tensor + HDL is a torch.Tensor representing the Hamiltonian matrix between the first principal layer and the device. + SDL : torch.Tensor + SDL is a torch.Tensor representing the overlap matrix between the first principal layer and the device. + + Returns + ------- + HDL_reduced, SDL_reduced + The reduced Hamiltonian and overlap matrix. + + ''' + HDL_nonzero_range = (HDL.nonzero().min(dim=0).values, HDL.nonzero().max(dim=0).values) + if HDL.shape[0] == 1: # Only 1 orbital in the device + HDL_reduced = HDL + SDL_reduced = SDL + elif HDL_nonzero_range[0][0] > 0: # Right lead + HDL_reduced = HDL[HDL_nonzero_range[0][0]:, :] + SDL_reduced = SDL[HDL_nonzero_range[0][0]:, :] + else: # Left lead + HDL_reduced = HDL[:HDL_nonzero_range[1][0]+1, :] + SDL_reduced = SDL[:HDL_nonzero_range[1][0]+1, :] + + return HDL_reduced, SDL_reduced + + def sigmaLR2Gamma(self, se): '''calculate the Gamma function from the self energy. diff --git a/dptb/tests/test_negf_device_property.py b/dptb/tests/test_negf_device_property.py index 45c34c95..98d3a20c 100644 --- a/dptb/tests/test_negf_device_property.py +++ b/dptb/tests/test_negf_device_property.py @@ -102,14 +102,8 @@ def test_negf_Device(root_directory): ) # check left and right leads' self-energy - lead_L_se_standard=torch.tensor([[-3.3171e-07-0.6096j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], - [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], - [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], - [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j]], dtype=torch.complex128) - lead_R_se_standard=torch.tensor([[ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], - [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], - [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,0.0000e+00+0.0000j], - [ 0.0000e+00+0.0000j, 0.0000e+00+0.0000j, 0.0000e+00+0.0000j,-3.3171e-07-0.6096j]], dtype=torch.complex128) + lead_L_se_standard=torch.tensor([[-3.3171e-07-0.6096j]], dtype=torch.complex128) + lead_R_se_standard=torch.tensor([[-3.3171e-07-0.6096j]], dtype=torch.complex128) print('device.lead_L.se:',device.lead_L.se) print('device.lead_R.se:',device.lead_R.se) assert abs(device.lead_L.se-lead_L_se_standard).max()<1e-5 diff --git a/dptb/tests/test_negf_negf_hamiltonian_init.py b/dptb/tests/test_negf_negf_hamiltonian_init.py index 6b933f87..ed4081ed 100644 --- a/dptb/tests/test_negf_negf_hamiltonian_init.py +++ b/dptb/tests/test_negf_negf_hamiltonian_init.py @@ -71,7 +71,24 @@ def test_negf_Hamiltonian(root_directory): ) ) - + leads = ["lead_L", "lead_R"] + e=0 + for ll in leads: + getattr(deviceprop, ll).self_energy( + energy=e, + kpoint=kpoints[0], + eta_lead=negf_json['task_options']["eta_lead"], + method=negf_json['task_options']["sgf_solver"], + save=False + ) + print("lead_L self energy:",deviceprop.lead_L.se) + print("lead_R self energy:",deviceprop.lead_R.se) + + lead_L_se_standard = torch.tensor([[1.8103e-08-0.6096j]], dtype=torch.complex128) + assert abs(deviceprop.lead_L.se-lead_L_se_standard).max()<1e-5 + lead_R_se_standard = torch.tensor([[1.8103e-08-0.6096j]], dtype=torch.complex128) + assert abs(deviceprop.lead_R.se-lead_R_se_standard).max()<1e-5 + #check device's Hamiltonian assert all(struct_device.symbols=="C4") assert all(struct_device.pbc)==False From 2f0c1ec361926c60000b179628fc7e3b1166b11c Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 3 Mar 2025 22:16:31 +0800 Subject: [PATCH 196/209] fix logic bug when get_hs_device --- dptb/negf/device_property.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index 3846fa55..8a5c4eac 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -190,7 +190,7 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr # self.hd, self.sd, self.hl, self.su, self.sl, self.hu = self.hamiltonian.get_hs_device(self.kpoint, self.V, block_tridiagonal) # hd in format:(block_index,orb,orb) - if (hasattr(self, "hd") and hasattr(self, "sd")) or (self.newK_flag or self.newV_flag): + if (not (hasattr(self, "hd") and hasattr(self, "sd"))) or (self.newK_flag or self.newV_flag): self.hd, self.sd, self.hl, self.su, self.sl, self.hu = self.hamiltonian.get_hs_device(self.kpoint, self.V, block_tridiagonal) @@ -343,6 +343,11 @@ def _cal_dos_(self): DOS with spin multiplicity ''' dos = 0 + #TODO: transfer cal_dos to static method for any k and energy + if (not(hasattr(self, "hd") and hasattr(self, "sd"))) or (self.newK_flag or self.newV_flag): + self.hd, self.sd, self.hl, self.su, self.sl, self.hu = \ + self.hamiltonian.get_hs_device(self.kpoint, self.V, self.block_tridiagonal) + for jj in range(len(self.grd)): if not self.block_tridiagonal or len(self.gru) == 0: temp = self.grd[jj] @ self.sd[jj] # taking each diagonal block with all energy e together From 7cd8a6989f05835e898e0b1459b5fdf404817246 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 3 Mar 2025 22:17:31 +0800 Subject: [PATCH 197/209] add only_subblocks term in get_hs_device --- dptb/negf/negf_hamiltonian_init.py | 13 ++++++++++++- dptb/postprocess/NEGF.py | 11 +++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 76507fb2..f2dd1a52 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -572,7 +572,7 @@ def get_block_tridiagonal(self,HK,SK,structase:ase.Atoms,leftmost_size:int,right return hd, hu, hl, sd, su, sl, subblocks - def get_hs_device(self, kpoint, V, block_tridiagonal=False): + def get_hs_device(self, kpoint=[0,0,0], V=None, block_tridiagonal=False, only_subblocks=False): """ get the device Hamiltonian and overlap matrix at a specific kpoint In diagonalization mode, the Hamiltonian and overlap matrix are block tridiagonalized, @@ -597,6 +597,17 @@ def get_hs_device(self, kpoint, V, block_tridiagonal=False): log.error(msg="The HS_device.pth does not exist in the saved path {}.".format(self.saved_HS_path)) raise FileNotFoundError f = torch.load(HS_device_path) + + if only_subblocks: + if "subblocks" not in f: + log.warning(msg=" 'subblocks' might not be saved in the HS_device.pth for old version.") + log.error(msg="The subblocks are not saved in the HS_device.pth.") + + raise ValueError + subblocks = f["subblocks"] + return subblocks + + kpoints = f["kpoints"] ik = None diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index 72a72c19..5e1a8d6a 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -50,7 +50,7 @@ def __init__(self, **kwargs): - self.model = model + # self.model = model # No need to set model as property for memory saving self.results_path = results_path self.cdtype = torch.complex128 self.torch_device = torch_device @@ -401,7 +401,10 @@ def negf_compute(self,scf_require=False,Vbias=None): else: getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected_orb_mask].mean() # getattr(self.deviceprop, ll).voltage = Vbias[-1] - + + if self.negf_hamiltonian.subblocks is None: + self.negf_hamiltonian.subblocks = \ + self.negf_hamiltonian.get_hs_device(only_subblocks=True) self.density.density_integrate_Fiori( e_grid = self.uni_grid, kpoint=k, @@ -493,6 +496,10 @@ def negf_compute(self,scf_require=False,Vbias=None): elif self.density_options["method"] == "Fiori": log.warning("Fiori method is under test in this version.") try: + if self.negf_hamiltonian.subblocks is None: + self.negf_hamiltonian.subblocks = \ + self.negf_hamiltonian.get_hs_device(only_subblocks=True) + self.density.density_integrate_Fiori( e_grid = self.uni_grid, kpoint=k, From 3969d34dda2031be078fd98cb06bdcba3e3624f8 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 4 Mar 2025 14:40:48 +0800 Subject: [PATCH 198/209] add some docstring --- dptb/negf/device_property.py | 12 ++++++++---- dptb/negf/lead_property.py | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index 8a5c4eac..2fef1b8c 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -110,7 +110,7 @@ def set_leadLR(self, lead_L, lead_R): def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=True, Vbias=None, - HS_inmem:bool=False): + HS_inmem:bool=True): ''' computes the Green's function for a given energy and k-point in device. the tags used here to identify different Green's functions follows the NEGF theory @@ -209,12 +209,16 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr # Fluctuation-Dissipation theorem seinL = 1j*(seL-seL.conj().T) * self.lead_L.fermi_dirac(energy+self.mu).reshape(-1) seinR = 1j*(seR-seR.conj().T) * self.lead_R.fermi_dirac(energy+self.mu).reshape(-1) - s01, s02 = s_in[0].shape - se01, se02 = seL.shape - idx0, idy0 = min(s01, se01), min(s02, se02) + s01, s02 = s_in[0].shape # The shape of the first H block + se01, se02 = seL.shape # The shape of the left self-energy + if se01 > s01 or se02 > s02: + log.warning("The shape of left self-energy is larger than the first Hamiltonian block.") + idx0, idy0 = min(s01, se01), min(s02, se02) # The minimum of the two shapes s11, s12 = s_in[-1].shape se11, se12 = seR.shape + if se11 > s11 or se12 > s12: + log.warning("The shape of right self-energy is larger than the last Hamiltonian block.") idx1, idy1 = min(s11, se11), min(s12, se12) green_funcs = {} diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 754b2601..4023c8f6 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -258,6 +258,7 @@ def HDL_reduced(HDL: torch.Tensor, SDL: torch.Tensor) -> torch.Tensor: ''' HDL_nonzero_range = (HDL.nonzero().min(dim=0).values, HDL.nonzero().max(dim=0).values) + # HDL_nonzero_range is a tuple((min_row,min_col),(max_row,max_col)) if HDL.shape[0] == 1: # Only 1 orbital in the device HDL_reduced = HDL SDL_reduced = SDL From de780d5b132341b89c71db5ed5dd5ff750cba199 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Tue, 4 Mar 2025 14:41:20 +0800 Subject: [PATCH 199/209] reset V as one-dimensal vector for memory saving --- dptb/negf/negf_hamiltonian_init.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index f2dd1a52..8e20265e 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -627,15 +627,16 @@ def get_hs_device(self, kpoint=[0,0,0], V=None, block_tridiagonal=False, only_su if V.shape == torch.Size([]): allorb = sum([hd_k[i].shape[0] for i in range(len(hd_k))]) V = V.repeat(allorb) - V = torch.diag(V).cdouble() + # V = torch.diag(V).cdouble() counted = 0 for i in range(len(hd_k)): # TODO: this part may have probelms when V!=0 l_slice = slice(counted, counted+hd_k[i].shape[0]) - hd_k[i] = hd_k[i] - V[l_slice,l_slice]@sd_k[i] + V_sub = V[l_slice].view(-1,1).cdouble() + hd_k[i] = hd_k[i] - V_sub * sd_k[i] if i 0: - hl_k[i-1] = hl_k[i-1] - V[l_slice,l_slice]@sl_k[i-1] + hl_k[i-1] = hl_k[i-1] - V_sub * sl_k[i-1] counted += hd_k[i].shape[0] return hd_k , sd_k, hl_k , su_k, sl_k, hu_k From 8a2a6b3578ffa336ee4e88b6ad774256ee89eb12 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 8 Mar 2025 14:41:37 +0800 Subject: [PATCH 200/209] add self energy shape check for block-tri format --- dptb/negf/device_property.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index 2fef1b8c..c2c286a5 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -211,15 +211,18 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr seinR = 1j*(seR-seR.conj().T) * self.lead_R.fermi_dirac(energy+self.mu).reshape(-1) s01, s02 = s_in[0].shape # The shape of the first H block se01, se02 = seL.shape # The shape of the left self-energy - if se01 > s01 or se02 > s02: - log.warning("The shape of left self-energy is larger than the first Hamiltonian block.") - idx0, idy0 = min(s01, se01), min(s02, se02) # The minimum of the two shapes - s11, s12 = s_in[-1].shape se11, se12 = seR.shape - if se11 > s11 or se12 > s12: - log.warning("The shape of right self-energy is larger than the last Hamiltonian block.") + idx0, idy0 = min(s01, se01), min(s02, se02) # The minimum of the two shapes idx1, idy1 = min(s11, se11), min(s12, se12) + if block_tridiagonal: + if se01 != s01 or se02 != s02: + log.warning("The shape of left self-energy is not equal to the first Hamiltonian block.") + log.warning("This shouldn't happen in block tridiagonal format.") + if se11 != s11 or se12 != s12: + log.warning("The shape of right self-energy is not equal to the last Hamiltonian block.") + log.warning("This shouldn't happen in block tridiagonal format.") + green_funcs = {} From 491a24aead67ffab8b3a229da180fc5965f46458 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Sat, 8 Mar 2025 14:42:52 +0800 Subject: [PATCH 201/209] use HDL_reduced to cal self energy in blcok-tri format --- dptb/negf/lead_property.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 4023c8f6..6350710e 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -216,8 +216,9 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S sgf_k = sgf_k[self.bloch_sorted_indice,:][:,self.bloch_sorted_indice] b = self.HDL.shape[1] # size of lead hamiltonian - # HDL_reduced, SDL_reduced = self.HDL_reduced(self.HDL, self.SDL) - HDL_reduced, SDL_reduced = self.HDL, self.SDL + # reduce the Hamiltonian and overlap matrix based on the non-zero range of HDL + HDL_reduced, SDL_reduced = self.HDL_reduced(self.HDL, self.SDL) + # HDL_reduced, SDL_reduced = self.HDL, self.SDL if not isinstance(energy, torch.Tensor): eeshifted = torch.scalar_tensor(energy, dtype=torch.complex128) + self.efermi else: @@ -257,6 +258,10 @@ def HDL_reduced(HDL: torch.Tensor, SDL: torch.Tensor) -> torch.Tensor: The reduced Hamiltonian and overlap matrix. ''' + assert len(HDL.shape) == 2, "The shape of HDL should be 2." + assert len(SDL.shape) == 2, "The shape of SDL should be 2." + assert HDL.shape == SDL.shape, "The shape of HDL and SDL should be the same." + HDL_nonzero_range = (HDL.nonzero().min(dim=0).values, HDL.nonzero().max(dim=0).values) # HDL_nonzero_range is a tuple((min_row,min_col),(max_row,max_col)) if HDL.shape[0] == 1: # Only 1 orbital in the device From 5b139c22001fa57f05194261ea5b6710f3903ed5 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 10 Mar 2025 21:24:17 +0800 Subject: [PATCH 202/209] rename variables for clearity --- dptb/negf/lead_property.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/dptb/negf/lead_property.py b/dptb/negf/lead_property.py index 6350710e..8b9e820e 100644 --- a/dptb/negf/lead_property.py +++ b/dptb/negf/lead_property.py @@ -154,19 +154,19 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S if not self.useBloch: if not hasattr(self, "HL") or abs(self.voltage_old-self.voltage)>1e-6 or max(abs(self.kpoint-torch.tensor(kpoint)))>1e-6: - self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL \ + self.HLk, self.HLLk, self.HDLk, self.SLk, self.SLLk, self.SDLk \ = self.hamiltonian.get_hs_lead(kpoint, tab=self.tab, v=self.voltage) self.voltage_old = self.voltage self.kpoint = torch.tensor(kpoint) - HDL_reduced, SDL_reduced = self.HDL_reduced(self.HDL, self.SDL) + HDL_reduced, SDL_reduced = self.HDL_reduced(self.HDLk, self.SDLk) self.se, _ = selfEnergy( ee=energy, - hL=self.HL, - hLL=self.HLL, - sL=self.SL, - sLL=self.SLL, + hL=self.HLk, + hLL=self.HLLk, + sL=self.SLk, + sLL=self.SLLk, hDL=HDL_reduced, sDL=SDL_reduced, #TODO: check chemiPot settiing is correct or not chemiPot=self.efermi, # temmporarily change to self.efermi for the case in which applying lead bias to corresponding to Nanotcad @@ -187,15 +187,15 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S m_size = self.bloch_factor[1]*self.bloch_factor[0] for ik_lead,k_bloch in enumerate(kpoints_bloch): k_bloch = torch.tensor(k_bloch) - self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL \ + self.HLk, self.HLLk, self.HDLk, self.SLk, self.SLLk, self.SDLk \ = self.hamiltonian.get_hs_lead(k_bloch, tab=self.tab, v=self.voltage) _, sgf = selfEnergy( ee=energy, - hL=self.HL, - hLL=self.HLL, - sL=self.SL, - sLL=self.SLL, #TODO: check chemiPot settiing is correct or not + hL=self.HLk, + hLL=self.HLLk, + sL=self.SLk, + sLL=self.SLLk, #TODO: check chemiPot settiing is correct or not chemiPot=self.efermi, # temmporarily change to self.efermi for the case in which applying lead bias to corresponding to Nanotcad etaLead=eta_lead, method=method @@ -214,10 +214,10 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S sgf_k = torch.sum(torch.stack(sgf_k),dim=0)/len(sgf_k) sgf_k = sgf_k[self.bloch_sorted_indice,:][:,self.bloch_sorted_indice] - b = self.HDL.shape[1] # size of lead hamiltonian + b = self.HDLk.shape[1] # size of lead hamiltonian # reduce the Hamiltonian and overlap matrix based on the non-zero range of HDL - HDL_reduced, SDL_reduced = self.HDL_reduced(self.HDL, self.SDL) + HDL_reduced, SDL_reduced = self.HDL_reduced(self.HDLk, self.SDLk) # HDL_reduced, SDL_reduced = self.HDL, self.SDL if not isinstance(energy, torch.Tensor): eeshifted = torch.scalar_tensor(energy, dtype=torch.complex128) + self.efermi @@ -227,7 +227,7 @@ def self_energy(self, kpoint, energy, eta_lead: float=1e-5, method: str="Lopez-S self.se = (eeshifted*SDL_reduced-HDL_reduced) @ sgf_k[:b,:b] @ (eeshifted*SDL_reduced.conj().T-HDL_reduced.conj().T) if not HS_inmem: - del self.HL, self.HLL, self.HDL, self.SL, self.SLL, self.SDL + del self.HLk, self.HLLk, self.HDLk, self.SLk, self.SLLk, self.SDLk if save: assert save_path is not None, "Please specify the path to save the self energy." @@ -261,7 +261,7 @@ def HDL_reduced(HDL: torch.Tensor, SDL: torch.Tensor) -> torch.Tensor: assert len(HDL.shape) == 2, "The shape of HDL should be 2." assert len(SDL.shape) == 2, "The shape of SDL should be 2." assert HDL.shape == SDL.shape, "The shape of HDL and SDL should be the same." - + HDL_nonzero_range = (HDL.nonzero().min(dim=0).values, HDL.nonzero().max(dim=0).values) # HDL_nonzero_range is a tuple((min_row,min_col),(max_row,max_col)) if HDL.shape[0] == 1: # Only 1 orbital in the device From 29fb773825c9bd8ab6c2fcb705ba7c61207dd9bb Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 10 Mar 2025 21:25:05 +0800 Subject: [PATCH 203/209] enable HS_device and HS_lead saved in hdf5 files --- dptb/negf/negf_hamiltonian_init.py | 177 +++++++++++++++++++++++------ dptb/tests/test_negf_run.py | 29 ++++- 2 files changed, 168 insertions(+), 38 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 8e20265e..eb4886e4 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -28,6 +28,7 @@ from dptb.negf.sort_btd import sort_lexico, sort_projection, sort_capacitance from dptb.negf.split_btd import show_blocks,split_into_subblocks,split_into_subblocks_optimized from scipy.spatial import KDTree +import h5py ''' a Hamiltonian object that initializes and manipulates device and lead Hamiltonians for NEGF ''' @@ -317,8 +318,13 @@ def Hamiltonian_initialized(self,kpoints:List[List[float]],useBloch:bool,bloch_f # device and lead's hopping HDL, SDL = HK[:,d_start:d_end, l_start:l_end], SK[:,d_start:d_end, l_start:l_end] nonzero_indice = torch.nonzero(HDL) - coupling_width[kk] = max(torch.max(nonzero_indice[:,1]).item()-torch.min(nonzero_indice[:,1]).item() +1,\ - torch.max(nonzero_indice[:,2]).item()-torch.min(nonzero_indice[:,2]).item() +1) + # The coupling width measures the coupling between the semi-infinite electrode and the electrode part in device + # depth_in_device measures the coupling extent in electrode part in device + # depth_in_lead measures the coupling extent in the semi-infinite electrode + # Considering it is a homojunction, depth_in_device and depth_in_lead should be same or very close + depth_in_device = torch.max(nonzero_indice[:,1]).item()-torch.min(nonzero_indice[:,1]).item() +1 + depth_in_lead = torch.max(nonzero_indice[:,2]).item()-torch.min(nonzero_indice[:,2]).item() +1 + coupling_width[kk] = max(depth_in_device,depth_in_lead) log.info(msg="The coupling width of {} is {}.".format(kk,coupling_width[kk])) # get lead_data @@ -387,8 +393,27 @@ def Hamiltonian_initialized(self,kpoints:List[List[float]],useBloch:bool,bloch_f "SLL":sLL.cdouble(), "useBloch":useBloch }) - - torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) + + lead_file = os.path.join(self.results_path, "HS_"+kk+".h5") + + with h5py.File(lead_file, "w") as f: + for key, items in HS_leads.items(): + if key == "useBloch": + f.create_dataset(f"{key}", data=items) + elif key == "kpoints": + f.create_dataset(f"{key}", data=np.array(items)) + elif key in ["kpoints_bloch", "bloch_factor"]: + if items is not None: + f.create_dataset(f"{key}", data=np.array(items)) + else: + f.create_dataset(f"{key}", data=np.array("None", dtype=h5py.string_dtype())) + elif key in ["HL", "SL", "HDL", "SDL", "HLL", "SLL"]: + f.create_dataset(f"{key}", data=items.numpy()) + else: + raise ValueError(f"Unsupported key {key} in HS_leads") + + + # torch.save(HS_leads, os.path.join(self.results_path, "HS_"+kk+".pth")) if not block_tridiagnal: @@ -408,8 +433,29 @@ def Hamiltonian_initialized(self,kpoints:List[List[float]],useBloch:bool,bloch_f "subblocks":subblocks, "block_tridiagonal":True}) self.subblocks = subblocks - torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) - + # torch.save(HS_device, os.path.join(self.results_path, "HS_device.pth")) + + device_file = os.path.join(self.results_path, "HS_device.h5") + with h5py.File(device_file, "w") as f: + for key, items in HS_device.items(): + if key in ["kpoints", "subblocks"]: + f.create_dataset(f"{key}", data=np.array(items)) + elif key == "block_tridiagonal": + f.create_dataset(f"{key}", data=items) # bool type + elif key in ["HD", "SD", "Hall", "Sall", "hd", "hu", "hl", "sd", "su", "sl"]: + if block_tridiagnal: + assert key in ["hd", "hu", "hl", "sd", "su", "sl"], f"Unsupported key {key} for block_tridiagnal case" + group = f.create_group(key) + for idx_k, item in enumerate(items): + assert isinstance(item, list), f"Unsupported type {type(item)} for {key}" + sub_block_len = len(item) + for idx_block in range(sub_block_len): + group.create_dataset(f"{key}_k{idx_k}_b{idx_block}", data=item[idx_block].numpy()) + else: + assert key in ["HD", "SD", "Hall", "Sall"], f"Unsupported key {key} for not block_tridiagnal case" + f.create_dataset(f"{key}", data=items.numpy()) + else: + raise ValueError(f"Unsupported key {key} in HS_device") @staticmethod @@ -592,24 +638,61 @@ def get_hs_device(self, kpoint=[0,0,0], V=None, block_tridiagonal=False, only_su if self.saved_HS_path is None: self.saved_HS_path = self.results_path - HS_device_path = os.path.join(self.saved_HS_path, "HS_device.pth") - if not os.path.exists(HS_device_path): - log.error(msg="The HS_device.pth does not exist in the saved path {}.".format(self.saved_HS_path)) - raise FileNotFoundError - f = torch.load(HS_device_path) + HS_device_path_pth = os.path.join(self.saved_HS_path, "HS_device.pth") + HS_device_path_h5 = os.path.join(self.saved_HS_path, "HS_device.h5") + + + if os.path.exists(HS_device_path_h5): + log.info(msg="The HS_device.h5 exists in the saved path {}.".format(self.saved_HS_path)) + HS_device_path = HS_device_path_h5 + HS_device = {} + with h5py.File(HS_device_path, "r") as f: + for key in f.keys(): + if isinstance(f[key], h5py.Dataset): + if key in ["kpoints", "subblocks"]: + HS_device[key] = np.array(f[key]) + elif key == "block_tridiagonal": + HS_device[key] = f[key][()] + else: + assert isinstance(f[key][()], np.ndarray),f"Expected np.ndarray, but got {type(f[key][()])}" + # read NumPy array: HD, SD, Hall, Sall + HS_device[key] = torch.tensor(f[key][()]) + else: + group = f[key] + items = [];sublist = [] + sub_keys = sorted(group.keys()) # ensure the order of subblocks + current_idx_k = -1 + for sub_key in sub_keys: # sub_key format: f"{key}_k{idx_k}_b{idx_block}" + parts = sub_key.split("_k") + if len(parts) < 2: + raise ValueError(f"Unexpected dataset format: {sub_key}") + idx_k, idx_block = map(int, parts[1].split("_b")) + if idx_k != current_idx_k: + if sublist: + items.append(sublist) # store the previous sublist + sublist = [] # begin a new sublist + current_idx_k = idx_k + sublist.append(torch.tensor(group[sub_key][()])) + if sublist: + items.append(sublist) # store the last sublist + HS_device[key] = items # idx_k, idx_block + + elif os.path.exists(HS_device_path_pth): + log.info(msg="The HS_device.pth exists in the saved path {}.".format(self.saved_HS_path)) + HS_device_path = HS_device_path_pth + HS_device = torch.load(HS_device_path) + if only_subblocks: - if "subblocks" not in f: + if "subblocks" not in HS_device: log.warning(msg=" 'subblocks' might not be saved in the HS_device.pth for old version.") log.error(msg="The subblocks are not saved in the HS_device.pth.") raise ValueError - subblocks = f["subblocks"] + subblocks = HS_device["subblocks"] return subblocks - - - kpoints = f["kpoints"] - + + kpoints = HS_device["kpoints"] ik = None for i, k in enumerate(kpoints): if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: @@ -622,7 +705,9 @@ def get_hs_device(self, kpoint=[0,0,0], V=None, block_tridiagonal=False, only_su if block_tridiagonal: # hd format: ( k_index,block_index, orb, orb) - hd_k, sd_k, hl_k, su_k, sl_k, hu_k = f["hd"][ik], f["sd"][ik], f["hl"][ik], f["su"][ik], f["sl"][ik], f["hu"][ik] + hd_k, sd_k, hl_k, su_k, sl_k, hu_k = HS_device["hd"][ik], HS_device["sd"][ik],\ + HS_device["hl"][ik], HS_device["su"][ik], \ + HS_device["sl"][ik], HS_device["hu"][ik] if V.shape == torch.Size([]): allorb = sum([hd_k[i].shape[0] for i in range(len(hd_k))]) @@ -641,7 +726,7 @@ def get_hs_device(self, kpoint=[0,0,0], V=None, block_tridiagonal=False, only_su return hd_k , sd_k, hl_k , su_k, sl_k, hu_k else: - HD_k, SD_k = f["HD"][ik], f["SD"][ik] + HD_k, SD_k = HS_device["HD"][ik], HS_device["SD"][ik] return HD_k - V*SD_k, SD_k, [], [], [], [] def get_hs_lead(self, kpoint, tab, v): @@ -664,14 +749,37 @@ def get_hs_lead(self, kpoint, tab, v): if self.saved_HS_path is None: self.saved_HS_path = self.results_path - HS_lead_path = os.path.join(self.saved_HS_path, "HS_{0}.pth".format(tab)) - if not os.path.exists(HS_lead_path): - log.error(msg="The HS_{0}.pth does not exist in the saved path {1}.".format(tab, self.saved_HS_path)) - raise FileNotFoundError - f = torch.load(HS_lead_path) - kpoints = f["kpoints"] - kpoints_bloch = f["kpoints_bloch"] - bloch_factor = f["bloch_factor"] + HS_lead_path_pth = os.path.join(self.saved_HS_path, "HS_{0}.pth".format(tab)) + HS_lead_path_h5 = os.path.join(self.saved_HS_path, "HS_{0}.h5".format(tab)) + + HS_leads = {} + if os.path.exists(HS_lead_path_h5): + log.info(msg="The HS_{0}.h5 exists in the saved path {1}.".format(tab, self.saved_HS_path)) + HS_lead_path = HS_lead_path_h5 + with h5py.File(HS_lead_path, "r") as f: + for key in f.keys(): + dataset = f[key] + if key == "useBloch": + HS_leads[key] = bool(dataset[()]) + elif key == "kpoints": + HS_leads[key] = dataset[()] + elif key in ["kpoints_bloch", "bloch_factor"]: + if dataset[()].decode() == "None": + HS_leads[key] = None + else: + HS_leads[key] = dataset[()] + else: + HS_leads[key] = torch.tensor(dataset[()]) + elif os.path.exists(HS_lead_path_pth): + log.info(msg="The HS_{0}.pth exists in the saved path {1}.".format(tab, self.saved_HS_path)) + HS_leads = torch.load(HS_lead_path_pth) + else: + log.error(msg="The HS_{0}.pth or HS_{0}.h5 does not exist in the saved path {1}.".format(tab, self.saved_HS_path)) + raise ValueError + + kpoints = HS_leads["kpoints"] + kpoints_bloch = HS_leads["kpoints_bloch"] + bloch_factor = HS_leads["bloch_factor"] if kpoints_bloch is None: ik = None @@ -681,9 +789,9 @@ def get_hs_lead(self, kpoint, tab, v): break assert ik is not None - assert len(kpoints) == f['HL'].shape[0], "The number of kpoints in the lead Hamiltonian file does not match the number of kpoints." - hL, hLL, sL, sLL = f["HL"][ik], f["HLL"][ik],f["SL"][ik], f["SLL"][ik] - hDL,sDL = f["HDL"][ik], f["SDL"][ik] + assert len(kpoints) == HS_leads['HL'].shape[0], "The number of kpoints in the lead Hamiltonian file does not match the number of kpoints." + hL, hLL, sL, sLL = HS_leads["HL"][ik], HS_leads["HLL"][ik],HS_leads["SL"][ik], HS_leads["SLL"][ik] + hDL,sDL = HS_leads["HDL"][ik], HS_leads["SDL"][ik] else: multi_k_num = int(bloch_factor[0]*bloch_factor[1]) @@ -694,10 +802,11 @@ def get_hs_lead(self, kpoint, tab, v): ik = int(i/multi_k_num) break assert ik is not None - assert len(kpoints_bloch) == f['HL'].shape[0], "The number of kpoints in the lead Hamiltonian file does not match the number of kpoints." - assert len(kpoints) == f['HDL'].shape[0], "The number of kpoints in the lead Hamiltonian file does not match the number of kpoints." - hL, hLL, sL, sLL = f["HL"][ik_bloch], f["HLL"][ik_bloch],f["SL"][ik_bloch], f["SLL"][ik_bloch] - hDL,sDL = f["HDL"][ik], f["SDL"][ik] + assert len(kpoints_bloch) == HS_leads['HL'].shape[0], "The number of kpoints in the lead Hamiltonian file does not match the number of kpoints." + assert len(kpoints) == HS_leads['HDL'].shape[0], "The number of kpoints in the lead Hamiltonian file does not match the number of kpoints." + hL, hLL, sL, sLL = HS_leads["HL"][ik_bloch], HS_leads["HLL"][ik_bloch],\ + HS_leads["SL"][ik_bloch], HS_leads["SLL"][ik_bloch] + hDL,sDL = HS_leads["HDL"][ik], HS_leads["SDL"][ik] return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL diff --git a/dptb/tests/test_negf_run.py b/dptb/tests/test_negf_run.py index 7ae647a8..4b87578a 100644 --- a/dptb/tests/test_negf_run.py +++ b/dptb/tests/test_negf_run.py @@ -2,6 +2,7 @@ import pytest import torch import numpy as np +import os @pytest.fixture(scope='session', autouse=True) @@ -22,11 +23,18 @@ def test_negf_run_chain(root_directory): run(INPUT=INPUT_file,init_model=checkfile,structure=structure,output=output,\ log_level=5,log_path=output+"/output.log") - negf_results = torch.load(output+"/results/negf.out.pth") + + negf_out_path = output+"/results/negf.out.pth" + assert os.path.exists(negf_out_path), "NEGF calculation output file not found" + negf_results = torch.load(negf_out_path) trans = negf_results['T_avg'] assert(abs(trans[int(len(trans)/2)]-1)<1e-5) #compare with calculated transmission at efermi + if os.path.exists(output+"/results"): + os.system("rm -r "+output+"/results") + + def test_negf_run_orth(root_directory): INPUT_file = root_directory +"/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json" @@ -37,8 +45,11 @@ def test_negf_run_orth(root_directory): run(INPUT=INPUT_file,init_model=checkfile,output=output,run_sk=True,structure=structure,\ log_level=5,log_path=output+"/test.log",use_correction=False) - negf_results = torch.load(output+"/results/negf.out.pth") + negf_out_path = output+"/results/negf.out.pth" + assert os.path.exists(negf_out_path), "NEGF calculation output file not found" + negf_results = torch.load(negf_out_path) + k_standard = np.array([[0. , 0. , 0.], [0. , 0.33333333, 0.]]) k = negf_results['k'] assert(abs(k-k_standard).max()<1e-5) #compare with calculated kpoints @@ -88,6 +99,11 @@ def test_negf_run_orth(root_directory): T_avg_standard = torch.tensor(T_avg_standard) assert abs(T_avg-T_avg_standard).max()<1e-4 #compare with calculated transmission at efermi + if os.path.exists(output+"/results"): + os.system("rm -r "+output+"/results") + + + def test_negf_run_S(root_directory): INPUT_file = root_directory +"/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json" output = root_directory +"/dptb/tests/data/test_negf/test_negf_run/out_negf_graphene" @@ -97,7 +113,9 @@ def test_negf_run_S(root_directory): run(INPUT=INPUT_file,init_model=checkfile,output=output,run_sk=True,structure=structure,\ log_level=5,log_path=output+"/test.log",use_correction=False) - negf_results = torch.load(output+"/results/negf.out.pth") + negf_out_path = output+"/results/negf.out.pth" + assert os.path.exists(negf_out_path), "NEGF calculation output file not found" + negf_results = torch.load(negf_out_path) k_standard = np.array([[0. , 0. , 0.], [0. , 0.33333333, 0.]]) k = negf_results['k'] @@ -146,4 +164,7 @@ def test_negf_run_S(root_directory): 1.2010e-16, 1.6627e-17, 2.8171e-18, 5.5635e-19, 1.2401e-19, 3.0499e-20, 8.1390e-21, 2.3272e-21] T_avg_standard = torch.tensor(T_avg_standard) - assert abs(T_avg-T_avg_standard).max()<1e-4 #compare with calculated transmission at efermi \ No newline at end of file + assert abs(T_avg-T_avg_standard).max()<1e-4 #compare with calculated transmission at efermi + + # if os.path.exists(output+"/results"): + # os.system("rm -r "+output+"/results") \ No newline at end of file From e8e13f55a6953db6be4729a58a21d3de20e0731c Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 24 Mar 2025 21:30:12 +0800 Subject: [PATCH 204/209] add natsorted --- dptb/negf/negf_utils.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dptb/negf/negf_utils.py b/dptb/negf/negf_utils.py index be233089..4d68cf98 100644 --- a/dptb/negf/negf_utils.py +++ b/dptb/negf/negf_utils.py @@ -125,6 +125,14 @@ def leggauss(fcn, xl, xu, params, n=100, **unused): res += wlg[i] * fcn(xs[i], *params) return res +def natsorted(lst): + '''sorts a list of strings in a natural order.''' + def sort_key(s): + return [int(text) if text.isdigit() else text for text in re.split(r'(\d+)', s)] + + return sorted(lst, key=sort_key) + + def gauss_xw(xl, xu, n=100): '''calculates the Gauss-Legendre quadrature points and weights for numerical integration. From 2adef6c2565a3a4ad00a7212b1e8d48789eb4c12 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 24 Mar 2025 21:32:38 +0800 Subject: [PATCH 205/209] enable h5py format --- dptb/negf/negf_hamiltonian_init.py | 93 ++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 23 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index eb4886e4..c0afb568 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -27,8 +27,11 @@ from dptb.negf.bloch import Bloch from dptb.negf.sort_btd import sort_lexico, sort_projection, sort_capacitance from dptb.negf.split_btd import show_blocks,split_into_subblocks,split_into_subblocks_optimized +from dptb.negf.negf_utils import natsorted from scipy.spatial import KDTree import h5py +import re + ''' a Hamiltonian object that initializes and manipulates device and lead Hamiltonians for NEGF ''' @@ -93,6 +96,17 @@ def __init__(self, else: raise ValueError('structure must be ase.Atoms or str') + # check the structure cell is larger than the range of device and leads + # In DeePTB-NEGF, the whole structure should be completely included in the cell + # for correct prediction of Hamiltonian and overlap matrix. + # TODO: Add support for non-ortho cell + xrange,yrange,zrange = self.structase.positions[:,0].max()-self.structase.positions[:,0].min(),\ + self.structase.positions[:,1].max()-self.structase.positions[:,1].min(),\ + self.structase.positions[:,2].max()-self.structase.positions[:,2].min() + if xrange > self.structase.cell[0][0] or yrange > self.structase.cell[1][1] or zrange > self.structase.cell[2][2]: + log.error(msg="The structure cell is smaller than the range of device and leads.") + raise ValueError + self.unit = unit self.stru_options = stru_options self.pbc_negf = pbc_negf @@ -216,6 +230,9 @@ def Hamiltonian_initialized(self,kpoints:List[List[float]],useBloch:bool,bloch_f block_tridiagnal:bool,lead_atom_range:dict,structure_leads:Atoms,structure_leads_fold:Atoms): '''This function initializes the Hamiltonian for a device with leads, handling various calculations and checks along the way. + + Note that the structure cell range should be larger than the device+leads range for correct + prediction of Hamiltonian and overlap matrix. Parameters ---------- @@ -394,7 +411,8 @@ def Hamiltonian_initialized(self,kpoints:List[List[float]],useBloch:bool,bloch_f "useBloch":useBloch }) - lead_file = os.path.join(self.results_path, "HS_"+kk+".h5") + lead_file = os.path.join(self.results_path, "HS_"+kk+".h5") + numpy_dtype = np.float32 if torch.get_default_dtype() == torch.float32 else np.float64 with h5py.File(lead_file, "w") as f: for key, items in HS_leads.items(): @@ -408,7 +426,9 @@ def Hamiltonian_initialized(self,kpoints:List[List[float]],useBloch:bool,bloch_f else: f.create_dataset(f"{key}", data=np.array("None", dtype=h5py.string_dtype())) elif key in ["HL", "SL", "HDL", "SDL", "HLL", "SLL"]: - f.create_dataset(f"{key}", data=items.numpy()) + # TODO: HDL,SDL can be saved in reduced form by eliminating zero rows + f.create_dataset(f"{key}_real", data=items.real.numpy().astype(numpy_dtype)) + f.create_dataset(f"{key}_imag", data=items.imag.numpy().astype(numpy_dtype)) else: raise ValueError(f"Unsupported key {key} in HS_leads") @@ -450,7 +470,9 @@ def Hamiltonian_initialized(self,kpoints:List[List[float]],useBloch:bool,bloch_f assert isinstance(item, list), f"Unsupported type {type(item)} for {key}" sub_block_len = len(item) for idx_block in range(sub_block_len): - group.create_dataset(f"{key}_k{idx_k}_b{idx_block}", data=item[idx_block].numpy()) + # group.create_dataset(f"{key}_k{idx_k}_b{idx_block}", data=item[idx_block].numpy()) + group.create_dataset(f"{key}_k{idx_k}_b{idx_block}_real", data=item[idx_block].real.numpy()) + group.create_dataset(f"{key}_k{idx_k}_b{idx_block}_imag", data=item[idx_block].imag.numpy()) else: assert key in ["HD", "SD", "Hall", "Sall"], f"Unsupported key {key} for not block_tridiagnal case" f.create_dataset(f"{key}", data=items.numpy()) @@ -641,9 +663,13 @@ def get_hs_device(self, kpoint=[0,0,0], V=None, block_tridiagonal=False, only_su HS_device_path_pth = os.path.join(self.saved_HS_path, "HS_device.pth") HS_device_path_h5 = os.path.join(self.saved_HS_path, "HS_device.h5") + torch_dtype = torch.float32 if torch.get_default_dtype() == torch.float32 else torch.float64 + #in this version, we only support complex128 for complex dtype to ensure the accuracy. + #TODO: check other complex dtype + complex_dtype = torch.complex128 if torch_dtype == torch.float32 else torch.complex128 if os.path.exists(HS_device_path_h5): - log.info(msg="The HS_device.h5 exists in the saved path {}.".format(self.saved_HS_path)) + # log.info(msg="The HS_device.h5 exists in the saved path {}.".format(self.saved_HS_path)) HS_device_path = HS_device_path_h5 HS_device = {} with h5py.File(HS_device_path, "r") as f: @@ -660,25 +686,33 @@ def get_hs_device(self, kpoint=[0,0,0], V=None, block_tridiagonal=False, only_su else: group = f[key] items = [];sublist = [] - sub_keys = sorted(group.keys()) # ensure the order of subblocks + sub_keys = natsorted(group.keys()) # ensure the order of subblocks current_idx_k = -1 - for sub_key in sub_keys: # sub_key format: f"{key}_k{idx_k}_b{idx_block}" - parts = sub_key.split("_k") - if len(parts) < 2: - raise ValueError(f"Unexpected dataset format: {sub_key}") - idx_k, idx_block = map(int, parts[1].split("_b")) - if idx_k != current_idx_k: - if sublist: - items.append(sublist) # store the previous sublist - sublist = [] # begin a new sublist - current_idx_k = idx_k - sublist.append(torch.tensor(group[sub_key][()])) + for sub_key in sub_keys: # sub_key format: f"{key}_k{idx_k}_b{idx_block}_real" + sub_key_type = sub_key.split("_")[-1] + if sub_key_type == "real": + parts = sub_key.split("_k") + if len(parts) < 2: + raise ValueError(f"Unexpected dataset format: {sub_key}") + match = re.search(r'_k(\d+)_', sub_key) + if not match: + raise ValueError(f"Unexpected dataset format: {sub_key}") + idx_k = int(match.group(1)) + if idx_k != current_idx_k: + if sublist: + items.append(sublist) # store the previous sublist + sublist = [] # begin a new sublist + current_idx_k = idx_k + real_part = torch.tensor(group[sub_key][()], dtype=torch_dtype) + imag_part = torch.tensor(group[re.sub(r"(_real|_imag)$", "", sub_key)+"_imag"][()], dtype=torch_dtype) + sublist.append(torch.complex(real_part, imag_part).to(complex_dtype)) + # sublist.append(torch.tensor(group[sub_key][()])) if sublist: items.append(sublist) # store the last sublist HS_device[key] = items # idx_k, idx_block elif os.path.exists(HS_device_path_pth): - log.info(msg="The HS_device.pth exists in the saved path {}.".format(self.saved_HS_path)) + # log.info(msg="The HS_device.pth exists in the saved path {}.".format(self.saved_HS_path)) HS_device_path = HS_device_path_pth HS_device = torch.load(HS_device_path) @@ -752,9 +786,13 @@ def get_hs_lead(self, kpoint, tab, v): HS_lead_path_pth = os.path.join(self.saved_HS_path, "HS_{0}.pth".format(tab)) HS_lead_path_h5 = os.path.join(self.saved_HS_path, "HS_{0}.h5".format(tab)) + torch_dtype = torch.float32 if torch.get_default_dtype() == torch.float32 else torch.float64 + #in this version, we only support complex128 for complex dtype to ensure the accuracy. + complex_dtype = torch.complex128 if torch_dtype == torch.float32 else torch.complex128 + HS_leads = {} if os.path.exists(HS_lead_path_h5): - log.info(msg="The HS_{0}.h5 exists in the saved path {1}.".format(tab, self.saved_HS_path)) + # log.info(msg="The HS_{0}.h5 exists in the saved path {1}.".format(tab, self.saved_HS_path)) HS_lead_path = HS_lead_path_h5 with h5py.File(HS_lead_path, "r") as f: for key in f.keys(): @@ -764,14 +802,23 @@ def get_hs_lead(self, kpoint, tab, v): elif key == "kpoints": HS_leads[key] = dataset[()] elif key in ["kpoints_bloch", "bloch_factor"]: - if dataset[()].decode() == "None": - HS_leads[key] = None - else: + + if isinstance(dataset[()], np.ndarray): HS_leads[key] = dataset[()] + else: + try : + assert dataset[()].decode() == "None" + HS_leads[key] = None + except: + raise ValueError(f"Unsupported value {dataset[()]} for key {key}") else: - HS_leads[key] = torch.tensor(dataset[()]) + HSkey = key.split("_") + if HSkey[1] == "real": + HS_leads[HSkey[0]] = torch.tensor(dataset[()], dtype=torch_dtype) \ + + 1j * torch.tensor(f[HSkey[0]+"_imag"][()], dtype=torch_dtype) + HS_leads[HSkey[0]] = HS_leads[HSkey[0]].to(complex_dtype) elif os.path.exists(HS_lead_path_pth): - log.info(msg="The HS_{0}.pth exists in the saved path {1}.".format(tab, self.saved_HS_path)) + # log.info(msg="The HS_{0}.pth exists in the saved path {1}.".format(tab, self.saved_HS_path)) HS_leads = torch.load(HS_lead_path_pth) else: log.error(msg="The HS_{0}.pth or HS_{0}.h5 does not exist in the saved path {1}.".format(tab, self.saved_HS_path)) From 93df255a948a1bc07b805268a7922299c4f8c01d Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Mon, 24 Mar 2025 21:35:22 +0800 Subject: [PATCH 206/209] fix: Fix self_energy dimension check comparing with the first and last BTD block --- dptb/negf/device_property.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index c2c286a5..3e11a591 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -216,12 +216,16 @@ def cal_green_function(self, energy, kpoint, eta_device=0., block_tridiagonal=Tr idx0, idy0 = min(s01, se01), min(s02, se02) # The minimum of the two shapes idx1, idy1 = min(s11, se11), min(s12, se12) if block_tridiagonal: - if se01 != s01 or se02 != s02: - log.warning("The shape of left self-energy is not equal to the first Hamiltonian block.") - log.warning("This shouldn't happen in block tridiagonal format.") - if se11 != s11 or se12 != s12: - log.warning("The shape of right self-energy is not equal to the last Hamiltonian block.") - log.warning("This shouldn't happen in block tridiagonal format.") + # Based on the block tridiagonal algorithm, the shape of the self-energy should be + # equal to or larger than the corresponding Hamiltonian block + if se01 > s01 or se02 > s02: + log.warning(f"The shape of left self-energy ({se01},{se02}) is larger than\ + the first Hamiltonian block ({s01},{s02}).") + raise ValueError("Left Lead Self Energy size is larger than the first Hamiltonian Block.") + if se11 > s11 or se12 > s12: + log.warning(f"The shape of right self-energy ({se11},{se12}) is different from\ + the last Hamiltonian block ({s11},{s12}).") + raise ValueError("Right Lead Self Energy size is larger than the last Hamiltonian Block.") green_funcs = {} From 668d4218c6b01658703cae2452a6534cc67e3c1f Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 2 May 2025 18:50:51 +0800 Subject: [PATCH 207/209] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E4=B8=BA=E6=A8=A1=E5=9E=8B=E7=9A=84=E6=95=B0=E6=8D=AE=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dptb/negf/negf_hamiltonian_init.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index c0afb568..9aaf5eb9 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -77,7 +77,9 @@ def __init__(self, ) -> None: # TODO: add dtype and device setting to the model - torch.set_default_dtype(torch.float64) + # torch.set_default_dtype(torch.float64) + + torch.set_default_dtype(model.dtype) if isinstance(torch_device, str): torch_device = torch.device(torch_device) @@ -221,7 +223,7 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor log.info(msg=f"The Hamiltonian has been initialized by model.") log.info(msg="=="*40) - torch.set_default_dtype(torch.float32) + # torch.set_default_dtype(torch.float32) return structure_device, structure_leads, structure_leads_fold, \ bloch_sorted_indices, bloch_R_lists From b72a5022e8b876deb7e0b3aaa6dc7e6763214ac2 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 2 May 2025 19:04:11 +0800 Subject: [PATCH 208/209] =?UTF-8?q?fix:=20=E7=A7=BB=E9=99=A4=E6=9C=AA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9A=84Density2Potential=E5=92=8CgetImg?= =?UTF-8?q?=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dptb/negf/negf_hamiltonian_init.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index 9aaf5eb9..abf1daab 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -7,7 +7,6 @@ from dptb.negf.ozaki_res_cal import ozaki_residues from dptb.negf.areshkin_pole_sum import pole_maker from ase.io import read,write -from dptb.negf.poisson import Density2Potential, getImg from dptb.negf.scf_method import SCFMethod import logging import os From cbe2e4f5c6d524176502f98f115cbae8ee09ee92 Mon Sep 17 00:00:00 2001 From: AsymmetryChou <181240085@smail.nju.edu.cn> Date: Fri, 2 May 2025 19:15:15 +0800 Subject: [PATCH 209/209] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3NEGF=E7=B1=BB?= =?UTF-8?q?=E4=B8=AD=E8=BF=9E=E6=8E=A5=E5=8E=9F=E5=AD=90=E6=8E=A9=E7=A0=81?= =?UTF-8?q?=E7=9A=84=E5=B8=83=E5=B0=94=E5=80=BC=E8=BD=AC=E6=8D=A2=EF=BC=8C?= =?UTF-8?q?=E7=A1=AE=E4=BF=9D=E6=AD=A3=E7=A1=AE=E5=A4=84=E7=90=86=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dptb/negf/device_property.py | 2 +- dptb/negf/negf_hamiltonian_init.py | 6 +++--- dptb/postprocess/NEGF.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dptb/negf/device_property.py b/dptb/negf/device_property.py index 3e11a591..330a7965 100644 --- a/dptb/negf/device_property.py +++ b/dptb/negf/device_property.py @@ -310,7 +310,7 @@ def _cal_current_nscf_(self, energy_grid, tc): cc = [] for dv in vv * 0.5: - I = simpson((f(energy_grid+self.mu, self.lead_L.efermi-vm+dv) - f(energy_grid+self.mu, self.lead_R.efermi-vm-dv)) * tc, energy_grid) + I = simpson(y=(f(energy_grid+self.mu, self.lead_L.efermi-vm+dv) - f(energy_grid+self.mu, self.lead_R.efermi-vm-dv)) * tc, x=energy_grid) cc.append(I) return vv, cc diff --git a/dptb/negf/negf_hamiltonian_init.py b/dptb/negf/negf_hamiltonian_init.py index abf1daab..21c7b7a3 100644 --- a/dptb/negf/negf_hamiltonian_init.py +++ b/dptb/negf/negf_hamiltonian_init.py @@ -76,9 +76,9 @@ def __init__(self, ) -> None: # TODO: add dtype and device setting to the model - # torch.set_default_dtype(torch.float64) + torch.set_default_dtype(torch.float64) - torch.set_default_dtype(model.dtype) + # torch.set_default_dtype(model.dtype) if isinstance(torch_device, str): torch_device = torch.device(torch_device) @@ -222,7 +222,7 @@ def initialize(self, kpoints, block_tridiagnal=False,useBloch=False,bloch_factor log.info(msg=f"The Hamiltonian has been initialized by model.") log.info(msg="=="*40) - # torch.set_default_dtype(torch.float32) + torch.set_default_dtype(torch.float32) return structure_device, structure_leads, structure_leads_fold, \ bloch_sorted_indices, bloch_R_lists diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index ad1986f5..6d4e2c35 100644 --- a/dptb/postprocess/NEGF.py +++ b/dptb/postprocess/NEGF.py @@ -177,9 +177,9 @@ def __init__(self, left_connected_atom_mask = abs(struct_device.positions[:,2]-min(struct_device.positions[:,2]))<1e-6 right_connected_atom_mask = abs(struct_device.positions[:,2]-max(struct_device.positions[:,2]))<1e-6 - self.left_connected_orb_mask = torch.tensor( [p for p, norb in zip(left_connected_atom_mask, self.device_atom_norbs) \ + self.left_connected_orb_mask = torch.tensor( [bool(p) for p, norb in zip(left_connected_atom_mask, self.device_atom_norbs) \ for _ in range(norb)],dtype=torch.bool) - self.right_connected_orb_mask = torch.tensor( [p for p, norb in zip(right_connected_atom_mask, self.device_atom_norbs) \ + self.right_connected_orb_mask = torch.tensor( [bool(p) for p, norb in zip(right_connected_atom_mask, self.device_atom_norbs) \ for _ in range(norb)],dtype=torch.bool)