diff --git a/.gitignore b/.gitignore index 31ced741..06abc039 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,10 @@ 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/negf/check.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 +176,6 @@ dmypy.json _date.py _version.py /.idea/ + + + diff --git a/dptb/entrypoints/run.py b/dptb/entrypoints/run.py index 61480aa7..5019178e 100644 --- a/dptb/entrypoints/run.py +++ b/dptb/entrypoints/run.py @@ -9,10 +9,15 @@ 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 +from dptb.postprocess.tbtrans_init import TBTransInputSet,sisl_installed + +# from dptb.postprocess.write_ham import write_ham from dptb.postprocess.write_block import write_block import torch import h5py + log = logging.getLogger(__name__) def run( @@ -87,6 +92,48 @@ def run( emax=jdata["task_options"].get("emax", None)) 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() + + 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.') + + # 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): + 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, + 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.') + elif task=='write_block': task = torch.load(init_model, map_location="cpu")["task"] block = write_block(data=struct_file, AtomicData_options=jdata['AtomicData_options'], model=model, device=jdata["device"]) @@ -95,4 +142,4 @@ def run( default_group = fid.create_group("0") for key_str, value in block.items(): default_group[key_str] = value.detach().cpu().numpy() - log.info(msg='write block successfully completed.') \ No newline at end of file + log.info(msg='write block successfully completed.') 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..49a4e0ce 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,block_tridiagonal=False): '''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=block_tridiagonal, + 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=block_tridiagonal, 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=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 @@ -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..b2c9d534 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 +import ase log = logging.getLogger(__name__) @@ -63,7 +66,10 @@ 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: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 self.tab = tab @@ -74,8 +80,22 @@ 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 + + + 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"): + 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 @@ -91,33 +111,86 @@ 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"): + #TODO: check here whether it is necessary to calculate the self energy every time + + + 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 + ) + + # 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: + self.kpoint = torch.tensor(kpoint) + self.voltage_old = self.voltage + + bloch_unfolder = Bloch(self.bloch_factor) + 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_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 + 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_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) + - 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): - 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) - - - self.se, _ = selfEnergy( - ee=energy, - hL=self.HL, - hLL=self.HLL, - sL=self.SL, - sLL=self.SLL, - hDL=self.HDL, - sDL=self.SDL, - chemiPot=self.mu, - etaLead=eta_lead, - method=method - ) + # 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. @@ -132,10 +205,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..dd1cc00b 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,18 @@ 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 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 +from scipy.spatial import KDTree ''' a Hamiltonian object that initializes and manipulates device and lead Hamiltonians for NEGF ''' @@ -48,19 +60,76 @@ 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 + log.info(msg="The AtomicData_options is {}".format(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": @@ -71,7 +140,7 @@ def __init__(self, apiH, structase, stru_options, results_path) -> None: 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 @@ -81,6 +150,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. @@ -92,82 +163,346 @@ 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) + self.structase.pbc[2] = True + 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 + - if not block_tridiagnal: - HS_device.update({"HD":HD.cdouble()*self.h_factor, "SD":SD.cdouble()}) + 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 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 = {};structure_leads_fold = {} + bloch_sorted_indices={};coupling_width = {};bloch_R_lists = {} 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"]) + if useBloch: + bloch_unfolder = Bloch(bloch_factor) + k_unfolds_list = [] + for kp in kpoints: + k_unfolds_list.append(bloch_unfolder.unfold_points(kp)) + 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 + 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]][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],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) + 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: + 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) + + 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 + 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 + rmse_l_HK = 0 + rmse_l_SK = 0 - 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 + # 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 + # 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 <= 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: + 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({ - "HLL":HLL.cdouble()*self.h_factor, - "SLL":SLL.cdouble()} - ) - - HS_leads["kpoints"] = kpoints - + "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() + }) + 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, structure_leads_fold, bloch_sorted_indices, bloch_R_lists,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,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] + + 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 + + # 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()) + 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], + [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] 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 +523,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 @@ -225,20 +575,36 @@ 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"] + kpoints_bloch = f["kpoints_bloch"] + bloch_factor = f["bloch_factor"] - 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 kpoints_bloch is None: + ik = None + for i, k in enumerate(kpoints): + if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: + ik = i + break - hL, hLL, hDL, sL, sLL, sDL = f["HL"][ix], f["HLL"][ix], f["HDL"][ix], \ - f["SL"][ix], f["SLL"][ix], f["SDL"][ix] + 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] - - return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL + else: + multi_k_num = int(bloch_factor[0]*bloch_factor[1]) + ik = None; ik_bloch = None + for i, k in enumerate(kpoints_bloch): + if np.abs(np.array(k) - np.array(kpoint)).sum() < 1e-8: + ik_bloch = i + 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] + + return hL-v*sL, hLL-v*sLL, hDL, sL, sLL, sDL def attach_potential(): pass @@ -251,8 +617,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..3ed6f51a --- /dev/null +++ b/dptb/negf/try_bloch.ipynb @@ -0,0 +1,941 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "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": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Atoms(symbols='Au36', pbc=True, cell=[8.8188394545, 8.8188394545, 16.628864960000016])\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": 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", + "positions = structure.get_positions()\n", + "atomic_numbers = structure.get_atomic_numbers()\n", + "\n", + "bloch_factor = [3,3,1]\n", + "# Create a new cell with the z-direction fixed\n", + "fixed_z_cell = np.array([\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", + "new_positions = []\n", + "new_atomic_numbers = []\n", + "\n", + "delta = 1e-4\n", + "for ip,pos in enumerate(positions):\n", + " if pos[0]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "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": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "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": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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mjXasGGDevToEbW/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": [ + "
" + ] + }, + "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" + ] + }, + { + "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": 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": [ + "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": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "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": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor(0)" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "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": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAGxCAYAAACeKZf2AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAABxvklEQVR4nO3dd3hUZdo/8O+ZkknvpJHQe+9Nd0FFiizFXbEvguir+wMbrgXXV9fC4r6ubS2g6yqWZUFxxbKKIgoiRTpSBKlJIL1O6sxk5vz+mDlnZpJJMpOcycyZfD/XNRfJ5MzMEwLJnfu+n/sRRFEUQURERBQiNIFeABEREZGSGNwQERFRSGFwQ0RERCGFwQ0RERGFFAY3REREFFIY3BAREVFIYXBDREREIYXBDREREYUUXaAX0NFsNhvy8vIQExMDQRACvRwiIiLygiiKqKqqQkZGBjSalnMznS64ycvLQ1ZWVqCXQURERG2Qm5uLzMzMFq8JaHCzatUqrFq1CufPnwcADB48GI899hhmzpzp8fo1a9Zg0aJFbvcZDAbU19d7/ZoxMTEA7H85sbGxbVs4ERERdSij0YisrCz553hLAhrcZGZm4plnnkHfvn0hiiLeeecdzJ07FwcPHsTgwYM9PiY2NhYnT56U3/e1tCRdHxsby+CGiIhIZbz5uR/Q4Gb27Nlu769YsQKrVq3C7t27mw1uBEFAWlpaRyyPiIiIVChodktZrVasW7cONTU1mDhxYrPXVVdXo3v37sjKysLcuXNx7NixFp/XZDLBaDS63YiIiCh0BTy4OXLkCKKjo2EwGHDnnXfi448/xqBBgzxe279/f7z11lv45JNP8P7778Nms2HSpEm4cOFCs8+/cuVKxMXFyTc2ExMREYU2QRRFMZALMJvNyMnJQWVlJTZs2IA333wT27ZtazbAcWWxWDBw4EDccMMNeOqppzxeYzKZYDKZ5PelhqTKykr23BAREamE0WhEXFycVz+/A74VPCwsDH369AEAjB49Gnv37sVLL72E119/vdXH6vV6jBw5EqdPn272GoPBAIPBoNh6iYiIKLgFvCzVmM1mc8u0tMRqteLIkSNIT0/386qIiIhILQKauVm+fDlmzpyJbt26oaqqCmvXrsXWrVvx1VdfAQAWLFiArl27YuXKlQCAJ598EhMmTECfPn1QUVGBZ599FtnZ2bjtttsC+WkQERFREAlocFNUVIQFCxYgPz8fcXFxGDZsGL766itceeWVAICcnBy3Ecvl5eW4/fbbUVBQgISEBIwePRo7d+70qj+HiIiIOoeANxR3NF8akoiIiCg4+PLzO+h6boiIiIjag8ENERERhRQGN0RERBRSGNwQkerVma3oZO2DRNQCBjdEpGrnS2ow8qmv8ejGo4FeChEFCQY3RKRqRy5Wot5iw97zZYFeChEFCQY3RKRqxnoLAKCyzhLglRBRsGBwQ0SqZqxrAMDghoicGNwQkapJQU29xYZ6izXAqyGiYMDghohUTSpLAYCR2RsiAoMbIlI514CGpSkiAhjcEJHKGesb5LcZ3BARwOCGiFSOmRsiaozBDRGpGoMbImqMwQ0RqZprQzGDGyICGNwQkYqJoijPuQGAiloGN0TE4IaIVMzUYIPZapPfZ+aGiAAGN0SkYo3n2nDODREBDG6ISMUaZ2qYuSEigMENEamYazMxwOCGiOwY3BCRark2EwMMbojIjsENEamWlLmJi9ADYHBDRHYMbohItaQG4m6JkQCACgY3RAQGN0SkYlKmJisxAgBgbrCh3mIN5JKIKAgwuCEi1ZIOzUyPi4BGsN/H0hQRMbghItWSylLxEXrEsu+GiBwY3BCRakkNxbERejYVE5GMwQ0RqZa0FTwuQo94Kbjh+VJEnR6DGyJSLSlLExuhY1mKiGQMbohIteSyVLizLMXt4ETE4IaIVMtYx54bImqKwQ0RqZIoivJWcNfMDU8GJyIGN0SkSrVmK6w2EYC9oZiZGyKSMLghIlWSghi9VkC4XsPghohkDG6ISJVcm4kFQUB8JIMbIrJjcENEqiTNuJG2gHMrOBFJGNwQkSrJO6XCdQDg3ArOIX5EnR6DGyJSJdejFwC47ZYSRTFg6yKiwGNwQ0SqVFnnObgxW22ot9gCti4iCryABjerVq3CsGHDEBsbi9jYWEycOBFffvlli4/58MMPMWDAAISHh2Po0KH44osvOmi1RBRM5J6bcHtQE23QQasRALDvhqizC2hwk5mZiWeeeQb79+/Hvn37cPnll2Pu3Lk4duyYx+t37tyJG264AYsXL8bBgwcxb948zJs3D0ePHu3glRNRoDnLUvaeG0EQuB2ciAAEOLiZPXs2rrrqKvTt2xf9+vXDihUrEB0djd27d3u8/qWXXsKMGTPwwAMPYODAgXjqqacwatQovPLKK82+hslkgtFodLsRkfo5G4r18n0MbogICKKeG6vVinXr1qGmpgYTJ070eM2uXbswdepUt/umT5+OXbt2Nfu8K1euRFxcnHzLyspSdN1EFBhS5kYKaABuByciu4AHN0eOHEF0dDQMBgPuvPNOfPzxxxg0aJDHawsKCpCamup2X2pqKgoKCpp9/uXLl6OyslK+5ebmKrp+IgqMxg3FgOt2cHNA1kREwUEX6AX0798fhw4dQmVlJTZs2IBbbrkF27ZtazbA8ZXBYIDBYFDkuYgoeDgbip3fxliWIiIgCIKbsLAw9OnTBwAwevRo7N27Fy+99BJef/31JtempaWhsLDQ7b7CwkKkpaV1yFqJKHg0nnMDAHGO5mKeDE7UuQW8LNWYzWaDyWTy+LGJEydiy5Ytbvdt3ry52R4dIgpdnhqK4yPCADBzQ9TZBTRzs3z5csycORPdunVDVVUV1q5di61bt+Krr74CACxYsABdu3bFypUrAQD33HMPJk+ejOeeew6zZs3CunXrsG/fPrzxxhuB/DSIqIPZbCKqTPayVJyHnhsGN0SdW0CDm6KiIixYsAD5+fmIi4vDsGHD8NVXX+HKK68EAOTk5ECjcSaXJk2ahLVr1+LRRx/FI488gr59+2Ljxo0YMmRIoD4FIgqAKlMDpBMWYthzQ0SNBDS4+ec//9nix7du3drkvvnz52P+/Pl+WhERqYFUkjLoNAjXa+X7O/NW8EJjPZ78/DhuGt8Nk3onB3o5RAEVdD03RESt8dRMDLhsBfdDcCOKYlAfyPnxwYv470/5uOO9/cgtqw30cogCisENEamOp23ggPvJ4Epb8NYeXPHcNtSZrYo/txLyKuoAAFX1Dbh73UFYrDw8lDovBjdEpDqephMDQFyksyylZJbFahOx/VQJzpbUYF92mWLPq6T8ynr57YM5FXh+8y8BXA1RYDG4ISLVMXqYTgwA8Y73LVYRdRblMizVjp1ZALD3fLliz6ukAkdw87tRmQCAVVvPYPup4kAuiShgGNwQkepUephxAwCRYVroNILbNUqoqnc+195zwZ25WXRJD9w4vhsA4L71h1Fc5XluGFEoY3BDRKpjrHf03ES499wIguCX7eBV9c7MzcHccpgbgqufxdxgQ0m1PYhJjwvHY78ZhP6pMSipNmHZB4dgswVvIzSRPzC4ISLV8TSdWOI8PFO54Ma1LFVvseFYXqViz62EQqM9axOm1SAxKgzhei1evnEkwvUabD9Vgnd3nQ/sAok6GIMbIlKd5hqKAf/MunEtSwHA3vPBVZoqcAQ3aXHhEAR7Wa5fagzuvqIvAODbk+y9oc6FwQ0RqY68FdxDcOPvshQQfE3FUr9NWly42/3dEiMBACYFm6uJ1IDBDRGpTktlqfhI5WfdSD0+qbEGAMC+82VB1cdSUGmfcZPeKLgJ09q/xZs584Y6GQY3RKQ6zgnFTU+Q8U/mxv5cE3olIVyvQXmtBWeKqxV7/vZqLnOj1zmCmyBrgCbyNwY3RKQ63jQU+6MslRAZhpFZCQCCqzQlzbhJj3UPbgxaBjfUOTG4ISLVkcpEnhqK/Zm5iQ3XYWzPRADB1VTszNxEuN0fpmNZijqngJ4KTkTkqwarTd6a7amhONYPW8GlzE1MuB4D0mMAAHuCaJifnLlp3HPDspTP6sxWlNWaUWtqQI3ZihpTA2pMDbBYRTTYbLDaRDTYRFhtIqrrG1BZZ3G7Ndjsf9cCBDg2riElJhzDs+IwtGscBqbHup1kT/7B4IaIVMV151JMeEf13EjBjQ6juiVAqxFwsaIOeRV1yIiPaOXR/tVgtaGoquXghodots5qE7F62xm8tOWUX4LBjw5cAADotQL6p8WgZ3I0YsJ1iA3XO/7UITU2HMOz4pHaqLxIvmNwQ0SqIjUTR4Zpodc2razH++FkcKksFROuR5RBh8EZsfjpQiX2ni/D3BFdFXudtiiuNsEmAjqNgKRog9vHpN1SJmZuWlRorMd96w9h55lSAPYAJMqgQ1SYDlEGLSLCdDBoNdBpBWg1AnQa+59RBh3iI/SIi9Aj1vGnFFCKIiBChM0G5JTV4qcLFfjpQiVKa8w4etGIoxeNza4nNdaA4ZnxGJ4VjxGOW5SBP659wb8tIlIVecaNh2ZiwP1kcKW4Zm4AYEz3xKAJbqR+m9TYcGgd52pJWJZq3TfHC/HAhsMor7UgQq/FE3MHY/7oTHkYopJEUcTFijr8dKES+ZX1qKq3oKq+AVX1FhjrGnC+tAa/FFah0GjC18cL8fXxQgCAViNgUHosxvRIwNgeifbsTowBOg/BPdkxuCEiVWlpGzjgXpYSRVGRH1KNg5txPRPw1o5z2BcEO6YKmtkGDrg3FCv1dxEqTA1W/OW/P+OdXdkAgMEZsfj7DSPRu0u0315TEARkJkQiMyGy2WtqzQ04lmfE4dwKHL5QiQPZ5bhYUYcjFytx5GIl3t5xHgCgEYAuMQakxUUgLdaA+Igw1DdYUWe2os5i/zNMp8EN47ph1tB0aDSd62vP4IaIVEUqN3naKeV6f4NNRK3Zqkg63+hSlgKA0d3tO6ZOFlahstYiZ4sCobkZN4CzLCWK9r8PvbZz/YBrTr3Fijve249tv9iPpbjt0p54YEZ/GHSBb/SNDNNhbI9EjO2RKN+XV1GHfdnl2He+DHvOleF0UTUabCIKjSYUGk043MLz7TxTipe/PYV7ruiHmUPSOk2Qw+CGiFSlsoUZNwAQoddCrxVgsYqorLO0O7ix2UTn7ixH5qZLjAG9kqNwtqQG+7LLcMXA1Ha9RnvI04k9NKFKmRvAXpry1KPU2dSaG7B4zT7sOluKCL0Wr940EpcPCNzXzxsZ8RGYEx+BOcMzANj/TZbUmFBYaUJ+ZR0KjPWorLUgIkyLiDAtIsO0iNDrcKLAiH/+cA6/FFZjydoDGJAWg3un9sX0wWkhn8VjcENEquIsS3kObgRBQFyEHiXVZlTUWtq9m6nG3ADRcdJCjEtANbZHIs6W1GDv+XJcMTAVoihi19lSrNlxHjXmBqy+ebTb9f7iTeYGsAc3UYYml3QqVfUW3LpmL/aeL0e0QYe3F411y5CohUYjICUmHCkx4RiaGdfsdTOGpGHRJT3xzx/O4e0fzuFEQRXufP8Abr2kJ/73NwNDOsBhGE9EquJsKG7+dzMlTwaX+m10GgHheue3zDE97JOKd50txYb9FzDr7z/gxn/8iK+PF2LH6VJ883Nhu1/bG84ZN02DOJ1WA6kK0dkH+VXWWnDzP/dg7/lyxITr8N7icaoMbHwVF6HHsiv74YeHLscfpvQGALy14xye/eokRDF4zkdTGoMbIlKV1jI3gHM7uJLBTUy4zu033XGOScWHcyvwxw8P43i+EeF6DXp1iQIAHMiuaPdre0PK3KTHe56Nwh1TQFmNGTe+uRuHcysQH6nHv2+fgJHdEgK9rA4VF6nHQzMG4Km5gwEAr209g5e/PR3gVfkPgxsiUpXWGopdP6bErJuqRs3Ekm6JkeiRZN/1khYbjgdn9Mfu5Vdg2ZX9AAAHcvy/k8pmE1Fo9DzAT9LZTwYvqKzHda/vwrE8I5KiwrDufyZgSNfmSzmh7vcTe+DRWQMBAM9v/gVvfH8mwCvyD/bcEJGqtNZQDCg7pbjxNnCJIAh459ZxOF9ai0m9k+Rm3VGOjMCJgirUmhsQGea/b7MlNSY02ET7tuBozw01YTotgIZOmbnJKa3FTf/cjdyyOqTFhuP928ahT0pMoJcVcLf9qhfqLVb87etf8JcvTsCg0+KWST0CvSxFMXND1AnsPFOCH06VBHoZipAOzWxuzg2gbHDj3Abe9PW6J0Vhcr8ubruQMuIjkB4XDqtNxOHcyna/fkukfpuUmPBmB7oZOmlZ6pfCKlyzeidyy+rQPSkSH945kYGNi6WX98XSy/oAAB7/9Bj+4zgeQgn1Fqtiz9VWDG6IQpy5wYbFa/bh1jV75S3NamYMWObG+51PUvbG36WplnZKSTrjyeCHcytw7eu7UFRlQv/UGHx4x0RkJTY/OK+zun9aPyy+tCcA4KGPflLkMNjiKhMu/9tW/OP7swFtWGZwQxTiymvNqLNYYbbakF9RF+jltJs3DcXyyeB+LEu1ZGS3eADAQT8HN82dBu5KGtzXWTI3208V48Z/7EZFrQXDs+Kx/o4JSOFBlB4JgoA/XTUQM4ekwWIVccd7+5BdWtPm57PaRNyz7iDyKuvx4f7cgJ5pxuCGKMSV1ZjltwsczadqJm0Fb6mhOD4yDIBSmZvWM0WNjeouZW4q/Prbq0+ZmxAPbkRRxJvbz+KWt/agxmzFpN5J+Ndt4+V/C+SZRiPg+WtHYFhmHMpr7XOA2vr/5qUtp7DzTCkiw7R47aZRCNcHbuIzgxuiEFde6wxupB+GamVusKHOUc8PdENxSwZnxCJMq0FZjRnnS2vbvYbmyNOJWwpuOsHJ4PUWK+5bfwhP//dn2ETgmtGZeGvhWETzJG2vRIRp8eaCMUiPC8eZ4hosXXsAFh/LmNtPFePlb08BAP5y9dCA9zcxuCEKcRW1zh/whSoPbqSSFABEtxBsJEbZf1s/V1wtZ17aqqqFhuLmGHRaDOkaCwA4kO2/0pQzc9P8FOZQ77m5WFGHa1bvxMZDedBqBPx59iA8e82wgGYN1CglNhxv3jIGkWFabD9Vgj9/eszrrGNBZT3uXXcIogjcMK4b5o3s6ufVto7BDVGIc8vcqLwsJTUTxxh00LZwAOCwzDj0So6Csb4B//j+bLtesy0NxUDHNBUXtDLjBpC2godmWWp/dhnmvPwDjl40IjEqDO8vHo+Fl/QM6WMF/GlwRhxeun4kBAH41485eN2L/zsNVhvu+vcBlNaYMSg9Fo/PHtQBK20dgxuiEBdamRtpG3jLgYZeq8ED0/sDAP6x/RyK2hHUtaUsBbj33fiDKIrOzE0LDbNSWcrXMkOw23WmFDe/uQelNWYMzojFp0svwcTeSYFelupdOSgVf7rKPuTvmS9P4J2d51u8/m9f/yKf1RXoPhtXDG6IQlx5Tej03MjbwFsJbgD7oYEju8WjzmLFi1tOtf01m5lQ3JrRjuDmZIHRL1vwy2stcjYmtYXgJhTn3PxwqgSL1uxBncWKX/VNxoY7JyEzgVu9lXLbr3rhrsudM3D+vSenyTX1Fiue/eoEVm+zTzj+v2uGoUdyVIeusyUMbohCXLlr5kblZSnndOLWsyiCIGD5TPtvoOv35uJMcXWbXrOtmZvU2HB0jY+ATQR+yq1o02u3JN/RTJwcbZD7ajwJtd1SW08W4dZ39qLeYsNl/bvgHwvGICIsOLIFoWTZlf3wP7/uBQB45OMj+Gi/c8jfgZxy/OblH/Dqd/bA5o5f98JVQ9MDss7mMLghCnEVLj03pTVmmBoCPz20raTPJT7SuyzKuJ6JmDowBVabiGc3nWzTazq3gvu+80aad7PfD03F3sy4AULrbKlvjhfif97dD3ODDVcOSsXq348OmjJIqLH/cjAAt0zsDlEEHthwGB/uy8VTnx/H71btxOmiaiRHG7D65lFY7ihjBRMGN0QhzrWhGACKjKYAraT9Sqrtn0tyM+coefLgjAHQCMCmYwU+BxmiKMolJV/LUoAyTcXfHC/EuBXf4NsThW73ezPjBgD0Ontzrdq3gm8+Xog7398Ps9WGq4am4bWbRsGgY2DjT4Ig4PHZg3HDuCzYROCBDT/hnz+cgygCvxuViW+W/RozhgRXxkbC4IYoxLk2FAPq7rsprbEHZkk+BDf9UmNwzehMAMAzX/7s01C9GrMVNsflvpalAGdT8cHctg/z++ynPBRVmfC/G4+5Zd28z9yof7fUz/lG3P3vg2iwiZgzPAN/v36k23le5D8ajYAV84bit47t3Rlx4Xh70Vg8d+3woB6QyH8dRCFOytwkOEo5ap5SXCpnbnz7pnrflf1g0Gmw93w5vvm5yOvHSSUprUZARBvKH4PSY2HQaVBRa8HZkraNtZeGAF6sqMO/djsbO73N3Ki956asxozb390nNw8/f+3wZg8JJf/QaAT8bf5wfPSHidi8bDIu658S6CW1KqD/QlauXImxY8ciJiYGKSkpmDdvHk6ebLkuvmbNGgiC4HYLD+e5IUSe2Gyi3IQ7IM0+VE6aaqtGUnCTFOV95gYA0uMicKvjgMAV/z3uNgywJa7NxG2ZnRKm02Bo1zgAbR/ml+Ny1s8r352WA64CY+vTiaU1AIDZqr5eqwarDUvXHsCF8jp0S4zEyzeMZGATIBqNgNHdExGlkqnPAf1Xsm3bNixZsgS7d+/G5s2bYbFYMG3aNNTUtPwbTmxsLPLz8+VbdnZ2B62YSF2M9Ra5rDIg3T4OvaBSxT03clnK93T4H6b0RnpcOM6X1uLedYdgtbVeJmrLdOLG2jPvprLOIu92y0qMQFmNGW9uPwfAJXMT2/x0YsC5FdzSELgTmttqxRc/y2cV/WPBmKAug1BwCWhws2nTJixcuBCDBw/G8OHDsWbNGuTk5GD//v0tPk4QBKSlpcm31NTUZq81mUwwGo1uN6LOQvrBGG3QoVuifQ6I9Bu/GrW1LAXYz6J64/djYNBp8O2JIjz3deu7p6ShgTEG35uJJaPacUJ4jqMklRxtkLe1v7n9LEqqTSG/W+rDfbl4e8d5AMDz145A/7TAnlVE6hJU+b3KykoAQGJiYovXVVdXo3v37sjKysLcuXNx7NixZq9duXIl4uLi5FtWVpaiayYKZuUuW6elKbYFKm0oNjfY5BKbr2UpydDMOPzfNcMAAK9tPYPPDue1eH11G2fcuJJ2TJ0srPK6HCY57yhJdU+KxMwhaRiWGYcasxV/+eJn1JrtZaZQ7Lk5mFOOP318FABw9xV9MWNIWoBXRGoTNMGNzWbDvffei0suuQRDhgxp9rr+/fvjrbfewieffIL3338fNpsNkyZNwoULFzxev3z5clRWVsq33Nxcf30KREGnQm4mDpN/CKo1uJECNa1GkE/9bou5I7riDsdwsgc2HMbRi5XNXtvWc6VcpcSGIzMhAqIIPP/1Lz4dg5BTZs/cdE+KhCAIeGjGAADAfw5cBGBvEm9tzosU3KhlK3i9xYp71x+C2WqfZXPvFX0DvSRSoaAJbpYsWYKjR49i3bp1LV43ceJELFiwACNGjMDkyZPxn//8B126dMHrr7/u8XqDwYDY2Fi3G1FnUV5jzxTER+rl4KaoyuRVv0mwKam299skRoVB08Khmd54cMYATO7XBfUWG+54b7/83I21Z4Cfq8WOZuY1O8/j+jd2y9OFW3PescOqe6J9rP0lfZJxaZ9k+eMtnQYu0ausLPXad6eRXVqLtNhwPH/t8HZ/ralzCorgZunSpfj888/x3XffITMz06fH6vV6jBw5EqdPn/bT6ojUq9wlc9Ml2gCNADTYRJQ288M8mDl3SrW/qVSrEfD360eiZ3IULlbU4b71hzxe19ajFxpbdElPrLppFGIMOuzPLsesv/+Abb8Ut/q4bEfmpkey89ykB2f0l99urd8GcC1LBf9uqdNF1VjlOKvoz3MGtStjRp1bQIMbURSxdOlSfPzxx/j222/Rs2dPn5/DarXiyJEjSE8PzimJRIHkOuNGp9WgS4y9V0WNs26kAX6+TCduSVykHqtvHg0A2H6qBHXmpj/8q9p4aKYnM4em4/O7L8XgjFiU1Zix8O09eO7rky0O98t29NxIzeAAMCwzHrMc5/i43t8cuaE4yMtSoiji0Y1HYLGKuHxACqYPZp8NtV1Ag5slS5bg/fffx9q1axETE4OCggIUFBSgrs6Zsl2wYAGWL18uv//kk0/i66+/xtmzZ3HgwAHcfPPNyM7Oxm233RaIT4EoqEm7paQttFJTsRqnFMuZmzbslGpOv9RoRDvmduR5KBUplbmRdE+Kwkd/mIQbx3eDKAIvf3saXx0r8HhtndmKQsdRGT2S3E9bXnH1EDw4oz/unNy71deUTwUP8rLUxwcvYvfZMoTrNXhizuA2zRUikgQ0uFm1ahUqKysxZcoUpKeny7f169fL1+Tk5CA/P19+v7y8HLfffjsGDhyIq666CkajETt37sSgQYMC8SkQBbWKRtOJpb4bNZ4OXtLGAX4tEQQBXePtfSsXy5sGN0YFGoobC9dr8Zerh+L6sfadmz+eK/N4ndRMHBOua3JQaHxkGP7flD6t7pQC1LFbqqLWjBX//RmAfXdUlhcZKaKWBHTUoDdnrWzdutXt/RdeeAEvvPCCn1ZEFFqkhuKEKPfMjRp3TEl9QkpmbgAgIz4cJwurcLHCU+am/UP8mjO2RyLW7c3FsYueZ29JJakeSVHtymJIwY3FGrxN5H/ddBKlNWb0TYnGbZf2CvRyKAQERUMxEfmHc86NI7hx7K5RZXBT0/YBfi3pmmD/O8nzGNwoW5ZyNcRxLMOxvErYPOxeyy51bgNvj2DvudmfXYZ/77GfmbXi6qFyMEbUHvxXRBTCpBPBnWUpFTcUS5kbBctSAJDRQlmqyqRcQ3FjvbtEIVyvQY3ZKg/rc5Vd5hzg1x7BPudm5RcnAADXjsnEuJ4tD3Al8haDG6IQ5roVHHCeQ6TGzE2JHxqKATh7blrI3LR3zo0nOq0GA9Ptc7eOeBgk6MzcRDX5mC+CeSt4Ra0Z+x3HUtx3Zb8Ar4ZCCYMbohBVZ7bKv63HN2ooLjDWe9XzFixEUVR8K7gkM8FzcCOKoiITilsyJEMqTTXtu5GDm3Y21wbz2VI7z5RCFO271tK9GEhI5C0GN0QhSsra6DSCvN1ZaiiuNVvlnUBqUGu2ot5i/+GsfEOxM5vlOrm5zmKV3/dHzw0ADOlqz9w0PgLCYrXJwVaPZKUyN8EX3PxwugSAffIykZIY3BCFKNdmYmm3TUSYVj6XSU3bwaUZNxF6LSLDlA00UmLCodMIaLCJKKpy/p1IWRutRkBkWMvnN7XVYEfm5ujFSrdM2sXyOlhtIsL1GqTEtC9TJWVubCLQEGTZmx2O4OZSBjekMAY3RCGqcTOxRBrZr6ZBfsV+2gYO2IMXqVzn2lQsbQOPNuj8NlCuX2oM9FoBxvoGXHB5bfk08MT2bQMH4Lb7KJhKU7lltcgurYVOI2B8r6RAL4dCDIMbohDVuJlYkuooTRWqKLhxzrhRtt9G4qmp2OjHbeCSMJ0G/dNiALiXpqQBft3auVNKeg1JMJWmpJLUyG7xctmUSCkMbohClPPoBfVnbuQZNwocmumJp+DG383EkqGOeTeuO6bOlzgOzFQguNFpBEjJn2DK3LDfhvyJwQ1RiKqoaTlzo6ZZN/6aTiyRBvl5Kkv5M3MDuPTduOyYynHMuOnWzm3ggP2IiWAb5GezidjJfhvyIwY3RCFKytwkNMp2yNvBPRwUGaycM278W5bK85C58ceMG1fypGKXpuLzpcplboDg2zF1PN+I8loLog06DM+KD/RyKAQxuCEKUeWNDs2UOGfdmDp8TW0llaWS/FSWyvBYlvLfdGJXA9JioNUIKK0xo8BYD5tNlHtuuie2P3MDBN+sG6kkNaFXIvRa/hgi5fFfFVGIaq6h2Hl4ZmAyNxarDYdyK3zaliyVpZQe4CdxLUtJ2RN/nivlKlyvRd+UaADA0YtGFBjrYW6wQacRkBHf+qnf3gi2zM0O9tuQnzG4IQpRrTUUl9daUG/p+JH8/9h+FvNe3YH3d2d7/ZhSPx29IMlwTMetMVthrLMHNR0V3ADu826kycSZCRHQKZTVCKbgpt5ixZ5zZQDYb0P+w+CGKERVSJmbRqWcuAg9DI4fdoEY5HcivwoA8JOH85SaIx29oPShmZKIMK1c8rpQYQ8ujB1UlgKAoS6TirOlGTcKNBNLgqmh+EB2OUwNNqTGGtDHkbEiUhqDG6IQVV7juedGEAQ5exOIAzSlgOpCmXdlMatNRJm0FdxPmRvA2XeTV2FfX0dmbqSm4qN5lciW+m0UaiYGXE4GD4Kem+0uJSl/DUckYnBDFIIarDZ5CF18ZNOAIJDbwYuq7FmY3PJar66vqDVDOvKpcRZKSfKsG8e6OqqhGAAGpsdCEIBCown7z9tPyVY0cxNEZSkeuUAdgcENUQiqrLPIb8dHNP3hHKjMjSiKcuamwFgPU0PrPT/STqn4SL1fd9ZITcV5lR2fuYky6NDLcUDm3mx7P0p7TwN3JZWlLAHO3FTUmuVhhWwmJn9icEMUgqRm4phwncem1NQATSmuNjWg1mwPaETRfWhec0qkAX5+zNoALtvBHWvqqDk3Eqk0JZ2f2SNZ+bJUoDM3u86UQhSBvinRcvaQyB8Y3BCFoIpmtoFL0qXzpTq4LFXYaLZOrhfBTamfB/hJpLLUhQopuOm4shQADHHsmAIAQQAyE5QLbgxBEtxI/TaX9mXWhvyLwQ1RCCpv5kRwSVqAMjdFjYIpaVhdS5wzbvybuXGdUiyKYoeWpQBn5gawB5/heq1iz60PkiF+u8+UAmC/DfkfgxuiECQN8PPUTAwAaY65Lh2eualyf70L3gQ38nRiP2duHD03xVUmGOsa0ODoYu6oE6sHZcTKbyvZTAwER1nK1GDFecc292GZ8QFbB3UODG6IQlBFM0cvSKQpxUVVJlilrUgdoGlZqvXgpsTPA/wkCZF6RDiyJScL7bN4BAGICuuY4CYuQi9v/1ZyGzjgbCg2BTC4yS2rhU20B4v+zsIRMbghCkHO6cSef4h0iTFAqxFgtYn46UJFh61LyhRJw9t8KUv5u+dGEJzHHZwssJ/QHW3QQaPpuFksUkajdxdlh9sFQ+bmXInjMNDkSM63Ib9jcEMUglprKNZqBFw1NB0A8NBHP3m1JVsJRY7MzZjuCQCAXC8G+UllqWQ/75YCgK6OJt6fC+yZm9gOaiaW/HFaP9w3tR+uHZul6PPKwU0Ae27Ol9hLUj0ULrkRecLghigEldc4Goqjmv/h/OfZg5AUFYZfCqvx4jenOmRdUuZmtCO4qayzuM3k8aSjMjcA0NWRuTmRb8/cdFQzsaR7UhTumdoXcR5mE7WHFNxYApm5cfTb9ExmcEP+x+CGKAS11lAM2IOFFVcPBQC8vu0MDuSU+31dUkNxry5R8tya3FZKU/4+NNOVtGPql8JqAB0f3PiLIQh2S/ktc2NtcA4HInJgcEMUgipa2QoumTEkDfNGZMAmAn/88LBfTwm3Tye2Z2FSYsKR6ZjAe6GFpuJ6ixVVJvuW7OQOyNxIg/yqTdI28I4tS/lLMPTcyMGNgsMJAQAH3wWe7QNseUrZ5yVVY3BDFILKWum5cfXEnCFIiTHgbHENnv3qpN/WVFFrkX+4psQa0M0R3LTUdyMdmKnXCh0yKVjK3EhCJXMT6OCm3mKVj7VQPHOTdxCoLQHEwJ+bRcGDwQ1RiBFF0dlQ7EUTblykHn/93TAAwFs7zmHPuTK/rEsqSSVE6mHQaZHlmCvT0o4puSQVZeiQHTbSrBtJqAQ30hC/QJ0Knl1q/xrHhOuQqHRjeN5B+58ZI5V9XlI1BjdEIabGbIXFau9BaK0sJblsQAquHZMJ0VGe8sfuKakkJZ0plCVlblooS5XUSM3EHTMXJTU2HK47v1mWUsa5EmczsaJBqqUeKPrZ/jaDG3LRpl9LKioqsGfPHhQVFcFmc//PsmDBAkUWRkRtU+4o5YTpNPJQOm88+ptB+Pp4IXLKanEsz4hR3RIUXZe0UyrFEdw4y1JeZG46oN8GsGc40mLD5RJKqGRupCF+gQpuskv91ExceAywNQCRSUBcprLPTarm8//czz77DDfddBOqq6sRGxvrFoULgsDghijAXJuJffktOTZcjx5JUThUW4HiKlPrD/CRdK5Uaow9UMlKkDI3dbDZRI/D8uRzpTpgxo0kIz7CJbhh5kYJ0rELPZTeBp53wP5nxkj7OGkiB5/LUvfffz9uvfVWVFdXo6KiAuXl5fKtrMw/tXoi8l65D83EjaU4Ao8iPwQ3jctS6fH2EpC5wYbias+vJ58r1YHj+l37bjqiibkjGAI8xM9ZllJ4p1TeIfufLElRIz4HNxcvXsTdd9+NyEiF/5ESkSKcM258zzqkxNqDm2I/HKgplaVSHa+h12qQ7jjAs7nSVEkHDvCTZLjsmAqZspQ0xC9Awc156egFpctS+YfsfzK4oUZ8Dm6mT5+Offv2+WMtRKQAZ1nK92xHl2h7VqW5TEp7FDqyQVLPDeDsu2lux5Rzt1QHZm7cgpsQKUtp7b1XgShL1ZmtKHAEtopOJzbXOpuJ00co97wUEnz+tWTWrFl44IEHcPz4cQwdOhR6vft//jlz5ii2OCLynTfTiZsjZW6KjH7suXEJbrISI7DrbPOzbkodu6U6YoCfxLUsFWqZm0AEN1K/TXykvk3/JptVeBQQrUBUChCbodzzUkjw+X/u7bffDgB48sknm3xMEARYrR1zAB8ReebtdGJPukT7p+fGZhPl55TKUoBrU3ErmZuO7LkJwcyNXmtvtjUFIrjx17ELrvNt2ExMjfhclrLZbM3efA1sVq5cibFjxyImJgYpKSmYN28eTp5sfULqhx9+iAEDBiA8PBxDhw7FF1984eunQRSy2tVQLPXctBLcHM6twMtbTnndw1FaY4bVJkIQ3LMw3ZKaL0uJotjhW8EBe3Cj0wjQagTEK3yAZaAE8lRwvx2YyWZiakFAh/ht27YNS5Yswe7du7F582ZYLBZMmzYNNTU1zT5m586duOGGG7B48WIcPHgQ8+bNw7x583D06NEOXDlR8Cp3ZG7a1FAcYy8ZlVSbYLM1fxjhiv/+jOc2/4KP9l/w6nmlZuKkKIM8LRcAMh2ZmwsegpsqU4P8w7gje26iDDo8f90I/G3+MEQZQqMsZQhkWcrvmZsRyj4vhYQ2BTfbtm3D7Nmz0adPH/Tp0wdz5szB9u3bfX6eTZs2YeHChRg8eDCGDx+ONWvWICcnB/v372/2MS+99BJmzJiBBx54AAMHDsRTTz2FUaNG4ZVXXmnLp0IUcirakblJig6DIAANNlE+n8qT7DL7D6xvTxR59bxFVe47pSRZifYSUL6xvskPXilrE23QIdyHYYRKmDM8A1ePDJ2hcIFsKJZ3Sim5DdxUDZQ4svxsJiYPfA5u3n//fUydOhWRkZG4++67cffddyMiIgJXXHEF1q5d267FVFZWAgASExObvWbXrl2YOnWq233Tp0/Hrl27PF5vMplgNBrdbkShTC5LRfmeudFrNUh0BEXNlaYsVpvcP7PjdIlXRzU0nnEj6RJtQLheA1EELla4NxWXVnfs0QuhLOTKUgVH7AdlxqQDsenKPS+FDJ+DmxUrVuD//u//sH79ejm4Wb9+PZ555hk89VTbj5y32Wy49957cckll2DIkCHNXldQUIDU1FS3+1JTU1FQUODx+pUrVyIuLk6+ZWVltXmNRGpQUSOVpdoWFHRpZZBfQWU9REfFqsZsxd5z5a0+Z+MZNxJBEJxNxY1KUyUB2AYeqqTgxmoTYW2h3Ki0alODHCQrOp2Y822oFT4HN2fPnsXs2bOb3D9nzhycO3euzQtZsmQJjh49inXr1rX5OTxZvnw5Kisr5Vtubq6iz08UTCxWG6pMDQDaVpYCXIKbZgb55Ve63//dydZLU81lboDmD9C84Hg/MarjmolDlRTcAB07yE/qt0mKCkOskjvPpH4blqSoGT4HN1lZWdiyZUuT+7/55ps2Z0WWLl2Kzz//HN999x0yM1uuc6elpaGwsNDtvsLCQqSlpXm83mAwIDY21u1GFKpO5FcBALQaAXFt3OkjNRU3N8gvv9JePtI5zoL6zou+G08zbiSeBvnVmBrwxvdnAQATejVfpibvhLk0cXfkdnD/nSnlsg2cyAOftwLcf//9uPvuu3Ho0CFMmjQJALBjxw6sWbMGL730kk/PJYoi7rrrLnz88cfYunUrevbs2epjJk6ciC1btuDee++V79u8eTMmTpzo02sThaLnNtubLGcOSYPWw0GU3mhtkF9ehT1QuWxACr47UYSzJTU4X1LT4g+wwmYaigEg0zE074LLIL/Xtp5GUZUJ3ZMi8fuJ3dv0eZCTNOcG6NimYr/slDJVASWn7G9zpxQ1w+fg5g9/+APS0tLw3HPP4YMPPgAADBw4EOvXr8fcuXN9eq4lS5Zg7dq1+OSTTxATEyP3zcTFxSEiwv4Nb8GCBejatStWrlwJALjnnnswefJkPPfcc5g1axbWrVuHffv24Y033vD1UyEKKbvOlGLryWLoNAL+OK1/m59HGuTXWuamX2o0quot2H22DFtPFmFhcvO/nEhlKSkr5KpxWSq3rBb/2G4vcf/pqoEw6Dp2p1QoEgQBYVoNzFZbhzYVn3PslFL0wMz8nwCIQGwmEJ2i3PNSSGnTEIerr74aV199dbtffNWqVQCAKVOmuN3/9ttvY+HChQCAnJwcaDTOlOqkSZOwdu1aPProo3jkkUfQt29fbNy4scUmZKJQJ4ointl0AgBw/bisdpUBnIdntpy5SY+LwOUD9Nh9tgzfnizGwks8BzcNVpt8AKY3Zam/fPEzzA02XNonGVcOSm1yPbVNmM4R3ASgLNVdycwN59uQFwI6oUoUW+/a37p1a5P75s+fj/nz5/thRUTB60SBESkx4Uj0sHvoq2MFOJxbgQi9Fndf0bddr+M8gsFzQ3GeY8t21/gIZCZE4C9fnMDus6WoNTcgMqzpt5SSajNE0d4H5Gnnk5S5qai14OtjBfjyaAG0GgH/+5tBEDhWXzFhOg1gCkxZStFt4AxuyAteNRQnJiaipKQEAJCQkIDExMRmb0SkvA/35WLGi9txxXNbsfd8mdvHGqw2/N9X9l6b237V02PpxxfSqd3NzbmRylLp8eHokxKNzIQImBts2Hm61OP10jbwlBgDNB76gKINOvkcrIf/cwQAcPP4buifFtOuz4PcSU3FHRXcGOstKK2xb+dXtKGYzcTkBa8yNy+88AJiYmLkt/nbFFHH2Xm6BMsdP/TLay246R8/4tn5wzB3RFcAwIf7L+BscQ0SIvX4n1/3avfrpTi2gteYragxNbgdQVBntsrHO6THRUAQBFzWPwXv7c7GdyeLMNVDGUkObjyUpCTdEiNRXluJshoz4iP1uO/Kfu3+PMidc5BfxxxunO3ot0mONiBaqWMs6iuBsjP2t9MZ3FDzvPoXd8stt8hvS70wROR/pwqrcMf7+9FgEzFrWDqsVhGbjhXgnnWHkF1ai9t/1QsvfvMLAGDJZX0UOcU6yqBDZJgWtWYriqpM6Onyg0nK2kSFaREbbr//8gGO4OZEEURRbPLLT6F0GnhM8/NqMhMjcfiCfUL5siv7tXkAITVPCm46aiu4czKxks3Eh+1/xnUDopKUe14KOT7PuTlw4ACOHDkiv//JJ59g3rx5eOSRR2A2N38WDRH5prjKhEVr9qKqvgFjuifgufnD8dpNo3CHIzvz/OZfMOvl7Sg0mtA1PgI3T1Buy7SUvWlcmpIG+KXHR8hBzIReSTDoNMirrMcvhdVNnqulGTeSHo7TwfulRuPGcd3a/wlQE1JZymLtmAnFftkGzn4b8pLPwc0dd9yBX36x/6Z49uxZXHfddYiMjMSHH36IBx98UPEFEnVGdWYrbnt3Hy6U16FHUiTeWDAG4XotNBoBy68aiBVXD4FWI+Bssf0HyH1X9lP0cEmpb6dxU7F0/lN6nDNQiQjTYlJv+2/Rng7SbO7oBVc3je+O68dm4ZUbR0GnbdN5vtSKsA4+GVwObthvQwHg83eRX375BSNGjAAAfPjhh5g8eTLWrl2LNWvW4KOPPlJ6fUSdjs0m4r71h3A4twLxkXq8vWhckx1SN43vjrcWjkViVBgm9ErE1SO7KroG5xEMjTI3jm3gXeMj3O6/bIB93oinoxjkGTctZG4y4iPwzO+GoV8qm4j9paMbiv1yYGbeIfufDG6oFT53eYmiCJvN/p/jm2++wW9+8xsA9mMZpB1VRNR2354owqZjBQjTavCPBWOa/eEwuV8X7P3TVIii2OZpxM2RgpvGg/zknVJxjYKb/ikAjmF/djkqay2Ii3T2/hR6UZYi/+vohmLFy1J15UC54/zC9OHKPCeFLJ8zN2PGjMHTTz+N9957D9u2bcOsWbMAAOfOnWtyWjcR+e6rY/ZJ3TeO74axPVoer6DVCH4p4zR3BEOe3HPjHqhkJUaib0o0rDYRGw5ccPuYdLp4S2Up8r+OLEtV1JrlXXU9lGoolrI2CT2ASI4doZb5/F3xxRdfxIEDB7B06VL86U9/Qp8+fQAAGzZskM+aIqK2sdpEuW9l2uDA/bLQ3CC/fEfPTUajzA0A3DTe3gj8100ncPSifeeTqcGKMsesk9R2zt+h9unIstQ5R9YmPS7c42DHNmG/DfnA5391w4YNc9stJXn22Weh1fIMGKL2OJRbjtIaM2LDda1mbfzJ0yA/URTl6cSNMzcAsGBiD2w/VYItJ4qwdO0BfHbXpaiss//2HqbVID6y/dvUqe06civ4OX9MJs4/ZP+TwQ15wefMTW5uLi5ccKad9+zZg3vvvRfvvvsu9Hp+8yJqj83H7VmbywakQB/AXUOetoIb6xtQY7b3a3jK3Gg0Av42fzgy4sJxvrQWy/9zxGWAn4HDPwPM2XPTccGNX3ZKpY9Q7jkpZPn83fPGG2/Ed999BwAoKCjAlVdeiT179uBPf/oTnnzyScUXSNSZfPNzIQBg6sDA9q9JDcWlNWZYHD8MpWbihEg9IsI8Z2kTosLw8o2joNMI+PynfLy05TQANhMHAym4sTT4f87NWUdw00up4KamFKjIsb/NZmLygs/BzdGjRzFu3DgAwAcffIAhQ4Zg586d+Ne//oU1a9YovT6iTuNcSQ1OF1VDpxEwuX+XgK4lMTJM3oFVWm3vmcl3OQ28JaO7J+ChGQMAAN//UgyAzcTBQO656YDdUueKFS5L5TuyNom9gYh4ZZ6TQprPwY3FYoHBYP9G9c0332DOnDkAgAEDBiA/P1/Z1RF1It8ct2dtJvRKQqwCxyi0h0YjIDnaPltHairOc2RuMjz02zR22696YurAFPn99h7mSe1n6KDdUqIoKt9zw/k25COfg5vBgwdj9erV2L59OzZv3owZM2YAAPLy8pCUxLM+iNpqs1ySSmnlyo4hTyl2bAf3NnMDAIJg77+Rhv1lJrT+GPIvfQftlio0mlBnsUKrEZCVqNQ2cB67QL7xObj561//itdffx1TpkzBDTfcgOHD7fXPTz/9VC5XEZFvymvM2He+DABwRYD7bSQpjQb5tbRTypP4yDC8u3gc7rq8D+aPzvLPIslrHdVQfLbEfr5Yt8RI5ZrimbkhH/m8FXzKlCkoKSmB0WhEQkKCfP///M//IDJSwdNfiTqR704WwSYCA9JilPttt50aH8Egl6W8yNxIeneJxv3T+iu/OPJZR20FP19SC0DBklR1MWC8AEAA0oYp85wU8to0XUmr1boFNgDQo0cPJdZD1ClJu6SmDQqOrA3gzNxIPTfSieAZ8SwxqVFHDfE758jcKNdMfMj+Z3JfIDxWmeekkOdVcDNq1Chs2bIFCQkJGDlyZIvzKg4cOKDY4og6A1ODFdtO2ncVTQ2i4KaLyyA/URTl4Mb1RHBSj446fkHxGTecb0Nt4FVwM3fuXHmH1Lx58/y5HqJOZ9eZUtSYrUiNNWBIRlyglyNzHsFgQmmNGeYGGwQBSGNwo0od13Oj8IwbHrtAbeBVcPP44497fJuI2k8qSV0xMBUahU/3bg/p8MziKpPcTNwl2hDQycnUdtJWcIsfg5sGqw05pQr33DC4oTZo14lm1dXVsNnc/6PExrImSuQtURTxjePIhSuDZJeUxPUIBudOKfbbqFVH9NxcKK9Dg01EuF6DNCWmUlcVAFX5gKAB0oa2//mo0/D5V7Bz585h1qxZiIqKQlxcHBISEpCQkID4+PgmTcZE1LKjF40oMNYjQq/FxN7BNScq2VGWMlttOFFQBQDIYElKtTqi50but0mKUiYLKW0BT+4HGKLb/3zUaficubn55pshiiLeeustpKam8jA8onZYv89+Xs5lA7ogXO/5vKZACddrERehR2WdBYdzKwBwp5SaSeVEf24Fl/tturAkRYHlc3Bz+PBh7N+/H/37c3YFUXtU1lrw0f6LAICbJ3QP8Go8S4kx2IObC5UAuFNKzTqioVjxbeAMbqiNfC5LjR07Frm5uf5YC1Gn8sG+XNRZrBiQFoOJvYKrJCWRBvmV1dgPz2TmRr06sizVM1mBEpIoOmfcMLghH/mcuXnzzTdx55134uLFixgyZAj0evcD/oYN4wRJotZYbSLe2XUeALDokh5BW96VmoolzNyoV0c0FDunEyswZbsqH6gutDcTpw5p//NRp+JzcFNcXIwzZ85g0aJF8n2CIEAURQiCAKvVqugCiULR5uOFuFBeh4RIPeaO6Bro5TSrS6Pghpkb9TL4uSxVb7HiomNXnSKZG6kk1WUgEBYcR5KQevgc3Nx6660YOXIk/v3vf7OhmKiN1uw8BwC4YVy3oGskdiWdDA4AOo0g76Ai9fF3Wep8qb0kFRehR0KkvpWrvcB+G2oHn4Ob7OxsfPrpp+jTp48/1kMU8n7ON2L32TJoNQJ+PzE4G4kl0iA/AEiNDYc2iIYMkm/C/DzE71yx1G8TpcwvvdGpQMYoIGts+5+LOh2fg5vLL78chw8fZnBD1EZv77BnbWYMSUO6DydsB0IXl0xNV5akVE3qubFYRdhsouLTsBU/dmHc7fYbURv4HNzMnj0b9913H44cOYKhQ4c2aSieM2eOYosjCjVlNWZsPJQHALj1kh6BXYwXXDM36fFsJlYzvc65OdZstSFco2w51LlTSqHghqgdfA5u7rzzTgDAk08+2eRjbCgmatm/9+TA3GDDsMw4jOoW/BO9u7j03AR7lolaFqZtFNwo3OslBzdKDfAjagefg5vGZ0kRkXcsVhve25UNAFg4KXi3f7uKDdchTKeBucGGDGZuVM0tuPFDUzEzNxRMFDnet6KiQomnIQpp3xwvRIGxHsnRBswalh7o5XhFEAR51g0zN+qm0QjQa+0BtdLBTUWtWR702COJwQ0Fns/BzV//+lesX79efn/+/PlITExE165dcfjwYUUXRxRKdpwpAQDMGZ4Bgy54t383tmBid4zpnoAJvRIDvRRqJ38N8pOyNqmxBkQZfC4IECnO5+Bm9erVyMrKAgBs3rwZ33zzDTZt2oSZM2figQceUHyBRMFOFEXkVdRBFMUWr9ufXQEAGNsj+HttXP3Pr3tjwx8mISZcgdklFFD+Ol9KmnHDkhQFC5+Dm4KCAjm4+fzzz3Httddi2rRpePDBB7F3717FF0gU7N7bnY1Jz3yLf+9p/sy1qnoLThYYAQCju6sruKHQ4a9Bfs4ZNwpMJiZSgM/BTUJCgnxw5qZNmzB16lQA9t9euVOKOqMjjhOz/3skr9lrDudWwiYCWYkRSIllYy4Fhr8yN4rPuCFqJ5+Dm9/+9re48cYbceWVV6K0tBQzZ84EABw8eNDnwX7ff/89Zs+ejYyMDAiCgI0bN7Z4/datWyEIQpNbQUGBr58GkWKkRsp958tRb/Ec4O/PLgcAjFbB9m8KXf7uuWFZioKFz8HNCy+8gKVLl2LQoEHYvHkzoqPtacj8/Hz8v//3/3x6rpqaGgwfPhyvvvqqT487efIk8vPz5VtKSopPjydSUqkjuDE12HAgp9zjNfsd97MkRYGk90NwI4oiZ9xQ0PG5rV2v1+OPf/xjk/vvu+8+n1985syZcubHFykpKYiPj/f5cUT+IGVuAGDn6VJM6p3s9nGbTcRBR+ZmFIMbCiCDH3puLpTXodZshV4rICuBp3dTcGjTnr1Tp07hu+++Q1FRUZOhfo899pgiC2vJiBEjYDKZMGTIEPz5z3/GJZdc0uy1JpMJJpNJft9oNPp9fdS5uAY3O86U4I/o7/bxU0XVqDI1ICpMi/6pMR29PCKZP3pujuXZe876pcbIz08UaD4HN//4xz/whz/8AcnJyUhLS3ObsioIgl+Dm/T0dKxevRpjxoyByWTCm2++iSlTpuDHH3/EqFGjPD5m5cqVeOKJJ/y2JurcTA1WVJsa5Pd/ulCJqnqL27Zpqd9mRLd46LT85k+B44/dUsfy7L8wDs6IVew5idrL5+Dm6aefxooVK/DQQw/5Yz0t6t+/P/r3d/5WPGnSJJw5cwYvvPAC3nvvPY+PWb58OZYtWya/bzQa5a3sRO0lZW10GgEZ8RHIKavFnnNluGJgqnwNm4kpWPijodgZ3MQp9pxE7eXzr5Hl5eWYP3++P9bSJuPGjcPp06eb/bjBYEBsbKzbjUgpUnCTEBWGS/rYe212nC51u0ZqMma/DQWalLkxKViWOnrRXpYa0pXfWyl4+BzczJ8/H19//bU/1tImhw4dQnq6Os7podAjBTeJkWG4pE8SAGCn45gFACitNsk7SUYyc0MBFuY49sOiUOamuMqEoioTBAEYkMbghoKHz2WpPn364H//93+xe/duDB06FHq9+0j2u+++2+vnqq6udsu6nDt3DocOHUJiYiK6deuG5cuX4+LFi3j33XcBAC+++CJ69uyJwYMHo76+Hm+++Sa+/fbboAq2qHORg5uoMEzsZQ9uThRUoaTahORoAw7kVAAA+qVGIy6CxxdQYMllKYUyN1Izcc/kKJ4pRUHF53+Nb7zxBqKjo7Ft2zZs27bN7WOCIPgU3Ozbtw+XXXaZ/L7UG3PLLbdgzZo1yM/PR05Ojvxxs9mM+++/HxcvXkRkZCSGDRuGb775xu05iDpSabUjuIkOQ1K0AQPSYnCioAo7z5RizvAMZ78NS1IUBMJ0yp4KLvXbDGG/DQUZn4Obc+fOKfbiU6ZMafGwwTVr1ri9/+CDD+LBBx9U7PWJ2kvK3CRFhQEALumTjBMFVdh1pgRzhmfggDTfhiUpCgJKNxRLmRvulKJgw32pRO1Q6lKWAiD33ew4XQpzgw2HL1QAYOaGgoPSc264U4qCVZuKpBcuXMCnn36KnJwcmM1mt489//zziiyMSA3KauwDIqXMzbieSdBpBOSU1eKrYwUwNdiQEKnnmTsUFJScc2OstyC7tBYAMzcUfHwObrZs2YI5c+agV69eOHHiBIYMGYLz589DFMVmB+kRharyGgsAIDHKAACINugwPCse+7PL8cq39mb50d0T3IZdEgVKmNa+W8qkQHBz3JG16RofgQRHcE8ULHwuSy1fvhx//OMfceTIEYSHh+Ojjz5Cbm4uJk+eHFTzb4g6Qqkjc5MQ5dwJNam3vTR1srAKAOfbUPBQMnMjlaQGMWtDQcjn4Obnn3/GggULAAA6nQ51dXWIjo7Gk08+ib/+9a+KL5AomDkbig3yfY0PzuRkYgoWSvbcHJOG97HfhoKQz8FNVFSU3GeTnp6OM2fOyB8rKSlp7mFEQeP5r09i6vPbUF5jbv3iFlhtIirqpLKUMy0/qns8wvX2/1o6jYBhmfHteh0ipUjBjRJD/HimFAUzn4ObCRMm4IcffgAAXHXVVbj//vuxYsUK3HrrrZgwYYLiCyRS2vp9uThdVI19jm3abVVea4Y0ySAh0lmWMui0GNsjEYD9G39EmLZdr0OkFINCQ/zqLVacLq4GAAzmsQsUhHxuKH7++edRXW3/R/3EE0+guroa69evR9++fblTioJevcWKQqO9T6bQWN+u55JKUvGR+ianfc8cko7tp0pw+YBUTw8lCgi9QkP8ThRUwWoTkRQVhrTYcCWWRqQon4Ibq9WKCxcuYNiwYQDsJarVq1f7ZWFE/nChvFZ+u6jK1K7nkqcTe9gpcsO4LAzLjEP/tJh2vQaRkqTdUu0NbqThfYMyYrkTkIKST2UprVaLadOmoby8fel8okCR5nIAQFE7Mzflte7TiV0JgoAhXeOg13JOJgUPpU4F5/A+CnY+f+cdMmQIzp4964+1EPldTpmCmRtHWSohkjM+SB2U2gou75Rivw0FKZ+Dm6effhp//OMf8fnnnyM/Px9Go9HtRhTM3IObdvbcOMpSSdEMbkgdnGdLWdv8HA1WG04U2Gc4MXNDwcrrnpsnn3wS999/P6666ioAwJw5c9xqraIoQhAEWK1t/09D5G+5LsGN1FjcVtLRC556boiCkRJzbs4U18DUYEO0QYfuiZFKLY1IUV4HN0888QTuvPNOfPfdd/5cD5FfuWZuSqtNaLDamux08pbz0ExDK1cSBQeDAmWpo46S1KD0WGg0bCam4OR1cCM6BnpMnjzZb4sh8idRFN2CG5toD1BS27iV1TmdmJkbUgd5iJ9VbPNz8NgFUgOffmXllj9Ss+IqE+otNmgEZ0BS1I7SVFlN81vBiYKRs+em7ZkbaRs4JxNTMPNpzk2/fv1aDXDKysratSAif5GyNulxEUiKDkNpjRmFxnoMRduaIhnckNro21mWstlE+TTwIV3ZTEzBy6fg5oknnkBcHP9BkzpJwU33pEhEOo5EaOt2cFEU5Tk3DG5ILcJcjl+QNoH44sjFSlSZGmDQadAnJdofSyRShE/BzfXXX4+UlBR/rYXIr6QBft0SI+Vv6m09gsFY3yD3LTC4IbWQem4Ae4Bj0Pl27tm6vTkAgBlD0jigkoKa18EN+21I7aRt4FmJkbA4tsK2NXMjlaSiwrQI1/NgTFIHg2tw0+BbcFNjasCnh/IAANeP7ab42oiU5PNuKSK1kspS3RIjUVXfAKDtRzDIM244wI9UJEzrHtz44rPDeagxW9EzOQoTeiUqvTQiRXkd3Nhs7RvXTRRorj03xY6MTVszN85DMznjhtRDoxGg0whosIk+D/L79x57Ser6sVnM5FPQ86nnhkit6sxWOZDplhgJAe3ruWnp0EyiYBam06DBbIWlwfts/PE8Iw5fqIReK+B3ozP9uDoiZbAjjDqF3HJ71iYmXIe4CD1SY+0Zl5JqE6w230uupdwGTirlPILB+6NypEbiKwelIjma2UoKfgxuqFPIabRTKinaAI0gTSn2vTRVVs3ghtRJ2uVk8rLnps5sxccHLwJgIzGpB4MbUqUN+y/IPQDecO23AQCtxh7gAG2bUswBfqRWvk4p/uJIPqrqG5CZEIFL+yT7c2lEimHPDalOvcWKhz/6CQ02EeN6JqJ3l9aHieW4bAOXpMYaUFxlQlFVPeDjlGKWpUitfD08UypJXT82iwdlkmowc0OqU15rRoOjT2bT0QKvHuO6DVySEmM/MLOwHZkbNhST2jh7bloPbk4XVWHv+XJoNQLmj8ny99KIFMPghlSnotYiv/3l0XyvHuMpuJGailmWos4kzIfMzbo9uQCAy/qnIDU23K/rIlISgxtSHdfg5uhFozx5uDk2myhf0z0xSr6/i5S5qfJ9O7gzc8OdI6Qu3vbcmBqs+OjABQDADeOYtSF1YXBDqlPhmDEj+epYy6Wp4moTTA02aDUC0uOdv322NXNTZ7aizmLfRssJxaQ23palThZUobzWgoRIPSb369IRSyNSDIMbUp2KOnvmRhqS+mUrfTfSgZkZ8eFuh/1JPTdFPmZupK3jYVoNosJ4rhSpi7dlqbyKOgBA96Qo6HhIJqkM/8WS6kjTgaVtqfuzy1ucNOyp3wZoe+bGtd+GY+hJbeSyVCuZm7wK+/+pjHj22pD6MLgh1al09Nz0T43BqG7xAFouTTmDmyi3+6XMTbGPU4q5DZzUTO9l5ia/0p65SY+L8PuaiJTG4IZUR2oojo/UY+aQdADAl0eaD25ym8ncJEeHQRAAq02UszGuqk0NOJRb0eR+aTpxEvttSIUMXjYU51VKmRsGN6Q+DG5IdaSyVHxkGGYMSQMA/Hiu1GOAAgDZpTUAmgY3Oq1G3u3kqe/myc+OYd6rO/DZ4Ty3+7kNnNTM256bfEfPTUYcy1KkPgxuSHWkhuL4SD2yEiMxOCMWNhHYfNxz9ianzP5NunFwAwApMZ77bkRRxJafiwA4J7RKymoZ3JB6ebtbSuq5SWfmhlSIwQ2pjtRzEx9hDy5mOrI3nnZN1ZobUFJtD1y6JTUNbuSm4kaZm3MlNXJvzc4zpShyaViWy1IMbkiFvJlz02C1yf8nmLkhNWJwQ6rjLEvpAQAzHH03O06XoLLO4nZtriNrExehR1yEvslzNXcEw97zZfLbogh89pNzErIU9CQwuCEVkjI3LZ0KXlhlgk0E9FoBydEcVEnqE9Dg5vvvv8fs2bORkZEBQRCwcePGVh+zdetWjBo1CgaDAX369MGaNWv8vk4KHqIoupWlAKBPSjT6pkTDYhXx7YlCt+ub67eRNJe52XOuHACQ5hg5/8mhi/LHyhxzbpi5ITXypiwl9dukxYXzsExSpYAGNzU1NRg+fDheffVVr64/d+4cZs2ahcsuuwyHDh3Cvffei9tuuw1fffWVn1dKwaLeYpPT6fGRzuBCaixufJCmvA3cQ0kKALrEtpy5eXBGf2g1An66UImzxdUAXBuK+RstqY8U3FhayNxcrOA2cFK3gAY3M2fOxNNPP42rr77aq+tXr16Nnj174rnnnsPAgQOxdOlSXHPNNXjhhReafYzJZILRaHS7kXpJJSm9VnCbDiwFN9+dKMZfvvgZvxRWAWh+G7hEbiiucgY3hcZ65JTVQhCAqYNS8au+9mGBnxyy75rinBtSM2+G+OVXst+G1E1VPTe7du3C1KlT3e6bPn06du3a1exjVq5cibi4OPmWlcUD4NRMmnETF+E+HXhQeiwu6ZMEs9WGN74/i2kvfI+5r/yA70+VAGipLOU4gsGlYVjK2gxMi0VsuB5zR2QAAD49nAdzgw1V9Q0AWJYidfJmK7hUluJOKVIrVQU3BQUFSE1NdbsvNTUVRqMRdXV1Hh+zfPlyVFZWyrfc3NyOWCr5SUWdezOxRBAErFk0Dm/8fjSmDUqFTiPg8IVKnCtpuedGytwUV5lgc0wp3nvOHtyM65kIAJg2KA3heg3OldRg2y/FAACtRvDYoEwU7LzZLcUBfqR2ukAvwN8MBgMMBvZGBJIoith1phR9UqPl3UltJWVuEiKbBhZ6rQbTBqdh2uA0lFSb8MmhPPznwAXoNAJGOo5paKyLI7hpsIkoqzUjOdqAPeftzcRje9iDmyiDDlcOSsNnh/Pw9o5z8uuz0ZLUyJuG4jwO8COVU1Vwk5aWhsJC990whYWFiI2NRUQEf8MIVgdzK3Djmz/ikj5J+NdtE9r1XK5lqZYkRxuw+NKeWHxpzxav02s1SIoKQ2mNGUVGE/RaDU4U2PuyxvZIkK+bNyIDnx3Ow84zpQCAhEiWpEidvNkKLvXcsKGY1EpVZamJEydiy5Ytbvdt3rwZEydODNCKyBsnC+zNvYdyKiCK3h9Q6UnjGTdKSJH6bqrqcSCnHKIIdE+KlO8HgF/17eL2mmwmJrUK19kb8evMVo8fr7dY5R2BPBGc1CqgwU11dTUOHTqEQ4cOAbBv9T506BBycuzj7pcvX44FCxbI19955504e/YsHnzwQZw4cQKvvfYaPvjgA9x3332BWD55SWpOrDFb5S2mbSUN6fNUlmor1yMYpH4bqSQlCdNpcNXQdPl9HppJatXdMRbhTHG13GfmSsraROi17Csj1QpocLNv3z6MHDkSI0eOBAAsW7YMI0eOxGOPPQYAyM/PlwMdAOjZsyf++9//YvPmzRg+fDiee+45vPnmm5g+fXpA1k/ekZoTAeBUYXW7nqvC5dBMpTi3g9fLO6XGNQpuAGDeiK7y28zckFr1TI5CmFaDWrMVueW1TT4uH5gZH+62I5FITQLaczNlypQWyxSepg9PmTIFBw8e9OOqSGl5LtmaXwqrcNmAlDY/V7ncc6Pcb5TSdvDcsjoczq0EAIzt2TS4GdM9ARlx4cirrOcAP1ItnVaDvqnROJZnxM/5VeieFOX28YtycMN+G1IvVfXckDrlu2Rufmln5qZS3i2lYObGcQTDlhNFMFttSI42oIeHicYajYA/TOkNvVbApX2SFXt9oo42MD0WAOTmeVfOZmL225B6qWq3FKmPKIpNMjft0dycm/aQtqdLp4eP65nQbDr+9xN74Kbx3bkNnFRtQFoMAODnfE/BDY9eIPVj5ob8qqzG7Lbl9HSR5yZGb0llKWV3S7mXmMZ0b1qScsXAhtTOmblp+stGXoU9c9OVZSlSMQY35FdSijsxKgxhOg3qLFZcKG/bjilRFOWylJINxamx7un3cR76bYhCiZS5yS6tRY2pwe1jefLRCyxLkXoxuCG/kr5RZiZEoHeXaABtL03VWazyVNV4BRuKu0Q7MzfRBp38Wy1RqEqKNsi7BBtnbzjAj0IBgxvyK+fpwhHol2oPbk62MbiRSlJhWg0iXU4Eb68wnUaemzOqewK0LDtRJzDAQ1Oxsd6CakcmhwP8SM0Y3JBfuaa4+6XaU+Gn2hjcSDNu4iL1is/fkEpT41yOXCAKZQPT7f8fT+Q7/z/mO/pt4iP1iAzjfhNSLwY35Fd5bpkb+zfTtm4Hl/tt/DA1dXK/LojQazFjSJriz00UjAam2TM3rjum5F9GWJIilWNoTn6V75a5sZelThdXw2oTfS7/lPthxo1k+VUD8cfp/aHXMt6nzmGAlLkpqIIoihAEAXmVPA2cQgO/k5Nf5blMO81KiES4XgNzgw3ZpTU+P5c04yZOwW3grhjYUGfSKzkaeq2AalODvINRKktxpxSpHb+bk99YbSIKq+yD8TLiIqDRCOiTIu2Y8r00VeHHshRRZxOm06BPivswvzwO8KMQweCG/Kaoqh5WmwidRkAXx7bT9jQVSw3FCTy0kkgRA9OcpSnAmbnhAD9SOwY35DdSSSo1Nlzur5GbiovanrlR8tBMos7M2XfTOHPDshSpG4Mb8htpjLvrvAypqbgtmRt/HL1A1JnJxzDk25uK5blUzNyQyjG4Ib/xdABfX0eN/0xxNSxWW5PH1JobYG3m7KlKR0OxP3ZLEXVGAxzbwc+V1uBCeR3MDTYIQtMjSYjUhsEN+Y0zc+MMbrrGRyAqTAuLVWyyY+piRR1+9dfvcOM/dnt8PjYUEymrS4wBydFhEEVg6y/F9vuiDQjT8UcDqRv/BZPfOLeBO38L1GgE9GlmmN/fvzmF0hozfjxX1uQwP8BZlvLXVnCizkgqTX37cyEAIJ0lKQoBDG7Ib5o7gK9fStMDNM+V1GDDgQvy+2eK3QMfURRZliLyA+mE8J1nSgFwgB+FBgY35Df5zey8cB7D4AxuXvzmF7dem9ONdlPVmq2wWO0fZ0MxkXKkvhtTg70HjjNuKBQwuCG/qLdYUVJtz7Q0npnRL829LHWyoAqfHs4DAIzpbj+48lSj4KbcMeMmTKdBhF65E8GJOjupLCXhaeAUChjckF8UOEpS4XpNk0yLtB38fEkNzA02PPf1SYgicNXQNMwengEAONWoH8e1mVjpE8GJOrPeKVHQuZzzxm3gFAoY3JBfOA/gi2gSjKTFhiPGoEODTcTGQxfx9fFCaARg2ZX90NfRj9O456ayjjNuiPzBoNOid5do+X0O8KNQwOCG/MLTNnCJIAjo68jePPnZcQDAvJFd0SclRj57Kru0BvUWq/wYqSwVz2ZiIsUNdEwqBpi5odDA4Ib8Ir+i5THuUlNxtakBOo2Ae6/oB8A+dyM2XAebaN9BJeGMGyL/GeDou9FpBCRHGwK8GqL2Y3BDfpEnbQNv5rdAKbgBgOvGZqFbUiQAe1ZHyt647phiWYrIfwY5gpuM+Aj5HDgiNdMFegEUmvLlnhvPmRtptoZBp8Fdl/d1+1jflBgcyKlw2zFVXsMZN0T+ckmfZPzPr3thbI/EQC+FSBEMbsgvnNOJPWduJvRKwh+m9MawrnFIaxQASf04p4ucc3Aq6jidmMhftBoBj1w1MNDLIFIMgxvyi3wPJ4K70mgEPDRjgMeP9fZQlqqQGoojmLkhIqKWseeGFGest6DKcTZUW6adStvBz5XUoMFxcrjUUJzAzA0REbWCwQ0pTsraxEXoEWXwPTmYEReBSOnk8LJaACxLERGR9xjchKh/fH8WU5/fhsO5FR3+2nnNnCnlLY1GkIeKSZOKWZYiIiJvMbgJQRarDa9uPY3TRdVY+PaeJodQKuVkQRWufH4bPtyX63Z/fgsD/LzVN8XZVCyKorMsFcXMDRERtYzBTQjac65MDgbKay34/T9/xEXH7iUlrd52BqeKqvHoxqNuA/fyWhng5w3XpuIasxUNjhPDmbkhIqLWMLgJQZuOFgAAZg5JQ+8uUcivrMfv//kjyhyzYpRQVW/Bl0fzAQCmBhse/ugn2BwBiHyulAKZm1NF1fKMmzCdBuF6/pMlIqKW8SdFiLHZRHx1zB7cXDc2C+8tHo+MuHCcLa7Borf3oNqxi6m9vjxSgHqLDV3jIxCh1+LHc2VYt9denmptG7g3+jomGJ8prpbPlUqI5IngRETUOgY3IeZgbjmKqkyIMegwqXcyMuIj8O7i8UiI1OPwhUrc8d4+lFSb2v06G/ZfAADcNKEb/ji9PwBg5Rc/o6Cy3qWhuO2Zm6yECIRpNai32HAszwiAJSkiIvIOg5sQ8+URe9bmioEpCNPZv7x9UqKxZtE4RIVpseN0KSat/Bb3rT+EAznlEEXR59fILq3BnvNl0AjAb0dmYuGkHhiRFY8qUwMe3XgE+Y5zpbq2oyyl02rQq0sUAGDvuTIAPFeKiIi8w+AmhIiiiE2OktSMIeluHxueFY93F4/D8Mw4mK02fHzwIn772k785uUf8MG+XLlfxhsfHbgIwH4eTVpcOLQaAf93zTDotQK++bkI5gYbBAFIjW17WQpwNhXvzWZwQ0RE3mNwE0KO5RlxobwOEXotJvfr0uTjo7sn4pOll+KTJZfgmtGZCNNpcCzPiAc3/ISXvz3t1WvYbCL+c8BekrpmdKZ8f7/UGCy5rI/8fnK0Qc4ctZXUVJxbZi9zsSxFRETeCIrg5tVXX0WPHj0QHh6O8ePHY8+ePc1eu2bNGgiC4HYLD29fhiBUSLukpvTvgogwbbPXDc+Kx9/mD8ePy6/AUkdA8trW08h1TANuyY/nynChvA4xBh2mD05z+9j/m9IH/RyHXjZ3Grgv+qbEuL0fzxk3RETkhYAHN+vXr8eyZcvw+OOP48CBAxg+fDimT5+OoqKiZh8TGxuL/Px8+Zadnd2BKw5ezpJUWitX2iVEheH+af0wsVcSTA02/OWLn1t9jNRI/Jvh6QjXuwdQYToNnps/At0SIzF3RFcfV99UH0fmRsLMDREReSPgwc3zzz+P22+/HYsWLcKgQYOwevVqREZG4q233mr2MYIgIC0tTb6lpqY2e63JZILRaHS7haLTRVU4XVQNvVbAZQNSvH6cIAh4fM4gaDUCvjxagB2nS5q9tsbUIM+2+d2oTI/XDM2Mw/cPXoZbL+3p2yfgQY/kSGg1zq3f7LkhIiJvBDS4MZvN2L9/P6ZOnSrfp9FoMHXqVOzatavZx1VXV6N79+7IysrC3LlzcezYsWavXblyJeLi4uRbVlaWop9DsJBKUpf2SUZsuG9BwIC0WPx+QncAwBOfHZNP4m7sy6MFqDVb0SMpEqO7J7RvwV4w6LTonhgpv88TwYmIyBsBDW5KSkpgtVqbZF5SU1NRUFDg8TH9+/fHW2+9hU8++QTvv/8+bDYbJk2ahAsXLni8fvny5aisrJRvubm5Hq9Tuy+P+laSauy+qf2QEKnHL4XVeH+35zLfR/udjcQdNUzPtTQVx7IUERF5IeBlKV9NnDgRCxYswIgRIzB58mT85z//QZcuXfD66697vN5gMCA2NtbtFmpyy2pxLM8IjQBMHdh8ia4lcZF6eRjf85t/QWmjQX9niqux62wpBAG4upmSlD/0TXUGNyxLERGRNwIa3CQnJ0Or1aKwsNDt/sLCQqSleZeB0Ov1GDlyJE6f9m4rcyiSSlLjeyYhKdrQ5ue5fmw3DEqPhbG+AX/72h7grP0xBze9uRvTXvgeADCpd1K7hvP5yjVzkxDJzA0REbUuoMFNWFgYRo8ejS1btsj32Ww2bNmyBRMnTvTqOaxWK44cOYL09PTWLw5Rm3+2B4dtLUlJtBoBT8wdDABYtzcH4/6yBY98fAQ7TpfCahMxtGsc/jx7cLvX6wvX7eDM3BARkTd0gV7AsmXLcMstt2DMmDEYN24cXnzxRdTU1GDRokUAgAULFqBr165YuXIlAODJJ5/EhAkT0KdPH1RUVODZZ59FdnY2brvttkB+GgH1S2EVAGBsj8R2P9fYHomYOyIDnxzKg1W0BzRXDU3HrKHp6JYU2foTKKxvajR6JUchISqsydZzIiIiTwIe3Fx33XUoLi7GY489hoKCAowYMQKbNm2Sm4xzcnKg0TgTTOXl5bj99ttRUFCAhIQEjB49Gjt37sSgQYMC9SkEVGWdBRW1FgBAd4WCj2d+OwxXDEzFiMz4gAQ0rgw6LTYvmwyeBU5ERN4SxLacnKhiRqMRcXFxqKysDInm4iMXKjH7lR+QHG3Avkentv4AIiIiFfLl57fqdkuRu+yyGgDKZW2IiIjUjsGNymWX2s+Dch12R0RE1JkxuFG57FIpcxMV4JUQEREFBwY3KidnbliWIiIiAsDgRvVyyuzBTaB3NREREQULBjcqVm+xosBYD4A9N0RERBIGNyp2obwWoghEG3RIjOLRBERERACDG1Vz7bfpqFO6iYiIgh2DGxU7z2ZiIiKiJhjcqFiOYxt4t0RuAyciIpIwuFGx7DJmboiIiBpjcKNiOZxOTERE1ASDG5Wy2kTkljuCm2SWpYiIiCQMblQqr6IOFquIMK0GabHhgV4OERFR0GBwo1LSZOLMxAhoNdwGTkREJGFwo1I8DZyIiMgzBjcqlV3G08CJiIg8YXCjUjkc4EdEROQRgxuV4nRiIiIizxjcqJAoipxOTERE1AwGNypUWmNGjdkKQQCyEiMCvRwiIqKgwuBGhaSdUumx4TDotAFeDRERUXBhcKNCOdwpRURE1CwGNyqUzWZiIiKiZjG4USEpuOnG4IaIiKgJBjcqlO3YKdWdO6WIiIiaYHCjQtK5UixLERERNcXgRmWqTQ0oqTYDYFmKiIjIEwY3KiMdu5AYFYbYcH2AV0NERBR8GNyoTLY8mZhZGyIiIk8Y3KhMNvttiIiIWsTgRmXkGTfM3BAREXnE4EZlpOnE3TidmIiIyCMGNypSXGXCkQuVAIAeLEsRERF5xOBGJURRxIMbDsNY34D+qTEYnhUf6CUREREFJQY3KvHOzvP47mQxwnQa/P2GkdBr+aUjIiLyhD8hVeBEgRF/+fIEAOCRmQPQPy0mwCsiIiIKXgxugly9xYp7/n0I5gYbLuvfBbdM6hHoJREREQW1oAhuXn31VfTo0QPh4eEYP3489uzZ0+L1H374IQYMGIDw8HAMHToUX3zxRQettOM98+UJnCysQnJ0GJ6dPxyCIAR6SUREREEt4MHN+vXrsWzZMjz++OM4cOAAhg8fjunTp6OoqMjj9Tt37sQNN9yAxYsX4+DBg5g3bx7mzZuHo0ePdvDK/cvUYMWnh/OwZud5AMCz84cjOdoQ2EURERGpgCCKohjIBYwfPx5jx47FK6+8AgCw2WzIysrCXXfdhYcffrjJ9ddddx1qamrw+eefy/dNmDABI0aMwOrVq1t9PaPRiLi4OFRWViI2Nlaxz8PUYEVxlQmiCNhEUf7TJv8pwmoTYbMBVlGExWpDvcWKeov0pxX5lfU4WVCFk4VVOFdSA6vN/qVZOKkH/jxnsGJrJSIiUhtffn7rOmhNHpnNZuzfvx/Lly+X79NoNJg6dSp27drl8TG7du3CsmXL3O6bPn06Nm7c6PF6k8kEk8kkv280Gtu/cA+OXjTid6t2KvqcMeE6XDEgBQ/PHKDo8xIREYWygAY3JSUlsFqtSE1Ndbs/NTUVJ06c8PiYgoICj9cXFBR4vH7lypV44oknlFlwC7QaAQadBhpBgEYANIIAQQAEQYBWI8j3S2+H6TQw6DQw6LUI12kQrtciKToMA9Ji0C81Bv3TYpAWG84eGyIiIh8FNLjpCMuXL3fL9BiNRmRlZSn+OiOy4nHy6ZmKPy8RERH5JqDBTXJyMrRaLQoLC93uLywsRFpamsfHpKWl+XS9wWCAwcBGXCIios4ioLulwsLCMHr0aGzZskW+z2azYcuWLZg4caLHx0ycONHtegDYvHlzs9cTERFR5xLwstSyZctwyy23YMyYMRg3bhxefPFF1NTUYNGiRQCABQsWoGvXrli5ciUA4J577sHkyZPx3HPPYdasWVi3bh327duHN954I5CfBhEREQWJgAc31113HYqLi/HYY4+hoKAAI0aMwKZNm+Sm4ZycHGg0zgTTpEmTsHbtWjz66KN45JFH0LdvX2zcuBFDhgwJ1KdAREREQSTgc246mr/m3BAREZH/+PLzO+ATiomIiIiUxOCGiIiIQgqDGyIiIgopDG6IiIgopDC4ISIiopDC4IaIiIhCCoMbIiIiCikMboiIiCikMLghIiKikBLw4xc6mjSQ2Wg0BnglRERE5C3p57Y3Byt0uuCmqqoKAJCVlRXglRAREZGvqqqqEBcX1+I1ne5sKZvNhry8PMTExEAQhEAvx2dGoxFZWVnIzc3l2VhBgF+P4MKvR3Dh1yP4qPlrIooiqqqqkJGR4XagtiedLnOj0WiQmZkZ6GW0W2xsrOr+YYYyfj2CC78ewYVfj+Cj1q9JaxkbCRuKiYiIKKQwuCEiIqKQwuBGZQwGAx5//HEYDIZAL4XAr0ew4dcjuPDrEXw6y9ek0zUUExERUWhj5oaIiIhCCoMbIiIiCikMboiIiCikMLghIiKikMLgJgSYTCaMGDECgiDg0KFDgV5Op3T+/HksXrwYPXv2REREBHr37o3HH38cZrM50EvrVF599VX06NED4eHhGD9+PPbs2RPoJXVKK1euxNixYxETE4OUlBTMmzcPJ0+eDPSyyOGZZ56BIAi49957A70Uv2FwEwIefPBBZGRkBHoZndqJEydgs9nw+uuv49ixY3jhhRewevVqPPLII4FeWqexfv16LFu2DI8//jgOHDiA4cOHY/r06SgqKgr00jqdbdu2YcmSJdi9ezc2b94Mi8WCadOmoaamJtBL6/T27t2L119/HcOGDQv0UvyKW8FV7ssvv8SyZcvw0UcfYfDgwTh48CBGjBgR6GURgGeffRarVq3C2bNnA72UTmH8+PEYO3YsXnnlFQD2c+SysrJw11134eGHHw7w6jq34uJipKSkYNu2bfj1r38d6OV0WtXV1Rg1ahRee+01PP300xgxYgRefPHFQC/LL5i5UbHCwkLcfvvteO+99xAZGRno5VAjlZWVSExMDPQyOgWz2Yz9+/dj6tSp8n0ajQZTp07Frl27ArgyAuz/FwDw/0OALVmyBLNmzXL7fxKqOt3BmaFCFEUsXLgQd955J8aMGYPz588Heknk4vTp03j55Zfxt7/9LdBL6RRKSkpgtVqRmprqdn9qaipOnDgRoFURYM+g3XvvvbjkkkswZMiQQC+n01q3bh0OHDiAvXv3BnopHYKZmyDz8MMPQxCEFm8nTpzAyy+/jKqqKixfvjzQSw5p3n49XF28eBEzZszA/Pnzcfvttwdo5UTBYcmSJTh69CjWrVsX6KV0Wrm5ubjnnnvwr3/9C+Hh4YFeTodgz02QKS4uRmlpaYvX9OrVC9deey0+++wzCIIg32+1WqHVanHTTTfhnXfe8fdSOwVvvx5hYWEAgLy8PEyZMgUTJkzAmjVroNHw94eOYDabERkZiQ0bNmDevHny/bfccgsqKirwySefBG5xndjSpUvxySef4Pvvv0fPnj0DvZxOa+PGjbj66quh1Wrl+6xWKwRBgEajgclkcvtYKGBwo1I5OTkwGo3y+3l5eZg+fTo2bNiA8ePHIzMzM4Cr65wuXryIyy67DKNHj8b7778fct8sgt348eMxbtw4vPzyywDs5ZBu3bph6dKlbCjuYKIo4q677sLHH3+MrVu3om/fvoFeUqdWVVWF7Oxst/sWLVqEAQMG4KGHHgrJciF7blSqW7dubu9HR0cDAHr37s3AJgAuXryIKVOmoHv37vjb3/6G4uJi+WNpaWkBXFnnsWzZMtxyyy0YM2YMxo0bhxdffBE1NTVYtGhRoJfW6SxZsgRr167FJ598gpiYGBQUFAAA4uLiEBEREeDVdT4xMTFNApioqCgkJSWFZGADMLghUsTmzZtx+vRpnD59uklwyeRox7juuutQXFyMxx57DAUFBRgxYgQ2bdrUpMmY/G/VqlUAgClTprjd//bbb2PhwoUdvyDqdFiWIiIiopDCbkciIiIKKQxuiIiIKKQwuCEiIqKQwuCGiIiIQgqDGyIiIgopDG6IiIgopDC4ISIiopDC4IaIiIhCCoMbIiIApaWlSElJwfnz5xV93uPHjyMzMxM1NTWKPi8RNY/BDRH5ZOHChRAEocltxowZgV5au6xYsQJz585Fjx49vLp+9uzZzX7O27dvhyAI+OmnnzBo0CBMmDABzz//vIKrJaKW8PgFIvLJwoULUVhYiLffftvtfoPBgISEBL+9rtlsRlhYmF+eu7a2Funp6fjqq68wYcIErx6zceNG/O53v0N2dnaT88RuvfVWHDlyBHv37gUA/Pe//8Xtt9+OnJwc6HQ80o/I35i5ISKfGQwGpKWlud1cAxtBEPDmm2/i6quvRmRkJPr27YtPP/3U7TmOHj2KmTNnIjo6Gqmpqfj973+PkpIS+eNTpkzB0qVLce+99yI5ORnTp08HAHz66afo27cvwsPDcdlll+Gdd96BIAioqKhATU0NYmNjsWHDBrfX2rhxI6KiolBVVeXx8/niiy9gMBiaBDYtrfE3v/kNunTpgjVr1rg9prq6Gh9++CEWL14s33fllVeirKwM27Zt8/JvmIjag8ENEfnFE088gWuvvRY//fQTrrrqKtx0000oKysDAFRUVODyyy/HyJEjsW/fPmzatAmFhYW49tpr3Z7jnXfeQVhYGHbs2IHVq1fj3LlzuOaaazBv3jwcPnwYd9xxB/70pz/J10dFReH6669vklV6++23cc011yAmJsbjWrdv347Ro0e73dfaGnU6HRYsWIA1a9a4nfz+4Ycfwmq14oYbbpDvCwsLw4gRI7B9+/Y2/E0Skc9EIiIf3HLLLaJWqxWjoqLcbitWrJCvASA++uij8vvV1dUiAPHLL78URVEUn3rqKXHatGluz5ubmysCEE+ePCmKoihOnjxZHDlypNs1Dz30kDhkyBC3+/70pz+JAMTy8nJRFEXxxx9/FLVarZiXlyeKoigWFhaKOp1O3Lp1a7Of09y5c8Vbb73V7T5v1vjzzz+LAMTvvvtOvuZXv/qVePPNNzd5jauvvlpcuHBhs2sgIuWw+EtEPrvsssuwatUqt/sSExPd3h82bJj8dlRUFGJjY1FUVAQAOHz4ML777jtER0c3ee4zZ86gX79+ANAkm3Ly5EmMHTvW7b5x48Y1eX/w4MF455138PDDD+P9999H9+7d8etf/7rZz6eurg7h4eFu93mzxgEDBmDSpEl46623MGXKFJw+fRrbt2/Hk08+2eQxERERqK2tbXYNRKQcBjdE5LOoqCj06dOnxWv0er3b+4IgwGazAbD3pcyePRt//etfmzwuPT3d7XXa4rbbbsOrr76Khx9+GG+//TYWLVoEQRCavT45ORnl5eVu93m7xsWLF+Ouu+7Cq6++irfffhu9e/fG5MmTmzymrKwMvXv3btPnQ0S+Yc8NEXW4UaNG4dixY+jRowf69OnjdmspoOnfvz/27dvndp+0I8nVzTffjOzsbPz973/H8ePHccstt7S4npEjR+L48eNtWuO1114LjUaDtWvX4t1338Wtt97qMZA6evQoRo4c2eI6iEgZDG6IyGcmkwkFBQVuN9edTq1ZsmQJysrKcMMNN2Dv3r04c+YMvvrqKyxatAhWq7XZx91xxx04ceIEHnroIfzyyy/44IMP5N1KrgFFQkICfvvb3+KBBx7AtGnTmmzVbmz69Ok4duyYW/bG2zVGR0fjuuuuw/Lly5Gfn4+FCxc2ef7z58/j4sWLmDp1qpd/Q0TUHgxuiMhnmzZtQnp6utvt0ksv9frxGRkZ2LFjB6xWK6ZNm4ahQ4fi3nvvRXx8PDSa5r8t9ezZExs2bMB//vMfDBs2DKtWrZJ3SxkMBrdrFy9eDLPZjFtvvbXV9QwdOhSjRo3CBx980KY1Ll68GOXl5Zg+fToyMjKaPP+///1vTJs2Dd27d291LUTUfhziR0SqtmLFCqxevRq5ublu97/33nu47777kJeX59Xwv//+97944IEHcPTo0RYDLF+ZzWb07dsXa9euxSWXXKLY8xJR89hQTESq8tprr2Hs2LFISkrCjh078Oyzz2Lp0qXyx2tra5Gfn49nnnkGd9xxh9dTjWfNmoVTp07h4sWLyMrKUmy9OTk5eOSRRxjYEHUgZm6ISFXuu+8+rF+/HmVlZejWrRt+//vfY/ny5fKxBn/+85+xYsUK/PrXv8Ynn3zicSs3EYU2BjdEREQUUthQTERERCGFwQ0RERGFFAY3REREFFIY3BAREVFIYXBDREREIYXBDREREYUUBjdEREQUUhjcEBERUUj5//CXGAhi0VacAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import torch\n", + "import matplotlib.pyplot as plt\n", + "\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", + "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": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "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": [ + "se_nobloch" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor(30)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "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()" + ] + } + ], + "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/nn/hr2hk.py b/dptb/nn/hr2hk.py index 7b443364..88b854c4 100644 --- a/dptb/nn/hr2hk.py +++ b/dptb/nn/hr2hk.py @@ -45,6 +45,8 @@ def __init__( self.node_field = node_field self.out_field = out_field + self.atom_norbs = [] + def forward(self, data: AtomicDataDict.Type) -> AtomicDataDict.Type: # construct bond wise hamiltonian block from obital pair wise node/edge features @@ -82,7 +84,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 @@ -127,6 +129,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_norbs.append(masked_oblock.shape[0]) ist += masked_oblock.shape[0] # if data[AtomicDataDict.NODE_SOC_SWITCH_KEY].all(): diff --git a/dptb/postprocess/NEGF.py b/dptb/postprocess/NEGF.py index f4b18069..b0983dae 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,105 @@ 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') - - self.results_path = run_opt.get('results_path') - self.jdata = jdata - self.cdtype = torch.complex128 - self._device = "cpu" + 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.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"] + 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"] + 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" 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) - + # 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) + 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( @@ -86,39 +128,76 @@ 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"], + 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.jdata["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"] ) ) # 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]] + 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 - 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 +210,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 +228,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 +242,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) + + + # 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!") + - # 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") + 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_orb_mask].mean() + # getattr(self.deviceprop, ll).voltage = Vbias[0] + else: + getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected_orb_mask].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) + if output_freq == 0: output_freq = 1 + 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[self.left_connected_orb_mask].mean() + + else: + getattr(self.deviceprop, ll).voltage = Vbias[self.right_connected_orb_mask].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)]) + 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_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) - - 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) - 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())) + prop_local_current = self.out.setdefault('LOCAL_CURRENT', {}) + prop_local_current[str(k)] = lcurrent - 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 +570,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, block_tridiagonal=self.block_tridiagonal) 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 +587,5 @@ def compute_lcurrent(self, kpoint): def SCF(self): - pass \ No newline at end of file + pass + diff --git a/dptb/postprocess/tbtrans_init.py b/dptb/postprocess/tbtrans_init.py index 1dc8af17..8264c292 100644 --- a/dptb/postprocess/tbtrans_init.py +++ b/dptb/postprocess/tbtrans_init.py @@ -2,16 +2,16 @@ 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 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 +from dptb.data.interfaces.ham_to_feature import feature_to_block log = logging.getLogger(__name__) @@ -37,72 +37,67 @@ 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, 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'] + 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): + ''' + 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 + # 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 = j_must_have(jdata, "stru_options") - self.energy_unit_option = 'eV' # enenrgy unit for TBtrans calculation + self.stru_options = stru_options + 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(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) + 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) @@ -124,6 +119,16 @@ def __init__(self, apiHrk, run_opt, jdata): 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 hamil_get_write(self,write_nc:bool=True): @@ -146,21 +151,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') @@ -169,15 +171,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): @@ -239,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] @@ -272,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) @@ -316,7 +281,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 . @@ -346,8 +311,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()) @@ -426,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') - + return orbital_name_list - # 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 - - - - def _load_model(self,apiHrk,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. - + def _load_model(self,model,AtomicData_options,structure_tbtrans_file:str): + '''The `load_dptb_model` function loads model and returns the Hamiltonian elements. Parameters ---------- checkfile : str @@ -558,23 +425,34 @@ 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) # 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:]])) + 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. @@ -599,27 +477,10 @@ 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)) - # 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 @@ -636,23 +497,17 @@ 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: 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): - 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)]) 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_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 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..a2bb6a46 --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_hamiltonian/run_input.json @@ -0,0 +1,93 @@ +{ + "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-5, + "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": "float64", + "overlap":false + }, + "model_options": { + "nnsk":{ + "onsite":{ + "method": "none" + }, + "hopping":{ + "method":"powerlaw", + "rs":1.6, + "w":0.3 + }, + "freeze":false, + "push":false, + "std":0.01, + "soc":{} + } + } +} \ No newline at end of file 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 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/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/negf_chain.json b/dptb/tests/data/test_negf/test_negf_run/negf_chain.json new file mode 100644 index 00000000..ec34ee80 --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_run/negf_chain.json @@ -0,0 +1,78 @@ +{ + "task_options": { + "task": "negf", + "scf": false, + "block_tridiagonal": false, + "ele_T": 300, + "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":{ + "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.1, + "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 +}, + +"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/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..d8140fbd --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_run/negf_chain_new.json @@ -0,0 +1,58 @@ +{ + "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 +}, +"structure":"./chain.vasp" +} \ No newline at end of file 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/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..269bdba1 --- /dev/null +++ b/dptb/tests/data/test_negf/test_negf_run/negf_graphene_new.json @@ -0,0 +1,61 @@ +{ + "task_options": + { + "task": "negf", + "scf": false, + "block_tridiagonal": true, + "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 + }, + "structure":"./graphene.xyz" +} \ 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 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/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/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..c813e086 --- /dev/null +++ b/dptb/tests/data/test_tbtrans/best_nnsk_b3.600_c3.600_w0.300.json @@ -0,0 +1,122 @@ +{ "version": 1, + "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/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 new file mode 100644 index 00000000..e8579bba --- /dev/null +++ b/dptb/tests/data/test_tbtrans/negf_tbtrans.json @@ -0,0 +1,94 @@ +{ + "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":{} + } + }, + "structure":"./test_hBN_zigzag_struct.xyz", + "AtomicData_options": { + "r_max":3.6, + "oer_max": 1.6 + }, + "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, + "nel_atom": { + "N": 5, + "B": 3 + }, + "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/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/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/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 diff --git a/dptb/tests/data/v1/test_negf/test_negf_hamiltonian/run_input.json b/dptb/tests/data/v1/test_negf/test_negf_hamiltonian/run_input.json index 3782cd39..f4c232c8 100644 --- a/dptb/tests/data/v1/test_negf/test_negf_hamiltonian/run_input.json +++ b/dptb/tests/data/v1/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/v1/test_negf/test_negf_run/negf_chain.json b/dptb/tests/data/v1/test_negf/test_negf_run/negf_chain.json index 480dc27d..ec34ee80 100644 --- a/dptb/tests/data/v1/test_negf/test_negf_run/negf_chain.json +++ b/dptb/tests/data/v1/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/test_negf_density_Ozaki.py b/dptb/tests/test_negf_density_Ozaki.py new file mode 100644 index 00000000..20e36d13 --- /dev/null +++ b/dptb/tests/test_negf_density_Ozaki.py @@ -0,0 +1,96 @@ +# test_negf_density_Ozaki +from dptb.negf.density import Ozaki +from dptb.negf.device_property import DeviceProperty + +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 +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' + 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" + 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'), + 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"]) + 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 Ozaki + 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=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=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 new file mode 100644 index 00000000..45271472 --- /dev/null +++ b/dptb/tests/test_negf_device_property.py @@ -0,0 +1,183 @@ +#test_negf_Device_set_leadLR +from dptb.negf.device_property import DeviceProperty +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 +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' + 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" + 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'), + 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"]) + 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.Lead_L.structure + 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(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 + 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(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) + 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 + + 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','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], + [ 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_() + 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 + + + + 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_negf_hamiltonian_init.py b/dptb/tests/test_negf_negf_hamiltonian_init.py new file mode 100644 index 00000000..65a96294 --- /dev/null +++ b/dptb/tests/test_negf_negf_hamiltonian_init.py @@ -0,0 +1,262 @@ +# Hamiltonian +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 +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' + 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" + 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'), + 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"]) + 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") + 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 + + + + + + +# 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 diff --git a/dptb/tests/test_negf_run.py b/dptb/tests/test_negf_run.py new file mode 100644 index 00000000..7ae647a8 --- /dev/null +++ b/dptb/tests/test_negf_run.py @@ -0,0 +1,149 @@ +from dptb.entrypoints.run import run +import pytest +import torch +import numpy as np + + +@pytest.fixture(scope='session', autouse=True) +def root_directory(request): + """ + :return: + """ + return str(request.config.rootdir) + +# 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_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_new.json' + structure = root_directory +"/dptb/tests/data/test_negf/test_negf_run/chain.vasp" + + 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 + + + +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' + 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 + +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 diff --git a/dptb/tests/test_tbtrans_init.py b/dptb/tests/test_tbtrans_init.py new file mode 100644 index 00000000..fd8a134e --- /dev/null +++ b/dptb/tests/test_tbtrans_init.py @@ -0,0 +1,123 @@ +import pytest +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 + +@pytest.fixture(scope='session', autouse=True) +def root_directory(request): + return str(request.config.rootdir) + + +def test_tbtrans_init(root_directory): + + # check whether sisl is installed: if not, skip this test + try: + import sisl + except: + 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/test_output' + input_path = root_directory +"/dptb/tests/data/test_tbtrans/negf_tbtrans.json" + 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']) + + + + # 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(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*']) + assert element_orbital_name == ['3s', '3py', '3pz', '3px', 'dxy*', 'dyz*', 'dz2*', 'dxz*', 'dx2-y2*'] + + + + 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()*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()*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()*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()*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 + + + # tbtrans_hBN.hamil_write() + # check the hamiltonian through Hk at Gamma and M + H_lead = tbtrans_hBN.H_lead_L + G_eigs = sisl.BandStructure(H_lead, [[0., 0.,0.]], 1, ["G"]) + M_eigs = sisl.BandStructure(H_lead, [[0, 0.5 ,0 ]], 1, ["M"]) + Ef = -9.874358177185059 + G_eigs = G_eigs.apply.array.eigh() -Ef + M_eigs = M_eigs.apply.array.eigh() -Ef + + 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_correct[0]).max()<1e-4 + assert (M_eigs[0]-M_eigs_correct[0]).max()<1e-4 + + + diff --git a/dptb/utils/argcheck.py b/dptb/utils/argcheck.py index db81efe8..074abffc 100644 --- a/dptb/utils/argcheck.py +++ b/dptb/utils/argcheck.py @@ -1007,10 +1007,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(): @@ -1041,16 +1044,22 @@ def PDIIS(): def poisson_options(): doc_solver = "" doc_fmm = "" + doc_pyamg= "" + doc_scipy= "" return Variant("solver", [ - Argument("fmm", dict, fmm(), doc=doc_fmm) + Argument("fmm", dict, fmm(), doc=doc_fmm), + 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(): 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 = "" @@ -1062,6 +1071,14 @@ def Ozaki(): Argument("n_gauss", int, optional=True, default=10, doc=doc_n_gauss), ] +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) + ] + def fmm(): doc_err = "" @@ -1069,6 +1086,78 @@ def fmm(): Argument("err", [int, float], optional=True, default=1e-5, doc=doc_err) ] +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), + 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("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(): doc_task = "the task to run, includes: band, dos, pdos, FS2D, FS3D, ifermi" doc_structure = "the structure to run the task" diff --git a/dptb/utils/band.py b/dptb/utils/band.py index e0cca38e..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): @@ -115,4 +115,109 @@ 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, + 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. + + 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 + band_file: str + the band file generated by DFTB+ code + ylim: list[float] + the ylimit of the band structure plot + + Returns: + -------- + None + + Examples: + --------- + >>> struct = read('struct.vasp') or read('struct.gen') + >>> 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) + if ylim: + plt.ylim(ylim[0],ylim[1]) + plt.ylabel('Energy (eV)') + plt.show() + return + + \ No newline at end of file diff --git a/dptb/utils/make_kpoints.py b/dptb/utils/make_kpoints.py index 08ce0831..e969f93f 100644 --- a/dptb/utils/make_kpoints.py +++ b/dptb/utils/make_kpoints.py @@ -391,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 diff --git a/dptb/v1/nnsktb/formula.py b/dptb/v1/nnsktb/formula.py index f49f6fb0..dccd8165 100644 --- a/dptb/v1/nnsktb/formula.py +++ b/dptb/v1/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) 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) diff --git a/examples/get_fermi/get_fermi.ipynb b/examples/get_fermi/get_fermi.ipynb index 1a57a630..ee8573d8 100644 --- a/examples/get_fermi/get_fermi.ipynb +++ b/examples/get_fermi/get_fermi.ipynb @@ -459,7 +459,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.10.13" } }, "nbformat": 4, 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