def h_and_s(self): """Return LCAO Hamiltonian and overlap matrix in real-space.""" # Extract Bloch Hamiltonian and overlap matrix H_kMM = [] S_kMM = [] h = self.calc.hamiltonian wfs = self.calc.wfs kpt_u = wfs.kpt_u for kpt in kpt_u: H_MM = wfs.eigensolver.calculate_hamiltonian_matrix(h, wfs, kpt) S_MM = wfs.S_qMM[kpt.q] #XXX Converting to full matrices here tri2full(H_MM) tri2full(S_MM) H_kMM.append(H_MM) S_kMM.append(S_MM) # Convert to arrays H_kMM = np.array(H_kMM) S_kMM = np.array(S_kMM) H_NMM = self.bloch_to_real_space(H_kMM) S_NMM = self.bloch_to_real_space(S_kMM) return H_NMM, S_NMM
def test_multiply_randomized(self): # Known starting point of S_nn = <psit_m|S|psit_n> S_nn = self.S0_nn if self.dtype == complex: C_nn = np.random.uniform(size=self.nbands**2) * \ np.exp(1j*np.random.uniform(0,2*np.pi,size=self.nbands**2)) else: C_nn = np.random.normal(size=self.nbands**2) C_nn = C_nn.reshape( (self.nbands, self.nbands)) / np.linalg.norm(C_nn, 2) world.broadcast(C_nn, 0) # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self. async, True) self.psit_nG = overlap.matrix_multiply(C_nn.T.copy(), self.psit_nG, self.P_ani) D_nn = overlap.calculate_matrix_elements(self.psit_nG, \ self.P_ani, S, dS).T.copy() # transpose to get <psit_m|A|psit_n> tri2full(D_nn, 'U') # upper to lower... if self.bd.comm.rank == 0: self.gd.comm.broadcast(D_nn, 0) self.bd.comm.broadcast(D_nn, 0) if memstats: self.mem_test = record_memory() # D_nn = C_nn^dag * S_nn * C_nn D0_nn = np.dot(C_nn.T.conj(), np.dot(S_nn, C_nn)) self.check_and_plot(D_nn, D0_nn, 9, 'multiply,randomized')
def test_trivial_cholesky(self): # Known starting point of SI_nn = <psit_m|S+alpha*I|psit_n> I_nn = np.eye(*self.S0_nn.shape) alpha = 1e-3 # shift eigenvalues away from zero SI_nn = self.S0_nn + alpha * I_nn # Try Cholesky decomposition SI_nn = L_nn * L_nn^dag L_nn = np.linalg.cholesky(SI_nn) # |psit_n> -> C_nn |psit_n> , C_nn^(-1) = L_nn^dag # <psit_m|SI|psit_n> -> <psit_m|C_nn^dag SI C_nn|psit_n> = diag(W_n) C_nn = np.linalg.inv(L_nn.T.conj()) # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self. async, True) self.psit_nG = overlap.matrix_multiply(C_nn.T.copy(), self.psit_nG, self.P_ani) D_nn = overlap.calculate_matrix_elements(self.psit_nG, \ self.P_ani, S, dS).T.copy() # transpose to get <psit_m|A|psit_n> tri2full(D_nn, 'U') # upper to lower... if self.bd.comm.rank == 0: self.gd.comm.broadcast(D_nn, 0) self.bd.comm.broadcast(D_nn, 0) if memstats: self.mem_test = record_memory() # D_nn = C_nn^dag * S_nn * C_nn = I_nn - alpha * C_nn^dag * C_nn D0_nn = I_nn - alpha * np.dot(C_nn.T.conj(), C_nn) self.check_and_plot(D_nn, D0_nn, 6, 'trivial,cholesky') #XXX precision
def test_trivial_cholesky(self): # Known starting point of SI_nn = <psit_m|S+alpha*I|psit_n> I_nn = np.eye(*self.S0_nn.shape) alpha = 1e-3 # shift eigenvalues away from zero SI_nn = self.S0_nn + alpha * I_nn # Try Cholesky decomposition SI_nn = L_nn * L_nn^dag L_nn = np.linalg.cholesky(SI_nn) # |psit_n> -> C_nn |psit_n> , C_nn^(-1) = L_nn^dag # <psit_m|SI|psit_n> -> <psit_m|C_nn^dag SI C_nn|psit_n> = diag(W_n) C_nn = np.linalg.inv(L_nn.T.conj()) # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self.async, True) self.psit_nG = overlap.matrix_multiply(C_nn.T.copy(), self.psit_nG, self.P_ani) D_nn = overlap.calculate_matrix_elements(self.psit_nG, \ self.P_ani, S, dS).T.copy() # transpose to get <psit_m|A|psit_n> tri2full(D_nn, 'U') # upper to lower... if self.bd.comm.rank == 0: self.gd.comm.broadcast(D_nn, 0) self.bd.comm.broadcast(D_nn, 0) if memstats: self.mem_test = record_memory() # D_nn = C_nn^dag * S_nn * C_nn = I_nn - alpha * C_nn^dag * C_nn D0_nn = I_nn - alpha * np.dot(C_nn.T.conj(), C_nn) self.check_and_plot(D_nn, D0_nn, 6, 'trivial,cholesky') #XXX precision
def dump_hamiltonian_parallel(filename, atoms, direction=None): """ Dump the lcao representation of H and S to file(s) beginning with filename. If direction is x, y or z, the periodic boundary conditions will be removed in the specified direction. If the Fermi temperature is different from zero, the energy zero-point is taken as the Fermi level. Note: H and S are parallized over spin and k-points and is for now dumped into a number of pickle files. This may be changed into a dump to a single file in the future. """ if direction != None: d = 'xyz'.index(direction) calc = atoms.calc wfs = calc.wfs nao = wfs.setups.nao nq = len(wfs.kpt_u) // wfs.nspins H_qMM = np.empty((wfs.nspins, nq, nao, nao), wfs.dtype) calc_data = {'k_q':{}, 'skpt_qc':np.empty((nq, 3)), 'weight_q':np.empty(nq)} S_qMM = wfs.S_qMM for kpt in wfs.kpt_u: calc_data['skpt_qc'][kpt.q] = calc.wfs.ibzk_kc[kpt.k] calc_data['weight_q'][kpt.q] = calc.wfs.weight_k[kpt.k] calc_data['k_q'][kpt.q] = kpt.k ## print ('Calc. H matrix on proc. %i: ' ## '(rk, rd, q, k) = (%i, %i, %i, %i)') % ( ## wfs.world.rank, wfs.kpt_comm.rank, ## wfs.gd.domain.comm.rank, kpt.q, kpt.k) H_MM = wfs.eigensolver.calculate_hamiltonian_matrix(calc.hamiltonian, wfs, kpt) H_qMM[kpt.s, kpt.q] = H_MM tri2full(H_qMM[kpt.s, kpt.q]) if kpt.s==0: tri2full(S_qMM[kpt.q]) if direction!=None: remove_pbc(atoms, H_qMM[kpt.s, kpt.q], S_qMM[kpt.q], d) else: if direction is not None: remove_pbc(atoms, H_qMM[kpt.s, kpt.q], None, d) if calc.occupations.width > 0: H_qMM[kpt.s, kpt.q] -= S_qMM[kpt.q] * \ calc.occupations.get_fermi_level() if wfs.gd.comm.rank == 0: fd = file(filename+'%i.pckl' % wfs.kpt_comm.rank, 'wb') H_qMM *= Hartree pickle.dump((H_qMM, S_qMM),fd , 2) pickle.dump(calc_data, fd, 2) fd.close()
def get_lcao_xc(calc, P_aqMi, bfs=None, spin=0): nq = len(calc.wfs.ibzk_qc) nao = calc.wfs.setups.nao dtype = calc.wfs.dtype if bfs is None: bfs = get_bfs(calc) if calc.density.nt_sg is None: calc.density.interpolate() nt_sg = calc.density.nt_sg vxct_sg = calc.density.finegd.zeros(calc.wfs.nspins) calc.hamiltonian.xc.calculate(calc.density.finegd, nt_sg, vxct_sg) vxct_G = calc.wfs.gd.zeros() calc.hamiltonian.restrict(vxct_sg[spin], vxct_G) Vxc_qMM = np.zeros((nq, nao, nao), dtype) for q, Vxc_MM in enumerate(Vxc_qMM): bfs.calculate_potential_matrix(vxct_G, Vxc_MM, q) tri2full(Vxc_MM, "L") # Add atomic PAW corrections for a, P_qMi in P_aqMi.items(): D_sp = calc.density.D_asp[a][:] H_sp = np.zeros_like(D_sp) calc.wfs.setups[a].xc_correction.calculate(calc.hamiltonian.xc, D_sp, H_sp) H_ii = unpack(H_sp[spin]) for Vxc_MM, P_Mi in zip(Vxc_qMM, P_qMi): Vxc_MM += dots(P_Mi, H_ii, P_Mi.T.conj()) return Vxc_qMM * Hartree
def calculate_density_matrix(self, f_n, C_nM, rho_MM=None): # ATLAS can't handle uninitialized output array: #rho_MM.fill(42) self.timer.start('Calculate density matrix') rho_MM = self.ksl.calculate_density_matrix(f_n, C_nM, rho_MM) self.timer.stop('Calculate density matrix') return rho_MM # ---------------------------- if 1: # XXX Should not conjugate, but call gemm(..., 'c') # Although that requires knowing C_Mn and not C_nM. # that also conforms better to the usual conventions in literature Cf_Mn = C_nM.T.conj() * f_n self.timer.start('gemm') gemm(1.0, C_nM, Cf_Mn, 0.0, rho_MM, 'n') self.timer.stop('gemm') self.timer.start('band comm sum') self.bd.comm.sum(rho_MM) self.timer.stop('band comm sum') else: # Alternative suggestion. Might be faster. Someone should test this from gpaw.utilities.blas import r2k C_Mn = C_nM.T.copy() r2k(0.5, C_Mn, f_n * C_Mn, 0.0, rho_MM) tri2full(rho_MM)
def get_lcao_hamiltonian(calc): """Return H_skMM, S_kMM on master, (None, None) on slaves. H is in eV.""" if calc.wfs.S_qMM is None: calc.wfs.set_positions(calc.get_atoms().get_scaled_positions() % 1) dtype = calc.wfs.dtype NM = calc.wfs.eigensolver.nao Nk = calc.wfs.kd.nibzkpts Ns = calc.wfs.nspins S_kMM = np.zeros((Nk, NM, NM), dtype) H_skMM = np.zeros((Ns, Nk, NM, NM), dtype) for kpt in calc.wfs.kpt_u: H_MM = calc.wfs.eigensolver.calculate_hamiltonian_matrix( calc.hamiltonian, calc.wfs, kpt) if kpt.s == 0: S_kMM[kpt.k] = calc.wfs.S_qMM[kpt.q] tri2full(S_kMM[kpt.k]) H_skMM[kpt.s, kpt.k] = H_MM * Hartree tri2full(H_skMM[kpt.s, kpt.k]) calc.wfs.kd.comm.sum(S_kMM, MASTER) calc.wfs.kd.comm.sum(H_skMM, MASTER) if rank == MASTER: return H_skMM, S_kMM else: return None, None
def test_overlaps_hermitian(self): # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self.async, True) S_nn = overlap.calculate_matrix_elements(self.psit_nG, self.P_ani, S, dS) if memstats: self.mem_test = record_memory() S_NN = self.ksl.nndescriptor.collect_on_master(S_nn) if self.bd.comm.rank == 0 and self.gd.comm.rank == 0: assert S_NN.shape == (self.bd.nbands,) * 2 S_NN = S_NN.T.copy() # Fortran -> C indexing tri2full(S_NN, 'U') # upper to lower... else: assert S_NN.nbytes == 0 S_NN = np.empty((self.bd.nbands,) * 2, dtype=S_NN.dtype) if self.bd.comm.rank == 0: self.gd.comm.broadcast(S_NN, 0) self.bd.comm.broadcast(S_NN, 0) self.check_and_plot(S_NN, self.S0_nn, 9, 'overlaps,hermitian')
def test_multiply_randomized(self): # Known starting point of S_nn = <psit_m|S|psit_n> S_nn = self.S0_nn if self.dtype == complex: C_nn = np.random.uniform(size=self.nbands**2) * \ np.exp(1j*np.random.uniform(0,2*np.pi,size=self.nbands**2)) else: C_nn = np.random.normal(size=self.nbands**2) C_nn = C_nn.reshape((self.nbands,self.nbands)) / np.linalg.norm(C_nn,2) world.broadcast(C_nn, 0) # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self.async, True) self.psit_nG = overlap.matrix_multiply(C_nn.T.copy(), self.psit_nG, self.P_ani) D_nn = overlap.calculate_matrix_elements(self.psit_nG, \ self.P_ani, S, dS).T.copy() # transpose to get <psit_m|A|psit_n> tri2full(D_nn, 'U') # upper to lower... if self.bd.comm.rank == 0: self.gd.comm.broadcast(D_nn, 0) self.bd.comm.broadcast(D_nn, 0) if memstats: self.mem_test = record_memory() # D_nn = C_nn^dag * S_nn * C_nn D0_nn = np.dot(C_nn.T.conj(), np.dot(S_nn, C_nn)) self.check_and_plot(D_nn, D0_nn, 9, 'multiply,randomized')
def get_vxc(paw, spin=0, U=None): """Calculate matrix elements of the xc-potential.""" assert not paw.hamiltonian.xc.xcfunc.orbital_dependent, "LDA/GGA's only" assert paw.wfs.dtype == float, 'Complex waves not implemented' if U is not None: # Rotate xc matrix return np.dot(U.T.conj(), np.dot(get_vxc(paw, spin), U)) gd = paw.hamiltonian.gd psit_nG = paw.wfs.kpt_u[spin].psit_nG[:] if paw.density.nt_sg is None: paw.density.interpolate_pseudo_density() nt_g = paw.density.nt_sg[spin] vxct_g = paw.density.finegd.zeros() paw.hamiltonian.xc.get_energy_and_potential(nt_g, vxct_g) vxct_G = gd.empty() paw.hamiltonian.restrict(vxct_g, vxct_G) Vxc_nn = np.zeros((paw.wfs.bd.nbands, paw.wfs.bd.nbands)) # Apply pseudo part r2k(.5 * gd.dv, psit_nG, vxct_G * psit_nG, .0, Vxc_nn) # lower triangle tri2full(Vxc_nn, 'L') # Fill in upper triangle from lower gd.comm.sum(Vxc_nn) # Add atomic PAW corrections for a, P_ni in paw.wfs.kpt_u[spin].P_ani.items(): D_sp = paw.density.D_asp[a][:] H_sp = np.zeros_like(D_sp) paw.wfs.setups[a].xc_correction.calculate_energy_and_derivatives( D_sp, H_sp) H_ii = unpack(H_sp[spin]) Vxc_nn += np.dot(P_ni, np.dot(H_ii, P_ni.T)) return Vxc_nn * Hartree
def get_lcao_xc(calc, P_aqMi, bfs=None, spin=0): nq = len(calc.wfs.kd.ibzk_qc) nao = calc.wfs.setups.nao dtype = calc.wfs.dtype if bfs is None: bfs = get_bfs(calc) if calc.density.nt_sg is None: calc.density.interpolate_pseudo_density() nt_sg = calc.density.nt_sg vxct_sg = calc.density.finegd.zeros(calc.wfs.nspins) calc.hamiltonian.xc.calculate(calc.density.finegd, nt_sg, vxct_sg) vxct_G = calc.wfs.gd.zeros() calc.hamiltonian.restrict_and_collect(vxct_sg[spin], vxct_G) Vxc_qMM = np.zeros((nq, nao, nao), dtype) for q, Vxc_MM in enumerate(Vxc_qMM): bfs.calculate_potential_matrix(vxct_G, Vxc_MM, q) tri2full(Vxc_MM, 'L') # Add atomic PAW corrections for a, P_qMi in P_aqMi.items(): D_sp = calc.density.D_asp[a][:] H_sp = np.zeros_like(D_sp) calc.hamiltonian.xc.calculate_paw_correction(calc.wfs.setups[a], D_sp, H_sp) H_ii = unpack(H_sp[spin]) for Vxc_MM, P_Mi in zip(Vxc_qMM, P_qMi): Vxc_MM += dots(P_Mi, H_ii, P_Mi.T.conj()) return Vxc_qMM * Hartree
def get_lcao_hamiltonian(calc): """Return H_skMM, S_kMM on master, (None, None) on slaves. H is in eV.""" if calc.wfs.S_qMM is None: calc.wfs.set_positions(calc.get_atoms().get_scaled_positions() % 1) dtype = calc.wfs.dtype NM = calc.wfs.eigensolver.nao Nk = calc.wfs.nibzkpts Ns = calc.wfs.nspins S_kMM = np.zeros((Nk, NM, NM), dtype) H_skMM = np.zeros((Ns, Nk, NM, NM), dtype) for kpt in calc.wfs.kpt_u: H_MM = calc.wfs.eigensolver.calculate_hamiltonian_matrix( calc.hamiltonian, calc.wfs, kpt) if kpt.s == 0: S_kMM[kpt.k] = calc.wfs.S_qMM[kpt.q] tri2full(S_kMM[kpt.k]) H_skMM[kpt.s, kpt.k] = H_MM * Hartree tri2full(H_skMM[kpt.s, kpt.k]) calc.wfs.kpt_comm.sum(S_kMM, MASTER) calc.wfs.kpt_comm.sum(H_skMM, MASTER) if rank == MASTER: return H_skMM, S_kMM else: return None, None
def test_overlaps_hermitian(self): # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self. async, True) S_nn = overlap.calculate_matrix_elements(self.psit_nG, self.P_ani, S, dS) if memstats: self.mem_test = record_memory() S_NN = self.ksl.nndescriptor.collect_on_master(S_nn) if self.bd.comm.rank == 0 and self.gd.comm.rank == 0: assert S_NN.shape == (self.bd.nbands, ) * 2 S_NN = S_NN.T.copy() # Fortran -> C indexing tri2full(S_NN, 'U') # upper to lower... else: assert S_NN.nbytes == 0 S_NN = np.empty((self.bd.nbands, ) * 2, dtype=S_NN.dtype) if self.bd.comm.rank == 0: self.gd.comm.broadcast(S_NN, 0) self.bd.comm.broadcast(S_NN, 0) self.check_and_plot(S_NN, self.S0_nn, 9, 'overlaps,hermitian')
def inverse_symmetric(a): assert a.dtype in [float, complex] n = len(a) assert a.shape == (n, n) info = _gpaw.inverse_symmetric(a) tri2full(a, 'L', 'symm') if info != 0: raise RuntimeError('inverse_symmetric: %d' % info)
def alternative_calculate_density_matrix(self, f_n, C_nM, rho_MM=None): if rho_MM is None: rho_MM = np.zeros((self.mynao, self.nao), dtype=C_nM.dtype) # Alternative suggestion. Might be faster. Someone should test this C_Mn = C_nM.T.copy() r2k(0.5, C_Mn, f_n * C_Mn, 0.0, rho_MM) tri2full(rho_MM) return rho_MM
def test_trivial_diagonalize(self): # Known starting point of S_nn = <psit_m|S|psit_n> S_nn = self.S0_nn # Eigenvector decomposition S_nn = V_nn * W_nn * V_nn^dag # Utilize the fact that they are analytically known (cf. Maple) band_indices = np.arange(self.nbands) V_nn = np.eye(self.nbands).astype(self.dtype) if self.dtype == complex: V_nn[1:,1] = np.conj(self.gamma)**band_indices[1:] * band_indices[1:]**0.5 V_nn[1,2:] = -self.gamma**band_indices[1:-1] * band_indices[2:]**0.5 else: V_nn[2:,1] = band_indices[2:]**0.5 V_nn[1,2:] = -band_indices[2:]**0.5 W_n = np.zeros(self.nbands).astype(self.dtype) W_n[1] = (1. + self.Qtotal) * self.nbands * (self.nbands - 1) / 2. # Find the inverse basis Vinv_nn = np.linalg.inv(V_nn) # Test analytical eigenvectors for consistency against analytical S_nn D_nn = np.dot(Vinv_nn, np.dot(S_nn, V_nn)) self.assertAlmostEqual(np.abs(D_nn.diagonal()-W_n).max(), 0, 8) self.assertAlmostEqual(np.abs(np.tril(D_nn, -1)).max(), 0, 4) self.assertAlmostEqual(np.abs(np.triu(D_nn, 1)).max(), 0, 4) del Vinv_nn, D_nn # Perform Gram Schmidt orthonormalization for diagonalization # |psit_n> -> C_nn |psit_n>, using orthonormalized basis Q_nn # <psit_m|S|psit_n> -> <psit_m|C_nn^dag S C_nn|psit_n> = diag(W_n) # using S_nn = V_nn * W_nn * V_nn^(-1) = Q_nn * W_nn * Q_nn^dag C_nn = V_nn.copy() gram_schmidt(C_nn) self.assertAlmostEqual(np.abs(np.dot(C_nn.T.conj(), C_nn) \ - np.eye(self.nbands)).max(), 0, 6) # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self.async, True) self.psit_nG = overlap.matrix_multiply(C_nn.T.copy(), self.psit_nG, self.P_ani) D_nn = overlap.calculate_matrix_elements(self.psit_nG, \ self.P_ani, S, dS).T.copy() # transpose to get <psit_m|A|psit_n> tri2full(D_nn, 'U') # upper to lower... if self.bd.comm.rank == 0: self.gd.comm.broadcast(D_nn, 0) self.bd.comm.broadcast(D_nn, 0) if memstats: self.mem_test = record_memory() # D_nn = C_nn^dag * S_nn * C_nn = W_n since Q_nn^dag = Q_nn^(-1) D0_nn = np.dot(C_nn.T.conj(), np.dot(S_nn, C_nn)) self.assertAlmostEqual(np.abs(D0_nn-np.diag(W_n)).max(), 0, 9) self.check_and_plot(D_nn, D0_nn, 9, 'trivial,diagonalize')
def dscf_matrix_elements(paw, kpt, A, dA): operator = paw.wfs.overlap.operator A_nn = operator.calculate_matrix_elements(kpt.psit_nG, kpt.P_ani, \ A, dA).T.copy() # transpose to get A_nn[m,n] = <m|A|n> tri2full(A_nn, 'U') # fill in from upper to lower... # Calculate <o|A|o'> = sum_nn' <o|n><n|A|n'><n'|o'> where c_on = <n|o> A_oo = np.dot(kpt.c_on.conj(), np.dot(A_nn, kpt.c_on.T)) return A_oo, A_nn
def derivative(self, spos_ac, dThetadR_qcMM, dTdR_qcMM, dPdR_aqcMi): calc = TwoCenterIntegralCalculator(self.ibzk_qc, derivative=True) self._calculate(calc, spos_ac, dThetadR_qcMM, dTdR_qcMM, dPdR_aqcMi) def antihermitian(src, dst): np.conj(-src, dst) if not self.blacs: for X_cMM in list(dThetadR_qcMM) + list(dTdR_qcMM): for X_MM in X_cMM: tri2full(X_MM, UL=UL, map=antihermitian)
def test_multiply_randomized(self): # Known starting point of S_nn = <psit_m|S|psit_n> S_NN = self.S0_nn if self.dtype == complex: C_NN = np.random.uniform(size=self.nbands**2) * \ np.exp(1j*np.random.uniform(0,2*np.pi,size=self.nbands**2)) else: C_NN = np.random.normal(size=self.nbands**2) C_NN = C_NN.reshape( (self.nbands, self.nbands)) / np.linalg.norm(C_NN, 2) world.broadcast(C_NN, 0) # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self. async, True) if self.bd.comm.rank == 0 and self.gd.comm.rank == 0: assert C_NN.shape == (self.bd.nbands, ) * 2 tmp_NN = C_NN.T.copy() # C -> Fortran indexing else: tmp_NN = self.ksl.nndescriptor.as_serial().empty(dtype=C_NN.dtype) C_nn = self.ksl.nndescriptor.distribute_from_master(tmp_NN) self.psit_nG = overlap.matrix_multiply(C_nn, self.psit_nG, self.P_ani) D_nn = overlap.calculate_matrix_elements(self.psit_nG, self.P_ani, S, dS) if memstats: self.mem_test = record_memory() D_NN = self.ksl.nndescriptor.collect_on_master(D_nn) if self.bd.comm.rank == 0 and self.gd.comm.rank == 0: assert D_NN.shape == (self.bd.nbands, ) * 2 D_NN = D_NN.T.copy() # Fortran -> C indexing tri2full(D_NN, 'U') # upper to lower... else: assert D_NN.nbytes == 0 D_NN = np.empty((self.bd.nbands, ) * 2, dtype=D_NN.dtype) if self.bd.comm.rank == 0: self.gd.comm.broadcast(D_NN, 0) self.bd.comm.broadcast(D_NN, 0) # D_nn = C_nn^dag * S_nn * C_nn D0_NN = np.dot(C_NN.T.conj(), np.dot(S_NN, C_NN)) self.check_and_plot(D_NN, D0_NN, 9, 'multiply,randomized')
def get_vxc(self, density, wfs): """Calculate matrix elements of the xc-potential.""" dtype = wfs.dtype nbands = wfs.nbands nu = len(wfs.kpt_u) if density.nt_sg is None: density.interpolate() # Allocate space for result matrix Vxc_unn = np.empty((nu, nbands, nbands), dtype=dtype) # Get pseudo xc potential on the coarse grid Vxct_sG = self.gd.empty(self.nspins) Vxct_sg = self.finegd.zeros(self.nspins) if nspins == 1: self.xc.get_energy_and_potential(density.nt_sg[0], Vxct_sg[0]) else: self.xc.get_energy_and_potential(density.nt_sg[0], Vxct_sg[0], density.nt_sg[1], Vxct_sg[1]) for Vxct_G, Vxct_g in zip(Vxct_sG, Vxct_sg): self.restrict(Vxct_g, Vxct_G) del Vxct_sg # Get atomic corrections to the xc potential Vxc_asp = {} for a, D_sp in density.D_asp.items(): Vxc_asp[a] = np.zeros_like(D_sp) self.setups[a].xc_correction.calculate_energy_and_derivatives( D_sp, Vxc_asp[a]) # Project potential onto the eigenstates for kpt, Vxc_nn in xip(wfs.kpt_u, Vxc_unn): s, q = kpt.s, kpt.q psit_nG = kpt.psit_nG # Project pseudo part r2k(.5 * self.gd.dv, psit_nG, Vxct_sG[s] * psit_nG, 0.0, Vxc_nn) tri2full(Vxc_nn, 'L') self.gd.comm.sum(Vxc_nn) # Add atomic corrections # H_ij = \int dr phi_i(r) Ĥ phi_j^*(r) # P_ni = \int dr psi_n(r) pt_i^*(r) # Vxc_nm = \int dr phi_n(r) vxc(r) phi_m^*(r) # + sum_ij P_ni H_ij P_mj^* for a, P_ni in kpt.P_ani.items(): Vxc_ii = unpack(Vxc_asp[a][s]) Vxc_nn += np.dot(P_ni, np.inner(H_ii, P_ni).conj()) return Vxc_unn
def soft_pseudo(self, paw, H_nn, h_nn=None, u=0): if h_nn is None: h_nn = H_nn kpt = paw.wfs.kpt_u[u] pd = self.pair_density deg = 2 / self.nspins fmin = 1e-9 Htpsit_nG = np.zeros(kpt.psit_nG.shape, self.dtype) for n1 in range(self.nbands): psit1_G = kpt.psit_nG[n1] f1 = kpt.f_n[n1] / deg for n2 in range(n1, self.nbands): psit2_G = kpt.psit_nG[n2] f2 = kpt.f_n[n2] / deg if f1 < fmin and f2 < fmin: continue pd.initialize(kpt, n1, n2) pd.get_coarse(self.nt_G) pd.add_compensation_charges(self.nt_G, self.rhot_g) self.poisson_solve(self.vt_g, -self.rhot_g, charge=-float(n1 == n2), eps=1e-12, zero_initial_phi=True) self.restrict(self.vt_g, self.vt_G) Htpsit_nG[n1] += f2 * self.vt_G * psit2_G if n1 != n2: Htpsit_nG[n2] += f1 * self.vt_G * psit1_G v_aL = paw.density.ghat.dict() paw.density.ghat.integrate(self.vt_g, v_aL) for a, v_L in v_aL.items(): v_ii = unpack(np.dot(paw.wfs.setups[a].Delta_pL, v_L)) P_ni = kpt.P_ani[a] h_nn[:, n1] += f2 * np.dot(P_ni, np.dot(v_ii, P_ni[n2])) if n1 != n2: h_nn[:, n2] += f1 * np.dot(P_ni, np.dot(v_ii, P_ni[n1])) symmetrize(h_nn) # Grrrr why!!! XXX # Fill in lower triangle r2k(0.5 * self.dv, kpt.psit_nG[:], Htpsit_nG, 1.0, H_nn) # Fill in upper triangle from lower tri2full(H_nn, 'L')
def test_trivial_diagonalize(self): #XXX XXX XXX # Known starting point of S_nn = <psit_m|S|psit_n> S_nn = self.S0_nn # Eigenvector decomposition S_nn = V_nn * W_nn * V_nn^dag # Utilize the fact that they are analytically known (cf. Maple) W_n = np.zeros(self.nbands).astype(self.dtype) W_n[1] = (1. + self.Qtotal) * self.nbands * (self.nbands - 1) / 2. # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self. async, True) S_nn = overlap.calculate_matrix_elements(self.psit_nG, self.P_ani, S, dS) eps_N = self.bd.empty(global_array=True) # XXX dtype? C_nn = self.ksl.nndescriptor.empty(dtype=S_nn.dtype) self.ksl.nndescriptor.diagonalize_dc(S_nn, C_nn, eps_N, 'L') self.assertAlmostEqual( np.abs(np.sort(eps_N) - np.sort(W_n)).max(), 0, 9) #eps_n = self.bd.empty() #self.bd.distribute(eps_N, eps_n) # XXX only blocked groups, right? # Rotate wavefunctions to diagonalize the overlap self.psit_nG = overlap.matrix_multiply(C_nn, self.psit_nG, self.P_ani) # Recaulculate the overlap matrix, which should now be diagonal D_nn = overlap.calculate_matrix_elements(self.psit_nG, self.P_ani, S, dS) D_NN = self.ksl.nndescriptor.collect_on_master(D_nn) if self.bd.comm.rank == 0 and self.gd.comm.rank == 0: assert D_NN.shape == (self.bd.nbands, ) * 2 D_NN = D_NN.T.copy() # Fortran -> C indexing tri2full(D_NN, 'U') # upper to lower... else: assert D_NN.nbytes == 0 D_NN = np.empty((self.bd.nbands, ) * 2, dtype=D_NN.dtype) if self.bd.comm.rank == 0: self.gd.comm.broadcast(D_NN, 0) self.bd.comm.broadcast(D_NN, 0) D0_NN = np.diag(eps_N) self.check_and_plot(D_NN, D0_NN, 9, 'trivial,diagonalize')
def test_multiply_randomized(self): # Known starting point of S_nn = <psit_m|S|psit_n> S_NN = self.S0_nn if self.dtype == complex: C_NN = np.random.uniform(size=self.nbands**2) * \ np.exp(1j*np.random.uniform(0,2*np.pi,size=self.nbands**2)) else: C_NN = np.random.normal(size=self.nbands**2) C_NN = C_NN.reshape((self.nbands,self.nbands)) / np.linalg.norm(C_NN,2) world.broadcast(C_NN, 0) # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self.async, True) if self.bd.comm.rank == 0 and self.gd.comm.rank == 0: assert C_NN.shape == (self.bd.nbands,) * 2 tmp_NN = C_NN.T.copy() # C -> Fortran indexing else: tmp_NN = self.ksl.nndescriptor.as_serial().empty(dtype=C_NN.dtype) C_nn = self.ksl.nndescriptor.distribute_from_master(tmp_NN) self.psit_nG = overlap.matrix_multiply(C_nn, self.psit_nG, self.P_ani) D_nn = overlap.calculate_matrix_elements(self.psit_nG, self.P_ani, S, dS) if memstats: self.mem_test = record_memory() D_NN = self.ksl.nndescriptor.collect_on_master(D_nn) if self.bd.comm.rank == 0 and self.gd.comm.rank == 0: assert D_NN.shape == (self.bd.nbands,) * 2 D_NN = D_NN.T.copy() # Fortran -> C indexing tri2full(D_NN, 'U') # upper to lower... else: assert D_NN.nbytes == 0 D_NN = np.empty((self.bd.nbands,) * 2, dtype=D_NN.dtype) if self.bd.comm.rank == 0: self.gd.comm.broadcast(D_NN, 0) self.bd.comm.broadcast(D_NN, 0) # D_nn = C_nn^dag * S_nn * C_nn D0_NN = np.dot(C_NN.T.conj(), np.dot(S_NN, C_NN)) self.check_and_plot(D_NN, D0_NN, 9, 'multiply,randomized')
def test_trivial_diagonalize(self): #XXX XXX XXX # Known starting point of S_nn = <psit_m|S|psit_n> S_nn = self.S0_nn # Eigenvector decomposition S_nn = V_nn * W_nn * V_nn^dag # Utilize the fact that they are analytically known (cf. Maple) W_n = np.zeros(self.nbands).astype(self.dtype) W_n[1] = (1. + self.Qtotal) * self.nbands * (self.nbands - 1) / 2. # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self.async, True) S_nn = overlap.calculate_matrix_elements(self.psit_nG, self.P_ani, S, dS) eps_N = self.bd.empty(global_array=True) # XXX dtype? C_nn = self.ksl.nndescriptor.empty(dtype=S_nn.dtype) self.ksl.nndescriptor.diagonalize_dc(S_nn, C_nn, eps_N, 'L') self.assertAlmostEqual(np.abs(np.sort(eps_N)-np.sort(W_n)).max(), 0, 9) #eps_n = self.bd.empty() #self.bd.distribute(eps_N, eps_n) # XXX only blocked groups, right? # Rotate wavefunctions to diagonalize the overlap self.psit_nG = overlap.matrix_multiply(C_nn, self.psit_nG, self.P_ani) # Recaulculate the overlap matrix, which should now be diagonal D_nn = overlap.calculate_matrix_elements(self.psit_nG, self.P_ani, S, dS) D_NN = self.ksl.nndescriptor.collect_on_master(D_nn) if self.bd.comm.rank == 0 and self.gd.comm.rank == 0: assert D_NN.shape == (self.bd.nbands,) * 2 D_NN = D_NN.T.copy() # Fortran -> C indexing tri2full(D_NN, 'U') # upper to lower... else: assert D_NN.nbytes == 0 D_NN = np.empty((self.bd.nbands,) * 2, dtype=D_NN.dtype) if self.bd.comm.rank == 0: self.gd.comm.broadcast(D_NN, 0) self.bd.comm.broadcast(D_NN, 0) D0_NN = np.diag(eps_N) self.check_and_plot(D_NN, D0_NN, 9, 'trivial,diagonalize')
def test_overlaps_hermitian(self): # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self.async, True) S_nn = overlap.calculate_matrix_elements(self.psit_nG, \ self.P_ani, S, dS).T.copy() # transpose to get <psit_m|A|psit_n> tri2full(S_nn, 'U') # upper to lower... if self.bd.comm.rank == 0: self.gd.comm.broadcast(S_nn, 0) self.bd.comm.broadcast(S_nn, 0) if memstats: self.mem_test = record_memory() self.check_and_plot(S_nn, self.S0_nn, 9, 'overlaps,hermitian')
def test_overlaps_hermitian(self): # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self. async, True) S_nn = overlap.calculate_matrix_elements(self.psit_nG, \ self.P_ani, S, dS).T.copy() # transpose to get <psit_m|A|psit_n> tri2full(S_nn, 'U') # upper to lower... if self.bd.comm.rank == 0: self.gd.comm.broadcast(S_nn, 0) self.bd.comm.broadcast(S_nn, 0) if memstats: self.mem_test = record_memory() self.check_and_plot(S_nn, self.S0_nn, 9, 'overlaps,hermitian')
def get_component(self, wfs, s, vt_sG, dH_asp, kpt, H_MM): wfs.basis_functions.calculate_potential_matrix(vt_sG[s], H_MM, kpt.q) # Add atomic contribution # # -- a a a* # H += > P dH P # mu nu -- mu i ij nu j # aij # wfs.timer.start('Atomic Hamiltonian') Mstart = wfs.basis_functions.Mstart Mstop = wfs.basis_functions.Mstop wfs.timer.stop('Atomic Hamiltonian') tri2full(H_MM) for a, P_Mi in kpt.P_aMi.items(): dH_ii = np.asarray(unpack(dH_asp[a][s]), wfs.dtype) dHP_iM = np.zeros((dH_ii.shape[1], P_Mi.shape[0]), wfs.dtype) # (ATLAS can't handle uninitialized output array) gemm(1.0, P_Mi, dH_ii, 0.0, dHP_iM, 'c') gemm(1.0, dHP_iM, P_Mi[Mstart:Mstop], 1.0, H_MM)
def test_trivial_cholesky(self): # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self. async, True) S_nn = overlap.calculate_matrix_elements(self.psit_nG, self.P_ani, S, dS) # Known starting point of SI_nn = <psit_m|S+alpha*I|psit_n> I_nn = self.ksl.nndescriptor.empty(dtype=S_nn.dtype) scalapack_set(self.ksl.nndescriptor, I_nn, 0.0, 1.0, 'L') alpha = 1e-3 # shift eigenvalues away from zero C_nn = S_nn + alpha * I_nn self.ksl.nndescriptor.inverse_cholesky(C_nn, 'L') self.psit_nG = overlap.matrix_multiply(C_nn, self.psit_nG, self.P_ani) D_nn = overlap.calculate_matrix_elements(self.psit_nG, self.P_ani, S, dS) D_NN = self.ksl.nndescriptor.collect_on_master(D_nn) if self.bd.comm.rank == 0 and self.gd.comm.rank == 0: assert D_NN.shape == (self.bd.nbands, ) * 2 D_NN = D_NN.T.copy() # Fortran -> C indexing tri2full(D_NN, 'U') # upper to lower.. else: assert D_NN.nbytes == 0 D_NN = np.empty((self.bd.nbands, ) * 2, dtype=D_NN.dtype) if self.bd.comm.rank == 0: self.gd.comm.broadcast(D_NN, 0) self.bd.comm.broadcast(D_NN, 0) # D_NN = C_NN^dag * S_NN * C_NN = I_NN - alpha * C_NN^dag * C_NN I_NN = np.eye(self.bd.nbands) C0_NN = np.linalg.inv( np.linalg.cholesky(self.S0_nn + alpha * I_NN).T.conj()) D0_NN = I_NN - alpha * np.dot(C0_NN.T.conj(), C0_NN) self.check_and_plot(D_NN, D0_NN, 6, 'trivial,cholesky') #XXX precision
def get_xc2(calc, w_wG, P_awi, spin=0): if calc.density.nt_sg is None: calc.density.interpolate() nt_g = calc.density.nt_sg[spin] vxct_g = calc.density.finegd.zeros() calc.hamiltonian.xc.get_energy_and_potential(nt_g, vxct_g) vxct_G = calc.wfs.gd.empty() calc.hamiltonian.restrict(vxct_g, vxct_G) # Integrate pseudo part Nw = len(w_wG) xc_ww = np.empty((Nw, Nw)) r2k(0.5 * calc.wfs.gd.dv, w_wG, vxct_G * w_wG, 0.0, xc_ww) tri2full(xc_ww, "L") # Add atomic PAW corrections for a, P_wi in P_awi.items(): D_sp = calc.density.D_asp[a][:] H_sp = np.zeros_like(D_sp) calc.wfs.setups[a].xc_correction.calculate_energy_and_derivatives(D_sp, H_sp) H_ii = unpack(H_sp[spin]) xc_ww += dots(P_wi, H_ii, P_wi.T.conj()) return xc_ww * Hartree
def get_xc2(calc, w_wG, P_awi, spin=0): if calc.density.nt_sg is None: calc.density.interpolate_pseudo_density() nt_g = calc.density.nt_sg[spin] vxct_g = calc.density.finegd.zeros() calc.hamiltonian.xc.get_energy_and_potential(nt_g, vxct_g) vxct_G = calc.wfs.gd.empty() calc.hamiltonian.restrict_and_collect(vxct_g, vxct_G) # Integrate pseudo part Nw = len(w_wG) xc_ww = np.empty((Nw, Nw)) r2k(.5 * calc.wfs.gd.dv, w_wG, vxct_G * w_wG, .0, xc_ww) tri2full(xc_ww, 'L') # Add atomic PAW corrections for a, P_wi in P_awi.items(): D_sp = calc.density.D_asp[a][:] H_sp = np.zeros_like(D_sp) calc.wfs.setups[a].xc_correction.calculate_energy_and_derivatives( D_sp, H_sp) H_ii = unpack(H_sp[spin]) xc_ww += dots(P_wi, H_ii, P_wi.T.conj()) return xc_ww * Hartree
def test_trivial_cholesky(self): # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self.async, True) S_nn = overlap.calculate_matrix_elements(self.psit_nG, self.P_ani, S, dS) # Known starting point of SI_nn = <psit_m|S+alpha*I|psit_n> I_nn = self.ksl.nndescriptor.empty(dtype=S_nn.dtype) scalapack_set(self.ksl.nndescriptor, I_nn, 0.0, 1.0, 'L') alpha = 1e-3 # shift eigenvalues away from zero C_nn = S_nn + alpha * I_nn self.ksl.nndescriptor.inverse_cholesky(C_nn, 'L') self.psit_nG = overlap.matrix_multiply(C_nn, self.psit_nG, self.P_ani) D_nn = overlap.calculate_matrix_elements(self.psit_nG, self.P_ani, S, dS) D_NN = self.ksl.nndescriptor.collect_on_master(D_nn) if self.bd.comm.rank == 0 and self.gd.comm.rank == 0: assert D_NN.shape == (self.bd.nbands,) * 2 D_NN = D_NN.T.copy() # Fortran -> C indexing tri2full(D_NN, 'U') # upper to lower.. else: assert D_NN.nbytes == 0 D_NN = np.empty((self.bd.nbands,) * 2, dtype=D_NN.dtype) if self.bd.comm.rank == 0: self.gd.comm.broadcast(D_NN, 0) self.bd.comm.broadcast(D_NN, 0) # D_NN = C_NN^dag * S_NN * C_NN = I_NN - alpha * C_NN^dag * C_NN I_NN = np.eye(self.bd.nbands) C0_NN = np.linalg.inv(np.linalg.cholesky(self.S0_nn + alpha*I_NN).T.conj()) D0_NN = I_NN - alpha * np.dot(C0_NN.T.conj(), C0_NN) self.check_and_plot(D_NN, D0_NN, 6, 'trivial,cholesky') #XXX precision
def h_and_s(calc): """Return LCAO Hamiltonian and overlap matrix in fourier-space.""" # Extract Bloch Hamiltonian and overlap matrix H_kMM = [] S_kMM = [] h = calc.hamiltonian wfs = calc.wfs kpt_u = wfs.kpt_u for kpt in kpt_u: H_MM = wfs.eigensolver.calculate_hamiltonian_matrix(h, wfs, kpt) S_MM = wfs.S_qMM[kpt.q] #XXX Converting to full matrices here tri2full(H_MM) tri2full(S_MM) H_kMM.append(H_MM)# * Hartree) S_kMM.append(S_MM) # Convert to arrays H_kMM = np.array(H_kMM) S_kMM = np.array(S_kMM) return H_kMM, S_kMM
def calculate_forces_by_kpoint(self, kpt, hamiltonian, F_av, tci, P_aqMi, dThetadR_vMM, dTdR_vMM, dPdR_aqvMi): k = kpt.k q = kpt.q mynao = self.ksl.mynao nao = self.ksl.nao dtype = self.dtype Mstart = self.ksl.Mstart Mstop = self.ksl.Mstop basis_functions = self.basis_functions my_atom_indices = basis_functions.my_atom_indices atom_indices = basis_functions.atom_indices def _slices(indices): for a in indices: M1 = basis_functions.M_a[a] - Mstart M2 = M1 + self.setups[a].niAO yield a, M1, M2 def slices(): return _slices(atom_indices) def my_slices(): return _slices(my_atom_indices) # # ----- ----- # \ -1 \ * # E = ) S H rho = ) c eps f c # mu nu / mu x x z z nu / n mu n n n nu # ----- ----- # x z n # # We use the transpose of that matrix. The first form is used # if rho is given, otherwise the coefficients are used. self.timer.start('LCAO forces: initial') if kpt.rho_MM is None: rhoT_MM = self.ksl.get_transposed_density_matrix(kpt.f_n, kpt.C_nM) ET_MM = self.ksl.get_transposed_density_matrix(kpt.f_n * kpt.eps_n, kpt.C_nM) if hasattr(kpt, 'c_on'): assert self.bd.comm.size == 1 d_nn = np.zeros((self.bd.mynbands, self.bd.mynbands), dtype=kpt.C_nM.dtype) for ne, c_n in zip(kpt.ne_o, kpt.c_on): d_nn += ne * np.outer(c_n.conj(), c_n) rhoT_MM += self.ksl.get_transposed_density_matrix_delta(d_nn, kpt.C_nM) ET_MM+=self.ksl.get_transposed_density_matrix_delta(d_nn*kpt.eps_n, kpt.C_nM) else: H_MM = self.eigensolver.calculate_hamiltonian_matrix(hamiltonian, self, kpt) tri2full(H_MM) S_MM = self.S_qMM[q].copy() tri2full(S_MM) ET_MM = np.linalg.solve(S_MM, gemmdot(H_MM, kpt.rho_MM)).T.copy() del S_MM, H_MM rhoT_MM = kpt.rho_MM.T.copy() self.timer.stop('LCAO forces: initial') # Kinetic energy contribution # # ----- d T # a \ mu nu # F += 2 Re ) -------- rho # / d R nu mu # ----- mu nu # mu in a; nu # Fkin_av = np.zeros_like(F_av) dEdTrhoT_vMM = (dTdR_vMM * rhoT_MM[np.newaxis]).real for a, M1, M2 in my_slices(): Fkin_av[a, :] = 2 * dEdTrhoT_vMM[:, M1:M2].sum(-1).sum(-1) del dEdTrhoT_vMM # Potential contribution # # ----- / d Phi (r) # a \ | mu ~ # F += -2 Re ) | ---------- v (r) Phi (r) dr rho # / | d R nu nu mu # ----- / a # mu in a; nu # self.timer.start('LCAO forces: potential') Fpot_av = np.zeros_like(F_av) vt_G = hamiltonian.vt_sG[kpt.s] DVt_vMM = np.zeros((3, mynao, nao), dtype) # Note that DVt_vMM contains dPhi(r) / dr = - dPhi(r) / dR^a basis_functions.calculate_potential_matrix_derivative(vt_G, DVt_vMM, q) for a, M1, M2 in slices(): for v in range(3): Fpot_av[a, v] = 2 * (DVt_vMM[v, M1:M2, :] * rhoT_MM[M1:M2, :]).real.sum() del DVt_vMM self.timer.stop('LCAO forces: potential') # Density matrix contribution due to basis overlap # # ----- d Theta # a \ mu nu # F += -2 Re ) ------------ E # / d R nu mu # ----- mu nu # mu in a; nu # Frho_av = np.zeros_like(F_av) dThetadRE_vMM = (dThetadR_vMM * ET_MM[np.newaxis]).real for a, M1, M2 in my_slices(): Frho_av[a, :] = -2 * dThetadRE_vMM[:, M1:M2].sum(-1).sum(-1) del dThetadRE_vMM # Density matrix contribution from PAW correction # # ----- ----- # a \ a \ b # F += 2 Re ) Z E - 2 Re ) Z E # / mu nu nu mu / mu nu nu mu # ----- ----- # mu nu b; mu in a; nu # # with # b* # ----- dP # b \ i mu b b # Z = ) -------- dS P # mu nu / dR ij j nu # ----- b mu # ij # self.timer.start('LCAO forces: paw correction') dPdR_avMi = dict([(a, dPdR_aqvMi[a][q]) for a in my_atom_indices]) work_MM = np.zeros((mynao, nao), dtype) ZE_MM = None for b in my_atom_indices: setup = self.setups[b] dO_ii = np.asarray(setup.dO_ii, dtype) dOP_iM = np.zeros((setup.ni, nao), dtype) gemm(1.0, self.P_aqMi[b][q], dO_ii, 0.0, dOP_iM, 'c') for v in range(3): gemm(1.0, dOP_iM, dPdR_avMi[b][v][Mstart:Mstop], 0.0, work_MM, 'n') ZE_MM = (work_MM * ET_MM).real for a, M1, M2 in slices(): dE = 2 * ZE_MM[M1:M2].sum() Frho_av[a, v] -= dE # the "b; mu in a; nu" term Frho_av[b, v] += dE # the "mu nu" term del work_MM, ZE_MM self.timer.stop('LCAO forces: paw correction') # Atomic density contribution # ----- ----- # a \ a \ b # F += -2 Re ) A rho + 2 Re ) A rho # / mu nu nu mu / mu nu nu mu # ----- ----- # mu nu b; mu in a; nu # # b* # ----- d P # b \ i mu b b # A = ) ------- dH P # mu nu / d R ij j nu # ----- b mu # ij # self.timer.start('LCAO forces: atomic density') Fatom_av = np.zeros_like(F_av) for b in my_atom_indices: H_ii = np.asarray(unpack(hamiltonian.dH_asp[b][kpt.s]), dtype) HP_iM = gemmdot(H_ii, np.conj(self.P_aqMi[b][q].T)) for v in range(3): dPdR_Mi = dPdR_avMi[b][v][Mstart:Mstop] ArhoT_MM = (gemmdot(dPdR_Mi, HP_iM) * rhoT_MM).real for a, M1, M2 in slices(): dE = 2 * ArhoT_MM[M1:M2].sum() Fatom_av[a, v] += dE # the "b; mu in a; nu" term Fatom_av[b, v] -= dE # the "mu nu" term self.timer.stop('LCAO forces: atomic density') F_av += Fkin_av + Fpot_av + Frho_av + Fatom_av
def get_M(self, modes, log=None, q=0): """Calculate el-ph coupling matrix for given modes(s). XXX: kwarg "q=0" is an ugly hack for k-points. There shuold be loops over q! Note that modes must be given as a dictionary with mode frequencies in eV and corresponding mode vectors in units of 1/sqrt(amu), where amu = 1.6605402e-27 Kg is an atomic mass unit. In short frequencies and mode vectors must be given in ase units. :: d d ~ < w | -- v | w' > = < w | -- v | w'> dP dP _ \ ~a d . ~a + ) < w | p > -- /_\H < p | w' > /_ i dP ij j a,ij _ \ d ~a . ~a + ) < w | -- p > /_\H < p | w' > /_ dP i ij j a,ij _ \ ~a . d ~a + ) < w | p > /_\H < -- p | w' > /_ i ij dP j a,ij """ if log is None: timer = nulltimer elif log == '-': timer = StepTimer(name='EPCM') else: timer = StepTimer(name='EPCM', out=open(log, 'w')) modes1 = modes.copy() #convert to atomic units amu = 1.6605402e-27 # atomic unit mass [Kg] me = 9.1093897e-31 # electron mass [Kg] modes = {} for k in modes1.keys(): modes[k / Hartree] = modes1[k] / np.sqrt(amu / me) dvt_Gx, ddH_aspx = self.get_gradient() from gpaw import restart atoms, calc = restart('eq.gpw', txt=None) if calc.wfs.S_qMM is None: calc.initialize(atoms) calc.initialize_positions(atoms) wfs = calc.wfs nao = wfs.setups.nao bfs = wfs.basis_functions dtype = wfs.dtype spin = 0 # XXX M_lii = {} timer.write_now('Starting gradient of pseudo part') for f, mode in modes.items(): mo = [] M_ii = np.zeros((nao, nao), dtype) for a in self.indices: mo.append(mode[a]) mode = np.asarray(mo).flatten() dvtdP_G = np.dot(dvt_Gx, mode) bfs.calculate_potential_matrix(dvtdP_G, M_ii, q=q) tri2full(M_ii, 'L') M_lii[f] = M_ii timer.write_now('Finished gradient of pseudo part') P_aqMi = calc.wfs.P_aqMi # Add the term # _ # \ ~a d . ~a # ) < w | p > -- /_\H < p | w' > # /_ i dP ij j # a,ij Ma_lii = {} for f, mode in modes.items(): Ma_lii[f] = np.zeros_like(M_lii.values()[0]) timer.write_now('Starting gradient of dH^a part') for f, mode in modes.items(): mo = [] for a in self.indices: mo.append(mode[a]) mode = np.asarray(mo).flatten() for a, ddH_spx in ddH_aspx.items(): ddHdP_sp = np.dot(ddH_spx, mode) ddHdP_ii = unpack2(ddHdP_sp[spin]) Ma_lii[f] += dots(P_aqMi[a][q], ddHdP_ii, P_aqMi[a][q].T) timer.write_now('Finished gradient of dH^a part') timer.write_now('Starting gradient of projectors part') dP_aMix = self.get_dP_aMix(calc.spos_ac, wfs, q, timer) timer.write_now('Finished gradient of projectors part') dH_asp = pickle.load(open('v.eq.pckl', 'rb'))[1] Mb_lii = {} for f, mode in modes.items(): Mb_lii[f] = np.zeros_like(M_lii.values()[0]) for f, mode in modes.items(): for a, dP_Mix in dP_aMix.items(): dPdP_Mi = np.dot(dP_Mix, mode[a]) dH_ii = unpack2(dH_asp[a][spin]) dPdP_MM = dots(dPdP_Mi, dH_ii, P_aqMi[a][q].T) Mb_lii[f] -= dPdP_MM + dPdP_MM.T # XXX The minus sign here is quite subtle. # It is related to how the derivative of projector # functions in GPAW is calculated. # More thorough explanations, anyone...? # Units of M_lii are Hartree/(Bohr * sqrt(m_e)) for mode in M_lii.keys(): M_lii[mode] += Ma_lii[mode] + Mb_lii[mode] # conversion to eV. The prefactor 1 / sqrt(hb^2 / 2 * hb * f) # has units Bohr * sqrt(me) M_lii_1 = M_lii.copy() M_lii = {} for f in M_lii_1.keys(): M_lii[f * Hartree] = M_lii_1[f] * Hartree / np.sqrt(2 * f) return M_lii
def dump_hamiltonian_parallel(filename, atoms, direction=None, Ef=None): """ Dump the lcao representation of H and S to file(s) beginning with filename. If direction is x, y or z, the periodic boundary conditions will be removed in the specified direction. If the Fermi temperature is different from zero, the energy zero-point is taken as the Fermi level. Note: H and S are parallized over spin and k-points and is for now dumped into a number of pickle files. This may be changed into a dump to a single file in the future. """ if direction is not None: d = 'xyz'.index(direction) calc = atoms.calc wfs = calc.wfs nao = wfs.setups.nao nq = len(wfs.kpt_u) // wfs.nspins H_qMM = np.empty((wfs.nspins, nq, nao, nao), wfs.dtype) calc_data = {'k_q':{}, 'skpt_qc':np.empty((nq, 3)), 'weight_q':np.empty(nq)} S_qMM = wfs.S_qMM for kpt in wfs.kpt_u: calc_data['skpt_qc'][kpt.q] = calc.wfs.kd.ibzk_kc[kpt.k] calc_data['weight_q'][kpt.q] = calc.wfs.kd.weight_k[kpt.k] calc_data['k_q'][kpt.q] = kpt.k ## print ('Calc. H matrix on proc. %i: ' ## '(rk, rd, q, k) = (%i, %i, %i, %i)') % ( ## wfs.world.rank, wfs.kd.comm.rank, ## wfs.gd.domain.comm.rank, kpt.q, kpt.k) H_MM = wfs.eigensolver.calculate_hamiltonian_matrix(calc.hamiltonian, wfs, kpt) H_qMM[kpt.s, kpt.q] = H_MM tri2full(H_qMM[kpt.s, kpt.q]) if kpt.s==0: tri2full(S_qMM[kpt.q]) if direction is not None: remove_pbc(atoms, H_qMM[kpt.s, kpt.q], S_qMM[kpt.q], d) else: if direction is not None: remove_pbc(atoms, H_qMM[kpt.s, kpt.q], None, d) if calc.occupations.width > 0: if Ef is None: Ef = calc.occupations.get_fermi_level() else: Ef = Ef / Hartree H_qMM[kpt.s, kpt.q] -= S_qMM[kpt.q] * Ef if wfs.gd.comm.rank == 0: fd = file(filename+'%i.pckl' % wfs.kd.comm.rank, 'wb') H_qMM *= Hartree pickle.dump((H_qMM, S_qMM),fd , 2) pickle.dump(calc_data, fd, 2) fd.close()
def test_trivial_diagonalize(self): # Known starting point of S_nn = <psit_m|S|psit_n> S_nn = self.S0_nn # Eigenvector decomposition S_nn = V_nn * W_nn * V_nn^dag # Utilize the fact that they are analytically known (cf. Maple) band_indices = np.arange(self.nbands) V_nn = np.eye(self.nbands).astype(self.dtype) if self.dtype == complex: V_nn[1:, 1] = np.conj( self.gamma)**band_indices[1:] * band_indices[1:]**0.5 V_nn[1, 2:] = -self.gamma**band_indices[1:-1] * band_indices[2:]**0.5 else: V_nn[2:, 1] = band_indices[2:]**0.5 V_nn[1, 2:] = -band_indices[2:]**0.5 W_n = np.zeros(self.nbands).astype(self.dtype) W_n[1] = (1. + self.Qtotal) * self.nbands * (self.nbands - 1) / 2. # Find the inverse basis Vinv_nn = np.linalg.inv(V_nn) # Test analytical eigenvectors for consistency against analytical S_nn D_nn = np.dot(Vinv_nn, np.dot(S_nn, V_nn)) self.assertAlmostEqual(np.abs(D_nn.diagonal() - W_n).max(), 0, 8) self.assertAlmostEqual(np.abs(np.tril(D_nn, -1)).max(), 0, 4) self.assertAlmostEqual(np.abs(np.triu(D_nn, 1)).max(), 0, 4) del Vinv_nn, D_nn # Perform Gram Schmidt orthonormalization for diagonalization # |psit_n> -> C_nn |psit_n>, using orthonormalized basis Q_nn # <psit_m|S|psit_n> -> <psit_m|C_nn^dag S C_nn|psit_n> = diag(W_n) # using S_nn = V_nn * W_nn * V_nn^(-1) = Q_nn * W_nn * Q_nn^dag C_nn = V_nn.copy() gram_schmidt(C_nn) self.assertAlmostEqual(np.abs(np.dot(C_nn.T.conj(), C_nn) \ - np.eye(self.nbands)).max(), 0, 6) # Set up Hermitian overlap operator: S = lambda x: x dS = lambda a, P_ni: np.dot(P_ni, self.setups[a].dO_ii) nblocks = self.get_optimal_number_of_blocks(self.blocking) overlap = MatrixOperator(self.ksl, nblocks, self. async, True) self.psit_nG = overlap.matrix_multiply(C_nn.T.copy(), self.psit_nG, self.P_ani) D_nn = overlap.calculate_matrix_elements(self.psit_nG, \ self.P_ani, S, dS).T.copy() # transpose to get <psit_m|A|psit_n> tri2full(D_nn, 'U') # upper to lower... if self.bd.comm.rank == 0: self.gd.comm.broadcast(D_nn, 0) self.bd.comm.broadcast(D_nn, 0) if memstats: self.mem_test = record_memory() # D_nn = C_nn^dag * S_nn * C_nn = W_n since Q_nn^dag = Q_nn^(-1) D0_nn = np.dot(C_nn.T.conj(), np.dot(S_nn, C_nn)) self.assertAlmostEqual(np.abs(D0_nn - np.diag(W_n)).max(), 0, 9) self.check_and_plot(D_nn, D0_nn, 9, 'trivial,diagonalize')
def set_positions(self, spos_ac): self.timer.start('Basic WFS set positions') WaveFunctions.set_positions(self, spos_ac) self.timer.stop('Basic WFS set positions') self.timer.start('Basis functions set positions') self.basis_functions.set_positions(spos_ac) self.timer.stop('Basis functions set positions') if self.ksl is not None: self.basis_functions.set_matrix_distribution(self.ksl.Mstart, self.ksl.Mstop) nq = len(self.kd.ibzk_qc) nao = self.setups.nao mynbands = self.bd.mynbands Mstop = self.ksl.Mstop Mstart = self.ksl.Mstart mynao = Mstop - Mstart if self.ksl.using_blacs: # XXX # S and T have been distributed to a layout with blacs, so # discard them to force reallocation from scratch. # # TODO: evaluate S and T when they *are* distributed, thus saving # memory and avoiding this problem self.S_qMM = None self.T_qMM = None S_qMM = self.S_qMM T_qMM = self.T_qMM if S_qMM is None: # XXX # First time: assert T_qMM is None if self.ksl.using_blacs: # XXX self.tci.set_matrix_distribution(Mstart, mynao) S_qMM = np.empty((nq, mynao, nao), self.dtype) T_qMM = np.empty((nq, mynao, nao), self.dtype) for kpt in self.kpt_u: if kpt.C_nM is None: kpt.C_nM = np.empty((mynbands, nao), self.dtype) self.allocate_arrays_for_projections( self.basis_functions.my_atom_indices) self.P_aqMi = {} for a in self.basis_functions.my_atom_indices: ni = self.setups[a].ni self.P_aqMi[a] = np.empty((nq, nao, ni), self.dtype) for kpt in self.kpt_u: q = kpt.q kpt.P_aMi = dict([(a, P_qMi[q]) for a, P_qMi in self.P_aqMi.items()]) self.timer.start('TCI: Calculate S, T, P') # Calculate lower triangle of S and T matrices: self.tci.calculate(spos_ac, S_qMM, T_qMM, self.P_aqMi) add_paw_correction_to_overlap(self.setups, self.P_aqMi, S_qMM, self.ksl.Mstart, self.ksl.Mstop) self.timer.stop('TCI: Calculate S, T, P') S_MM = None # allow garbage collection of old S_qMM after redist S_qMM = self.ksl.distribute_overlap_matrix(S_qMM) T_qMM = self.ksl.distribute_overlap_matrix(T_qMM) for kpt in self.kpt_u: q = kpt.q kpt.S_MM = S_qMM[q] kpt.T_MM = T_qMM[q] if (debug and self.band_comm.size == 1 and self.gd.comm.rank == 0 and nao > 0 and not self.ksl.using_blacs): # S and T are summed only on comm master, so check only there from numpy.linalg import eigvalsh self.timer.start('Check positive definiteness') for S_MM in S_qMM: tri2full(S_MM, UL='L') smin = eigvalsh(S_MM).real.min() if smin < 0: raise RuntimeError('Overlap matrix has negative ' 'eigenvalue: %e' % smin) self.timer.stop('Check positive definiteness') self.positions_set = True self.S_qMM = S_qMM self.T_qMM = T_qMM
def evaluate(self, spos_ac, Theta_qMM, T_qMM, P_aqMi): calc = TwoCenterIntegralCalculator(self.ibzk_qc, derivative=False) self._calculate(calc, spos_ac, Theta_qMM, T_qMM, P_aqMi) if not self.blacs: for X_MM in list(Theta_qMM) + list(T_qMM): tri2full(X_MM, UL=UL)
def calculate_supercell_matrix(self, dump=0, name=None, filter=None, include_pseudo=True, atoms=None): """Calculate matrix elements of the el-ph coupling in the LCAO basis. This function calculates the matrix elements between LCAOs and local atomic gradients of the effective potential. The matrix elements are calculated for the supercell used to obtain finite-difference approximations to the derivatives of the effective potential wrt to atomic displacements. Parameters ---------- dump: int Dump supercell matrix to pickle file (default: 0). 0: Supercell matrix not saved 1: Supercell matrix saved in a single pickle file. 2: Dump matrix for different gradients in separate files. Useful for large systems where the total array gets too large for a single pickle file. name: string User specified name of the generated pickle file(s). If not provided, the string in the ``name`` attribute is used. filter: str Fourier filter atomic gradients of the effective potential. The specified components (``normal`` or ``umklapp``) are removed (default: None). include_pseudo: bool Include the contribution from the psedupotential in the atomic gradients. If ``False``, only the gradient of the effective potential is included (default: True). atoms: Atoms object Calculate supercell for an ``Atoms`` object different from the one provided in the ``__init__`` method (WARNING, NOT working!). """ assert self.calc_lcao is not None, "Set LCAO calculator" # Supercell atoms if atoms is None: atoms_N = self.atoms * self.N_c else: atoms_N = atoms # Initialize calculator if required and extract useful quantities calc = self.calc_lcao if not hasattr(calc.wfs, 'S_qMM'): calc.initialize(atoms_N) calc.initialize_positions(atoms_N) self.set_basis_info() basis = calc.input_parameters['basis'] # Extract useful objects from the calculator wfs = calc.wfs gd = calc.wfs.gd kd = calc.wfs.kd kpt_u = wfs.kpt_u setups = wfs.setups nao = setups.nao bfs = wfs.basis_functions dtype = wfs.dtype spin = 0 # XXX # If gamma calculation, overlap with neighboring cell cannot be removed if kd.gamma: print "WARNING: Gamma-point calculation." else: # Bloch to real-space converter tb = TightBinding(atoms_N, calc) self.timer.write_now("Calculating supercell matrix") self.timer.write_now("Calculating real-space gradients") # Calculate finite-difference gradients (in Hartree / Bohr) V1t_xG, dH1_xasp = self.calculate_gradient() self.timer.write_now("Finished real-space gradients") # Fourier filter the atomic gradients of the effective potential if filter is not None: self.timer.write_now("Fourier filtering gradients") V1_xG = V1t_xG.copy() self.fourier_filter(V1t_xG, components=filter) self.timer.write_now("Finished Fourier filtering") # For the contribution from the derivative of the projectors dP_aqvMi = self.calculate_dP_aqvMi(wfs) # Equilibrium atomic Hamiltonian matrix (projector coefficients) dH_asp = pickle.load(open(self.name + '.eq.pckl'))[1] # Check that the grid is the same as in the calculator assert np.all(V1t_xG.shape[-3:] == (gd.N_c + gd.pbc_c - 1)), \ "Mismatch in grids." # Calculate < i k | grad H | j k >, i.e. matrix elements in Bloch basis # List for supercell matrices; g_xNNMM = [] self.timer.write_now("Calculating gradient of PAW Hamiltonian") # Do each cartesian component separately for i, a in enumerate(self.indices): for v in range(3): # Corresponding array index x = 3 * i + v V1t_G = V1t_xG[x] self.timer.write_now("%s-gradient of atom %u" % (['x','y','z'][v], a)) # Array for different k-point components g_qMM = np.zeros((len(kpt_u), nao, nao), dtype) # 1) Gradient of effective potential self.timer.write_now("Starting gradient of effective potential") for kpt in kpt_u: # Matrix elements geff_MM = np.zeros((nao, nao), dtype) bfs.calculate_potential_matrix(V1t_G, geff_MM, q=kpt.q) tri2full(geff_MM, 'L') # Insert in array g_qMM[kpt.q] += geff_MM self.timer.write_now("Finished gradient of effective potential") if include_pseudo: self.timer.write_now("Starting gradient of pseudo part") # 2) Gradient of non-local part (projectors) self.timer.write_now("Starting gradient of dH^a") P_aqMi = calc.wfs.P_aqMi # 2a) dH^a part has contributions from all other atoms for kpt in kpt_u: # Matrix elements gp_MM = np.zeros((nao, nao), dtype) dH1_asp = dH1_xasp[x] for a_, dH1_sp in dH1_asp.items(): dH1_ii = unpack2(dH1_sp[spin]) gp_MM += np.dot(P_aqMi[a_][kpt.q], np.dot(dH1_ii, P_aqMi[a_][kpt.q].T.conjugate())) g_qMM[kpt.q] += gp_MM self.timer.write_now("Finished gradient of dH^a") self.timer.write_now("Starting gradient of projectors") # 2b) dP^a part has only contributions from the same atoms dP_qvMi = dP_aqvMi[a] dH_ii = unpack2(dH_asp[a][spin]) for kpt in kpt_u: #XXX Sort out the sign here; conclusion -> sign = +1 ! P1HP_MM = +1 * np.dot(dP_qvMi[kpt.q][v], np.dot(dH_ii, P_aqMi[a][kpt.q].T.conjugate())) # Matrix elements gp_MM = P1HP_MM + P1HP_MM.T.conjugate() g_qMM[kpt.q] += gp_MM self.timer.write_now("Finished gradient of projectors") self.timer.write_now("Finished gradient of pseudo part") # Extract R_c=(0, 0, 0) block by Fourier transforming if kd.gamma or kd.N_c is None: g_MM = g_qMM[0] else: # Convert to array g_MM = tb.bloch_to_real_space(g_qMM, R_c=(0, 0, 0))[0] # Reshape to global unit cell indices N = np.prod(self.N_c) # Number of basis function in the primitive cell assert (nao % N) == 0, "Alarm ...!" nao_cell = nao / N g_NMNM = g_MM.reshape((N, nao_cell, N, nao_cell)) g_NNMM = g_NMNM.swapaxes(1, 2).copy() self.timer.write_now("Finished supercell matrix") if dump != 2: g_xNNMM.append(g_NNMM) else: if name is not None: fname = '%s.supercell_matrix_x_%2.2u.%s.pckl' % (name, x, basis) else: fname = self.name + \ '.supercell_matrix_x_%2.2u.%s.pckl' % (x, basis) if kd.comm.rank == 0: fd = open(fname, 'w') M_a = self.basis_info['M_a'] nao_a = self.basis_info['niAO_a'] pickle.dump((g_NNMM, M_a, nao_a), fd, 2) fd.close() self.timer.write_now("Finished gradient of PAW Hamiltonian") if dump != 2: # Collect gradients in one array self.g_xNNMM = np.array(g_xNNMM) # Dump to pickle file using binary mode together with basis info if dump and kd.comm.rank == 0: if name is not None: fname = '%s.supercell_matrix.%s.pckl' % (name, basis) else: fname = self.name + '.supercell_matrix.%s.pckl' % basis fd = open(fname, 'w') M_a = self.basis_info['M_a'] nao_a = self.basis_info['nao_a'] pickle.dump((self.g_xNNMM, M_a, nao_a), fd, 2) fd.close()
def orthonormalize(self, wfs, kpt, psit_nG=None): """Orthonormalizes the vectors a_nG with respect to the overlap. First, a Cholesky factorization C is done for the overlap matrix S_nn = <a_nG | S | a_nG> = C*_nn C_nn Cholesky matrix C is inverted and orthonormal vectors a_nG' are obtained as:: psit_nG' = inv(C_nn) psit_nG __ ~ _ \ -1 ~ _ psi (r) = ) C psi (r) n /__ nm m m Parameters ---------- psit_nG: ndarray, input/output On input the set of vectors to orthonormalize, on output the overlap-orthonormalized vectors. kpt: KPoint object: k-point object from kpoint.py. work_nG: ndarray Optional work array for overlap matrix times psit_nG. work_nn: ndarray Optional work array for overlap matrix. """ self.timer.start('Orthonormalize') if psit_nG is None: psit_nG = kpt.psit_nG P_ani = kpt.P_ani self.timer.start('projections') wfs.pt.integrate(psit_nG, P_ani, kpt.q) self.timer.stop('projections') # Construct the overlap matrix: operator = wfs.matrixoperator def S(psit_G): return psit_G def dS(a, P_ni): return np.dot(P_ni, wfs.setups[a].dO_ii) self.timer.start('calc_s_matrix') S_nn = operator.calculate_matrix_elements(psit_nG, P_ani, S, dS) self.timer.stop('calc_s_matrix') orthonormalization_string = repr(self.ksl) self.timer.start(orthonormalization_string) # if extra_parameters.get('sic', False): # # symmetric Loewdin Orthonormalization tri2full(S_nn, UL='L', map=np.conj) nrm_n = np.empty(S_nn.shape[0]) diagonalize(S_nn, nrm_n) nrm_nn = np.diag(1.0/np.sqrt(nrm_n)) S_nn = np.dot(np.dot(S_nn.T.conj(), nrm_nn), S_nn) else: # self.ksl.inverse_cholesky(S_nn) # S_nn now contains the inverse of the Cholesky factorization. # Let's call it something different: C_nn = S_nn del S_nn self.timer.stop(orthonormalization_string) self.timer.start('rotate_psi') operator.matrix_multiply(C_nn, psit_nG, P_ani, out_nG=kpt.psit_nG) self.timer.stop('rotate_psi') self.timer.stop('Orthonormalize')
def tddft_init(self): if not self.tddft_initialized: if world.rank == 0: print('Initializing real time LCAO TD-DFT calculation.') print('XXX Warning: Array use not optimal for memory.') print('XXX Taylor propagator probably doesn\'t work') print('XXX ...and no arrays are listed in memory estimate yet.') self.blacs = self.wfs.ksl.using_blacs if self.blacs: self.ksl = ksl = self.wfs.ksl nao = ksl.nao nbands = ksl.bd.nbands mynbands = ksl.bd.mynbands blocksize = ksl.blocksize from gpaw.blacs import Redistributor if world.rank == 0: print('BLACS Parallelization') # Parallel grid descriptors self.MM_descriptor = ksl.blockgrid.new_descriptor(nao, nao, nao, nao) # FOR DEBUG self.mm_block_descriptor = ksl.blockgrid.new_descriptor(nao, nao, blocksize, blocksize) self.Cnm_block_descriptor = ksl.blockgrid.new_descriptor(nbands, nao, blocksize, blocksize) #self.CnM_descriptor = ksl.blockgrid.new_descriptor(nbands, nao, mynbands, nao) self.mM_column_descriptor = ksl.single_column_grid.new_descriptor(nao, nao, ksl.naoblocksize, nao) self.CnM_unique_descriptor = ksl.single_column_grid.new_descriptor(nbands, nao, mynbands, nao) # Redistributors self.mm2MM = Redistributor(ksl.block_comm, self.mm_block_descriptor, self.MM_descriptor) # XXX FOR DEBUG self.MM2mm = Redistributor(ksl.block_comm, self.MM_descriptor, self.mm_block_descriptor) # XXX FOR DEBUG self.Cnm2nM = Redistributor(ksl.block_comm, self.Cnm_block_descriptor, self.CnM_unique_descriptor) self.CnM2nm = Redistributor(ksl.block_comm, self.CnM_unique_descriptor, self.Cnm_block_descriptor) self.mM2mm = Redistributor(ksl.block_comm, self.mM_column_descriptor, self.mm_block_descriptor) for kpt in self.wfs.kpt_u: scalapack_zero(self.mm_block_descriptor, kpt.S_MM,'U') scalapack_zero(self.mm_block_descriptor, kpt.T_MM,'U') # XXX to propagator class if self.propagator == 'taylor' and self.blacs: # cholS_mm = self.mm_block_descriptor.empty(dtype=complex) for kpt in self.wfs.kpt_u: kpt.invS_MM = kpt.S_MM.copy() scalapack_inverse(self.mm_block_descriptor, kpt.invS_MM, 'L') if self.propagator_debug: if world.rank == 0: print('XXX Doing serial inversion of overlap matrix.') self.timer.start('Invert overlap (serial)') invS2_MM = self.MM_descriptor.empty(dtype=complex) for kpt in self.wfs.kpt_u: #kpt.S_MM[:] = 128.0*(2**world.rank) self.mm2MM.redistribute(self.wfs.S_qMM[kpt.q], invS2_MM) world.barrier() if world.rank == 0: tri2full(invS2_MM,'L') invS2_MM[:] = inv(invS2_MM.copy()) self.invS2_MM = invS2_MM kpt.invS2_MM = ksl.mmdescriptor.empty(dtype=complex) self.MM2mm.redistribute(invS2_MM, kpt.invS2_MM) verify(kpt.invS_MM, kpt.invS2_MM, 'overlap par. vs. serial', 'L') self.timer.stop('Invert overlap (serial)') if world.rank == 0: print('XXX Overlap inverted.') if self.propagator == 'taylor' and not self.blacs: tmp = inv(self.wfs.kpt_u[0].S_MM) self.wfs.kpt_u[0].invS = tmp # Reset the density mixer self.density.mixer = DummyMixer() self.tddft_initialized = True for k, kpt in enumerate(self.wfs.kpt_u): kpt.C2_nM = kpt.C_nM.copy()
# Check gemm for transa='c' a = np.arange(4 * 5 * 1 * 3).reshape(4, 5, 1, 3) * (3. - 2.j) + 4. c = np.tensordot(a, a2.conj(), [[1, 2, 3], [1, 2, 3]]) gemm(1., a2, a, -1., c, 'c') assert not c.any() # Check axpy c = 5.j * a axpy(-5.j, a, c) assert not c.any() # Check rk c = np.tensordot(a, a.conj(), [[1, 2, 3], [1, 2, 3]]) rk(1., a, -1., c) tri2full(c) assert not c.any() # Check gemmdot for transa='c' c = np.tensordot(a, a2.conj(), [-1, -1]) gemmdot(a, a2, beta=-1., out=c, trans='c') assert not c.any() # Check gemmdot for transa='n' a2.shape = 3, 7, 5, 1 c = np.tensordot(a, a2, [-1, 0]) gemmdot(a, a2, beta=-1., out=c, trans='n') assert not c.any() # Check r2k a2 = 5. * a
def calculate_forces(self, hamiltonian, F_av): self.timer.start('LCAO forces') spos_ac = self.tci.atoms.get_scaled_positions() % 1.0 ksl = self.ksl nao = ksl.nao mynao = ksl.mynao nq = len(self.kd.ibzk_qc) dtype = self.dtype tci = self.tci gd = self.gd bfs = self.basis_functions Mstart = ksl.Mstart Mstop = ksl.Mstop from gpaw.kohnsham_layouts import BlacsOrbitalLayouts isblacs = isinstance(ksl, BlacsOrbitalLayouts) # XXX if not isblacs: self.timer.start('TCI derivative') dThetadR_qvMM = np.empty((nq, 3, mynao, nao), dtype) dTdR_qvMM = np.empty((nq, 3, mynao, nao), dtype) dPdR_aqvMi = {} for a in self.basis_functions.my_atom_indices: ni = self.setups[a].ni dPdR_aqvMi[a] = np.empty((nq, 3, nao, ni), dtype) tci.calculate_derivative(spos_ac, dThetadR_qvMM, dTdR_qvMM, dPdR_aqvMi) gd.comm.sum(dThetadR_qvMM) gd.comm.sum(dTdR_qvMM) self.timer.stop('TCI derivative') my_atom_indices = bfs.my_atom_indices atom_indices = bfs.atom_indices def _slices(indices): for a in indices: M1 = bfs.M_a[a] - Mstart M2 = M1 + self.setups[a].nao if M2 > 0: yield a, max(0, M1), M2 def slices(): return _slices(atom_indices) def my_slices(): return _slices(my_atom_indices) # # ----- ----- # \ -1 \ * # E = ) S H rho = ) c eps f c # mu nu / mu x x z z nu / n mu n n n nu # ----- ----- # x z n # # We use the transpose of that matrix. The first form is used # if rho is given, otherwise the coefficients are used. self.timer.start('Initial') rhoT_uMM = [] ET_uMM = [] if not isblacs: if self.kpt_u[0].rho_MM is None: self.timer.start('Get density matrix') for kpt in self.kpt_u: rhoT_MM = ksl.get_transposed_density_matrix(kpt.f_n, kpt.C_nM) rhoT_uMM.append(rhoT_MM) ET_MM = ksl.get_transposed_density_matrix(kpt.f_n * kpt.eps_n, kpt.C_nM) ET_uMM.append(ET_MM) if hasattr(kpt, 'c_on'): # XXX does this work with BLACS/non-BLACS/etc.? assert self.bd.comm.size == 1 d_nn = np.zeros((self.bd.mynbands, self.bd.mynbands), dtype=kpt.C_nM.dtype) for ne, c_n in zip(kpt.ne_o, kpt.c_on): d_nn += ne * np.outer(c_n.conj(), c_n) rhoT_MM += ksl.get_transposed_density_matrix_delta(d_nn, kpt.C_nM) ET_MM += ksl.get_transposed_density_matrix_delta(d_nn * kpt.eps_n, kpt.C_nM) self.timer.stop('Get density matrix') else: rhoT_uMM = [] ET_uMM = [] for kpt in self.kpt_u: H_MM = self.eigensolver.calculate_hamiltonian_matrix(hamiltonian, self, kpt) tri2full(H_MM) S_MM = kpt.S_MM.copy() tri2full(S_MM) ET_MM = np.linalg.solve(S_MM, gemmdot(H_MM, kpt.rho_MM)).T.copy() del S_MM, H_MM rhoT_MM = kpt.rho_MM.T.copy() rhoT_uMM.append(rhoT_MM) ET_uMM.append(ET_MM) self.timer.stop('Initial') if isblacs: # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from gpaw.blacs import BlacsGrid, Redistributor def get_density_matrix(f_n, C_nM, redistributor): rho1_mm = ksl.calculate_blocked_density_matrix(f_n, C_nM).conj() rho_mm = redistributor.redistribute(rho1_mm) return rho_mm pcutoff_a = [max([pt.get_cutoff() for pt in setup.pt_j]) for setup in self.setups] phicutoff_a = [max([phit.get_cutoff() for phit in setup.phit_j]) for setup in self.setups] # XXX should probably use bdsize x gdsize instead # That would be consistent with some existing grids grid = BlacsGrid(ksl.block_comm, self.gd.comm.size, self.bd.comm.size) blocksize1 = -(-nao // grid.nprow) blocksize2 = -(-nao // grid.npcol) # XXX what are rows and columns actually? desc = grid.new_descriptor(nao, nao, blocksize1, blocksize2) rhoT_umm = [] ET_umm = [] redistributor = Redistributor(grid.comm, ksl.mmdescriptor, desc) Fpot_av = np.zeros_like(F_av) for u, kpt in enumerate(self.kpt_u): self.timer.start('Get density matrix') rhoT_mm = get_density_matrix(kpt.f_n, kpt.C_nM, redistributor) rhoT_umm.append(rhoT_mm) self.timer.stop('Get density matrix') self.timer.start('Potential') rhoT_mM = ksl.distribute_to_columns(rhoT_mm, desc) vt_G = hamiltonian.vt_sG[kpt.s] Fpot_av += bfs.calculate_force_contribution(vt_G, rhoT_mM, kpt.q) del rhoT_mM self.timer.stop('Potential') self.timer.start('Get density matrix') for kpt in self.kpt_u: ET_mm = get_density_matrix(kpt.f_n * kpt.eps_n, kpt.C_nM, redistributor) ET_umm.append(ET_mm) self.timer.stop('Get density matrix') M1start = blocksize1 * grid.myrow M2start = blocksize2 * grid.mycol M1stop = min(M1start + blocksize1, nao) M2stop = min(M2start + blocksize2, nao) m1max = M1stop - M1start m2max = M2stop - M2start if not isblacs: # Kinetic energy contribution # # ----- d T # a \ mu nu # F += 2 Re ) -------- rho # / d R nu mu # ----- mu nu # mu in a; nu # Fkin_av = np.zeros_like(F_av) for u, kpt in enumerate(self.kpt_u): dEdTrhoT_vMM = (dTdR_qvMM[kpt.q] * rhoT_uMM[u][np.newaxis]).real for a, M1, M2 in my_slices(): Fkin_av[a, :] += 2.0 * dEdTrhoT_vMM[:, M1:M2].sum(-1).sum(-1) del dEdTrhoT_vMM # Density matrix contribution due to basis overlap # # ----- d Theta # a \ mu nu # F += -2 Re ) ------------ E # / d R nu mu # ----- mu nu # mu in a; nu # Ftheta_av = np.zeros_like(F_av) for u, kpt in enumerate(self.kpt_u): dThetadRE_vMM = (dThetadR_qvMM[kpt.q] * ET_uMM[u][np.newaxis]).real for a, M1, M2 in my_slices(): Ftheta_av[a, :] += -2.0 * dThetadRE_vMM[:, M1:M2].sum(-1).sum(-1) del dThetadRE_vMM if isblacs: from gpaw.lcao.overlap import TwoCenterIntegralCalculator self.timer.start('Prepare TCI loop') M_a = bfs.M_a Fkin2_av = np.zeros_like(F_av) Ftheta2_av = np.zeros_like(F_av) cell_cv = tci.atoms.cell spos_ac = tci.atoms.get_scaled_positions() % 1.0 overlapcalc = TwoCenterIntegralCalculator(self.kd.ibzk_qc, derivative=False) def get_phases(offset): return overlapcalc.phaseclass(overlapcalc.ibzk_qc, offset) # XXX this is not parallel *AT ALL*. self.timer.start('Get neighbors') nl = tci.atompairs.pairs.neighbors r_and_offset_aao = get_r_and_offsets(nl, spos_ac, cell_cv) atompairs = r_and_offset_aao.keys() atompairs.sort() self.timer.stop('Get neighbors') T_expansions = tci.T_expansions Theta_expansions = tci.Theta_expansions P_expansions = tci.P_expansions nq = len(self.ibzk_qc) dH_asp = hamiltonian.dH_asp self.timer.start('broadcast dH') alldH_asp = {} for a in range(len(self.setups)): gdrank = bfs.sphere_a[a].rank if gdrank == gd.rank: dH_sp = dH_asp[a] else: ni = self.setups[a].ni dH_sp = np.empty((self.nspins, ni * (ni + 1) // 2)) gd.comm.broadcast(dH_sp, gdrank) # okay, now everyone gets copies of dH_sp alldH_asp[a] = dH_sp self.timer.stop('broadcast dH') # This will get sort of hairy. We need to account for some # three-center overlaps, such as: # # a1 # Phi ~a3 a3 ~a3 a2 a2,a1 # < ---- |p > dH <p |Phi > rho # dR # # To this end we will loop over all pairs of atoms (a1, a3), # and then a sub-loop over (a3, a2). from gpaw.lcao.overlap import DerivativeAtomicDisplacement class Displacement(DerivativeAtomicDisplacement): def __init__(self, a1, a2, R_c, offset): phases = overlapcalc.phaseclass(overlapcalc.ibzk_qc, offset) DerivativeAtomicDisplacement.__init__(self, None, a1, a2, R_c, offset, phases) # Cache of Displacement objects with spherical harmonics with # evaluated spherical harmonics. disp_aao = {} def get_displacements(a1, a2, maxdistance): # XXX the way maxdistance is handled it can lead to # bad caching when different maxdistances are passed # to subsequent calls with same pair of atoms disp_o = disp_aao.get((a1, a2)) if disp_o is None: disp_o = [] for r, offset in r_and_offset_aao[(a1, a2)]: if np.linalg.norm(r) > maxdistance: continue disp = Displacement(a1, a2, r, offset) disp_o.append(disp) disp_aao[(a1, a2)] = disp_o return [disp for disp in disp_o if disp.r < maxdistance] self.timer.stop('Prepare TCI loop') self.timer.start('Not so complicated loop') for (a1, a2) in atompairs: if a1 >= a2: # Actually this leads to bad load balance. # We should take a1 > a2 or a1 < a2 equally many times. # Maybe decide which of these choices # depending on whether a2 % 1 == 0 continue m1start = M_a[a1] - M1start m2start = M_a[a2] - M2start if m1start >= blocksize1 or m2start >= blocksize2: continue T_expansion = T_expansions.get(a1, a2) Theta_expansion = Theta_expansions.get(a1, a2) P_expansion = P_expansions.get(a1, a2) nm1, nm2 = T_expansion.shape m1stop = min(m1start + nm1, m1max) m2stop = min(m2start + nm2, m2max) if m1stop <= 0 or m2stop <= 0: continue m1start = max(m1start, 0) m2start = max(m2start, 0) J1start = max(0, M1start - M_a[a1]) J2start = max(0, M2start - M_a[a2]) M1stop = J1start + m1stop - m1start J2stop = J2start + m2stop - m2start dTdR_qvmm = T_expansion.zeros((nq, 3), dtype=dtype) dThetadR_qvmm = Theta_expansion.zeros((nq, 3), dtype=dtype) disp_o = get_displacements(a1, a2, phicutoff_a[a1] + phicutoff_a[a2]) for disp in disp_o: disp.evaluate_overlap(T_expansion, dTdR_qvmm) disp.evaluate_overlap(Theta_expansion, dThetadR_qvmm) for u, kpt in enumerate(self.kpt_u): rhoT_mm = rhoT_umm[u][m1start:m1stop, m2start:m2stop] ET_mm = ET_umm[u][m1start:m1stop, m2start:m2stop] Fkin_v = 2.0 * (dTdR_qvmm[kpt.q][:, J1start:M1stop, J2start:J2stop] * rhoT_mm[np.newaxis]).real.sum(-1).sum(-1) Ftheta_v = 2.0 * (dThetadR_qvmm[kpt.q][:, J1start:M1stop, J2start:J2stop] * ET_mm[np.newaxis]).real.sum(-1).sum(-1) Fkin2_av[a1] += Fkin_v Fkin2_av[a2] -= Fkin_v Ftheta2_av[a1] -= Ftheta_v Ftheta2_av[a2] += Ftheta_v Fkin_av = Fkin2_av Ftheta_av = Ftheta2_av self.timer.stop('Not so complicated loop') dHP_and_dSP_aauim = {} a2values = {} for (a2, a3) in atompairs: if not a3 in a2values: a2values[a3] = [] a2values[a3].append(a2) Fatom_av = np.zeros_like(F_av) Frho_av = np.zeros_like(F_av) self.timer.start('Complicated loop') for a1, a3 in atompairs: if a1 == a3: continue m1start = M_a[a1] - M1start if m1start >= blocksize1: continue P_expansion = P_expansions.get(a1, a3) nm1 = P_expansion.shape[0] m1stop = min(m1start + nm1, m1max) if m1stop <= 0: continue m1start = max(m1start, 0) J1start = max(0, M1start - M_a[a1]) J1stop = J1start + m1stop - m1start disp_o = get_displacements(a1, a3, phicutoff_a[a1] + pcutoff_a[a3]) if len(disp_o) == 0: continue dPdR_qvmi = P_expansion.zeros((nq, 3), dtype=dtype) for disp in disp_o: disp.evaluate_overlap(P_expansion, dPdR_qvmi) dPdR_qvmi = dPdR_qvmi[:, :, J1start:J1stop, :].copy() for a2 in a2values[a3]: m2start = M_a[a2] - M2start if m2start >= blocksize2: continue P_expansion2 = P_expansions.get(a2, a3) nm2 = P_expansion2.shape[0] m2stop = min(m2start + nm2, m2max) if m2stop <= 0: continue disp_o = get_displacements(a2, a3, phicutoff_a[a2] + pcutoff_a[a3]) if len(disp_o) == 0: continue m2start = max(m2start, 0) J2start = max(0, M2start - M_a[a2]) J2stop = J2start + m2stop - m2start if (a2, a3) in dHP_and_dSP_aauim: dHP_uim, dSP_uim = dHP_and_dSP_aauim[(a2, a3)] else: P_qmi = P_expansion2.zeros((nq,), dtype=dtype) for disp in disp_o: disp.evaluate_direct(P_expansion2, P_qmi) P_qmi = P_qmi[:, J2start:J2stop].copy() dH_sp = alldH_asp[a3] dS_ii = self.setups[a3].dO_ii dHP_uim = [] dSP_uim = [] for u, kpt in enumerate(self.kpt_u): dH_ii = unpack(dH_sp[kpt.s]) dHP_im = np.dot(P_qmi[kpt.q], dH_ii).T.conj() # XXX only need nq of these dSP_im = np.dot(P_qmi[kpt.q], dS_ii).T.conj() dHP_uim.append(dHP_im) dSP_uim.append(dSP_im) dHP_and_dSP_aauim[(a2, a3)] = dHP_uim, dSP_uim for u, kpt in enumerate(self.kpt_u): rhoT_mm = rhoT_umm[u][m1start:m1stop, m2start:m2stop] ET_mm = ET_umm[u][m1start:m1stop, m2start:m2stop] dPdRdHP_vmm = np.dot(dPdR_qvmi[kpt.q], dHP_uim[u]) dPdRdSP_vmm = np.dot(dPdR_qvmi[kpt.q], dSP_uim[u]) Fatom_c = 2.0 * (dPdRdHP_vmm * rhoT_mm).real.sum(-1).sum(-1) Frho_c = 2.0 * (dPdRdSP_vmm * ET_mm).real.sum(-1).sum(-1) Fatom_av[a1] += Fatom_c Fatom_av[a3] -= Fatom_c Frho_av[a1] -= Frho_c Frho_av[a3] += Frho_c self.timer.stop('Complicated loop') if not isblacs: # Potential contribution # # ----- / d Phi (r) # a \ | mu ~ # F += -2 Re ) | ---------- v (r) Phi (r) dr rho # / | d R nu nu mu # ----- / a # mu in a; nu # self.timer.start('Potential') Fpot_av = np.zeros_like(F_av) for u, kpt in enumerate(self.kpt_u): vt_G = hamiltonian.vt_sG[kpt.s] Fpot_av += bfs.calculate_force_contribution(vt_G, rhoT_uMM[u], kpt.q) self.timer.stop('Potential') # Density matrix contribution from PAW correction # # ----- ----- # a \ a \ b # F += 2 Re ) Z E - 2 Re ) Z E # / mu nu nu mu / mu nu nu mu # ----- ----- # mu nu b; mu in a; nu # # with # b* # ----- dP # b \ i mu b b # Z = ) -------- dS P # mu nu / dR ij j nu # ----- b mu # ij # self.timer.start('Paw correction') Frho_av = np.zeros_like(F_av) for u, kpt in enumerate(self.kpt_u): work_MM = np.zeros((mynao, nao), dtype) ZE_MM = None for b in my_atom_indices: setup = self.setups[b] dO_ii = np.asarray(setup.dO_ii, dtype) dOP_iM = np.zeros((setup.ni, nao), dtype) gemm(1.0, self.P_aqMi[b][kpt.q], dO_ii, 0.0, dOP_iM, 'c') for v in range(3): gemm(1.0, dOP_iM, dPdR_aqvMi[b][kpt.q][v][Mstart:Mstop], 0.0, work_MM, 'n') ZE_MM = (work_MM * ET_uMM[u]).real for a, M1, M2 in slices(): dE = 2 * ZE_MM[M1:M2].sum() Frho_av[a, v] -= dE # the "b; mu in a; nu" term Frho_av[b, v] += dE # the "mu nu" term del work_MM, ZE_MM self.timer.stop('Paw correction') # Atomic density contribution # ----- ----- # a \ a \ b # F += -2 Re ) A rho + 2 Re ) A rho # / mu nu nu mu / mu nu nu mu # ----- ----- # mu nu b; mu in a; nu # # b* # ----- d P # b \ i mu b b # A = ) ------- dH P # mu nu / d R ij j nu # ----- b mu # ij # self.timer.start('Atomic Hamiltonian force') Fatom_av = np.zeros_like(F_av) for u, kpt in enumerate(self.kpt_u): for b in my_atom_indices: H_ii = np.asarray(unpack(hamiltonian.dH_asp[b][kpt.s]), dtype) HP_iM = gemmdot(H_ii, np.ascontiguousarray(self.P_aqMi[b][kpt.q].T.conj())) for v in range(3): dPdR_Mi = dPdR_aqvMi[b][kpt.q][v][Mstart:Mstop] ArhoT_MM = (gemmdot(dPdR_Mi, HP_iM) * rhoT_uMM[u]).real for a, M1, M2 in slices(): dE = 2 * ArhoT_MM[M1:M2].sum() Fatom_av[a, v] += dE # the "b; mu in a; nu" term Fatom_av[b, v] -= dE # the "mu nu" term self.timer.stop('Atomic Hamiltonian force') F_av += Fkin_av + Fpot_av + Ftheta_av + Frho_av + Fatom_av self.timer.start('Wait for sum') ksl.orbital_comm.sum(F_av) if self.bd.comm.rank == 0: self.kpt_comm.sum(F_av, 0) self.timer.stop('Wait for sum') self.timer.stop('LCAO forces')
def get_lcao_projections_HSP(calc, bfs=None, spin=0, projectionsonly=True): """Some title. if projectionsonly is True, return the projections:: V_qnM = <psi_qn | Phi_qM> else, also return the Hamiltonian, overlap, and projector overlaps:: H_qMM = <Phi_qM| H |Phi_qM'> S_qMM = <Phi_qM|Phi_qM'> P_aqMi = <pt^a_qi|Phi_qM> """ if calc.wfs.kd.comm.size != 1: raise NotImplementedError('Parallelization over spin/kpt not ' 'implemented yet.') spos_ac = calc.atoms.get_scaled_positions() % 1. comm = calc.wfs.gd.comm nq = len(calc.wfs.kd.ibzk_qc) Nk = calc.wfs.kd.nibzkpts nao = calc.wfs.setups.nao dtype = calc.wfs.dtype if bfs is None: bfs = get_bfs(calc) tci = TwoCenterIntegrals(calc.wfs.gd.cell_cv, calc.wfs.gd.pbc_c, calc.wfs.setups, calc.wfs.kd.ibzk_qc, calc.wfs.kd.gamma) # Calculate projector overlaps, and (lower triangle of-) S and T matrices S_qMM = np.zeros((nq, nao, nao), dtype) T_qMM = np.zeros((nq, nao, nao), dtype) P_aqMi = {} for a in bfs.my_atom_indices: ni = calc.wfs.setups[a].ni P_aqMi[a] = np.zeros((nq, nao, ni), dtype) tci.calculate(spos_ac, S_qMM, T_qMM, P_aqMi) add_paw_correction_to_overlap(calc.wfs.setups, P_aqMi, S_qMM) calc.wfs.gd.comm.sum(S_qMM) calc.wfs.gd.comm.sum(T_qMM) # Calculate projections V_qnM = np.zeros((nq, calc.wfs.bd.nbands, nao), dtype) for kpt in calc.wfs.kpt_u: if kpt.s != spin: continue V_nM = V_qnM[kpt.q] #bfs.integrate2(kpt.psit_nG[:], V_nM, kpt.q) # all bands to save time for n, V_M in enumerate(V_nM): # band-by-band to save memory bfs.integrate2(kpt.psit_nG[n][:], V_M, kpt.q) for a, P_ni in kpt.P_ani.items(): dS_ii = calc.wfs.setups[a].dO_ii P_Mi = P_aqMi[a][kpt.q] V_nM += np.dot(P_ni, np.inner(dS_ii, P_Mi).conj()) comm.sum(V_qnM) if projectionsonly: return V_qnM # Determine potential matrix vt_G = calc.hamiltonian.vt_sG[spin] H_qMM = np.zeros((nq, nao, nao), dtype) for q, H_MM in enumerate(H_qMM): bfs.calculate_potential_matrix(vt_G, H_MM, q) # Make Hamiltonian as sum of kinetic (T) and potential (V) matrices # and add atomic corrections for a, P_qMi in P_aqMi.items(): dH_ii = unpack(calc.hamiltonian.dH_asp[a][spin]) for P_Mi, H_MM in zip(P_qMi, H_qMM): H_MM += np.dot(P_Mi, np.inner(dH_ii, P_Mi).conj()) comm.sum(H_qMM) H_qMM += T_qMM # Fill in the upper triangles of H and S for H_MM, S_MM in zip(H_qMM, S_qMM): tri2full(H_MM) tri2full(S_MM) H_qMM *= Hartree return V_qnM, H_qMM, S_qMM, P_aqMi
def get_M(self, modes, log=None, q=0): """Calculate el-ph coupling matrix for given modes(s). XXX: kwarg "q=0" is an ugly hack for k-points. There shuold be loops over q! Note that modes must be given as a dictionary with mode frequencies in eV and corresponding mode vectors in units of 1/sqrt(amu), where amu = 1.6605402e-27 Kg is an atomic mass unit. In short frequencies and mode vectors must be given in ase units. :: d d ~ < w | -- v | w' > = < w | -- v | w'> dP dP _ \ ~a d . ~a + ) < w | p > -- /_\H < p | w' > /_ i dP ij j a,ij _ \ d ~a . ~a + ) < w | -- p > /_\H < p | w' > /_ dP i ij j a,ij _ \ ~a . d ~a + ) < w | p > /_\H < -- p | w' > /_ i ij dP j a,ij """ if log is None: timer = nulltimer elif log == '-': timer = StepTimer(name='EPCM') else: timer = StepTimer(name='EPCM', out=open(log, 'w')) modes1 = modes.copy() #convert to atomic units amu = 1.6605402e-27 # atomic unit mass [Kg] me = 9.1093897e-31 # electron mass [Kg] modes = {} for k in modes1.keys(): modes[k / Hartree] = modes1[k] / np.sqrt(amu / me) dvt_Gx, ddH_aspx = self.get_gradient() from gpaw import restart atoms, calc = restart('eq.gpw', txt=None) spos_ac = atoms.get_scaled_positions() if calc.wfs.S_qMM is None: calc.initialize(atoms) calc.initialize_positions(atoms) wfs = calc.wfs nao = wfs.setups.nao bfs = wfs.basis_functions dtype = wfs.dtype spin = 0 # XXX M_lii = {} timer.write_now('Starting gradient of pseudo part') for f, mode in modes.items(): mo = [] M_ii = np.zeros((nao, nao), dtype) for a in self.indices: mo.append(mode[a]) mode = np.asarray(mo).flatten() dvtdP_G = np.dot(dvt_Gx, mode) bfs.calculate_potential_matrix(dvtdP_G, M_ii, q=q) tri2full(M_ii, 'L') M_lii[f] = M_ii timer.write_now('Finished gradient of pseudo part') P_aqMi = calc.wfs.P_aqMi # Add the term # _ # \ ~a d . ~a # ) < w | p > -- /_\H < p | w' > # /_ i dP ij j # a,ij Ma_lii = {} for f, mode in modes.items(): Ma_lii[f] = np.zeros_like(M_lii.values()[0]) timer.write_now('Starting gradient of dH^a part') for f, mode in modes.items(): mo = [] for a in self.indices: mo.append(mode[a]) mode = np.asarray(mo).flatten() for a, ddH_spx in ddH_aspx.items(): ddHdP_sp = np.dot(ddH_spx, mode) ddHdP_ii = unpack2(ddHdP_sp[spin]) Ma_lii[f] += dots(P_aqMi[a][q], ddHdP_ii, P_aqMi[a][q].T) timer.write_now('Finished gradient of dH^a part') timer.write_now('Starting gradient of projectors part') spos_ac = self.atoms.get_scaled_positions() % 1.0 dP_aMix = self.get_dP_aMix(spos_ac, wfs, q, timer) timer.write_now('Finished gradient of projectors part') dH_asp = pickle.load(open('v.eq.pckl'))[1] Mb_lii = {} for f, mode in modes.items(): Mb_lii[f] = np.zeros_like(M_lii.values()[0]) for f, mode in modes.items(): for a, dP_Mix in dP_aMix.items(): dPdP_Mi = np.dot(dP_Mix, mode[a]) dH_ii = unpack2(dH_asp[a][spin]) dPdP_MM = dots(dPdP_Mi, dH_ii, P_aqMi[a][q].T) Mb_lii[f] -= dPdP_MM + dPdP_MM.T # XXX The minus sign here is quite subtle. # It is related to how the derivative of projector # functions in GPAW is calculated. # More thorough explanations, anyone...? # Units of M_lii are Hartree/(Bohr * sqrt(m_e)) for mode in M_lii.keys(): M_lii[mode] += Ma_lii[mode] + Mb_lii[mode] # conversion to eV. The prefactor 1 / sqrt(hb^2 / 2 * hb * f) # has units Bohr * sqrt(me) M_lii_1 = M_lii.copy() M_lii = {} for f in M_lii_1.keys(): M_lii[f * Hartree] = M_lii_1[f] * Hartree / np.sqrt(2 * f) return M_lii
def get_lcao_projections_HSP(calc, bfs=None, spin=0, projectionsonly=True): """Some title. if projectionsonly is True, return the projections:: V_qnM = <psi_qn | Phi_qM> else, also return the Hamiltonian, overlap, and projector overlaps:: H_qMM = <Phi_qM| H |Phi_qM'> S_qMM = <Phi_qM|Phi_qM'> P_aqMi = <pt^a_qi|Phi_qM> """ if calc.wfs.kpt_comm.size != 1: raise NotImplementedError('Parallelization over spin/kpt not ' 'implemented yet.') spos_ac = calc.atoms.get_scaled_positions() % 1. comm = calc.wfs.gd.comm nq = len(calc.wfs.ibzk_qc) Nk = calc.wfs.nibzkpts nao = calc.wfs.setups.nao dtype = calc.wfs.dtype if bfs is None: bfs = get_bfs(calc) tci = TwoCenterIntegrals(calc.wfs.gd.cell_cv, calc.wfs.gd.pbc_c, calc.wfs.setups, calc.wfs.ibzk_qc, calc.wfs.gamma) # Calculate projector overlaps, and (lower triangle of-) S and T matrices S_qMM = np.zeros((nq, nao, nao), dtype) T_qMM = np.zeros((nq, nao, nao), dtype) P_aqMi = {} for a in bfs.my_atom_indices: ni = calc.wfs.setups[a].ni P_aqMi[a] = np.zeros((nq, nao, ni), dtype) tci.calculate(spos_ac, S_qMM, T_qMM, P_aqMi) add_paw_correction_to_overlap(calc.wfs.setups, P_aqMi, S_qMM) calc.wfs.gd.comm.sum(S_qMM) calc.wfs.gd.comm.sum(T_qMM) # Calculate projections V_qnM = np.zeros((nq, calc.wfs.bd.nbands, nao), dtype) for kpt in calc.wfs.kpt_u: if kpt.s != spin: continue V_nM = V_qnM[kpt.q] #bfs.integrate2(kpt.psit_nG[:], V_nM, kpt.q) # all bands to save time for n, V_M in enumerate(V_nM): # band-by-band to save memory bfs.integrate2(kpt.psit_nG[n][:], V_M, kpt.q) for a, P_ni in kpt.P_ani.items(): dS_ii = calc.wfs.setups[a].dO_ii P_Mi = P_aqMi[a][kpt.q] V_nM += np.dot(P_ni, np.inner(dS_ii, P_Mi).conj()) comm.sum(V_qnM) if projectionsonly: return V_qnM # Determine potential matrix vt_G = calc.hamiltonian.vt_sG[spin] H_qMM = np.zeros((nq, nao, nao), dtype) for q, H_MM in enumerate(H_qMM): bfs.calculate_potential_matrix(vt_G, H_MM, q) # Make Hamiltonian as sum of kinetic (T) and potential (V) matrices # and add atomic corrections for a, P_qMi in P_aqMi.items(): dH_ii = unpack(calc.hamiltonian.dH_asp[a][spin]) for P_Mi, H_MM in zip(P_qMi, H_qMM): H_MM += np.dot(P_Mi, np.inner(dH_ii, P_Mi).conj()) comm.sum(H_qMM) H_qMM += T_qMM # Fill in the upper triangles of H and S for H_MM, S_MM in zip(H_qMM, S_qMM): tri2full(H_MM) tri2full(S_MM) H_qMM *= Hartree return V_qnM, H_qMM, S_qMM, P_aqMi
def calculate_supercell_matrix(self, dump=0, name=None, filter=None, include_pseudo=True, atoms=None): """Calculate matrix elements of the el-ph coupling in the LCAO basis. This function calculates the matrix elements between LCAOs and local atomic gradients of the effective potential. The matrix elements are calculated for the supercell used to obtain finite-difference approximations to the derivatives of the effective potential wrt to atomic displacements. Parameters ---------- dump: int Dump supercell matrix to pickle file (default: 0). 0: Supercell matrix not saved 1: Supercell matrix saved in a single pickle file. 2: Dump matrix for different gradients in separate files. Useful for large systems where the total array gets too large for a single pickle file. name: string User specified name of the generated pickle file(s). If not provided, the string in the ``name`` attribute is used. filter: str Fourier filter atomic gradients of the effective potential. The specified components (``normal`` or ``umklapp``) are removed (default: None). include_pseudo: bool Include the contribution from the psedupotential in the atomic gradients. If ``False``, only the gradient of the effective potential is included (default: True). atoms: Atoms object Calculate supercell for an ``Atoms`` object different from the one provided in the ``__init__`` method (WARNING, NOT working!). """ assert self.calc_lcao is not None, "Set LCAO calculator" # Supercell atoms if atoms is None: atoms_N = self.atoms * self.N_c else: atoms_N = atoms # Initialize calculator if required and extract useful quantities calc = self.calc_lcao if not hasattr(calc.wfs, 'S_qMM'): calc.initialize(atoms_N) calc.initialize_positions(atoms_N) self.set_basis_info() basis = calc.input_parameters['basis'] # Extract useful objects from the calculator wfs = calc.wfs gd = calc.wfs.gd kd = calc.wfs.kd kpt_u = wfs.kpt_u setups = wfs.setups nao = setups.nao bfs = wfs.basis_functions dtype = wfs.dtype spin = 0 # XXX # If gamma calculation, overlap with neighboring cell cannot be removed if kd.gamma: print("WARNING: Gamma-point calculation.") else: # Bloch to real-space converter tb = TightBinding(atoms_N, calc) self.timer.write_now("Calculating supercell matrix") self.timer.write_now("Calculating real-space gradients") # Calculate finite-difference gradients (in Hartree / Bohr) V1t_xG, dH1_xasp = self.calculate_gradient() self.timer.write_now("Finished real-space gradients") # Fourier filter the atomic gradients of the effective potential if filter is not None: self.timer.write_now("Fourier filtering gradients") V1_xG = V1t_xG.copy() self.fourier_filter(V1t_xG, components=filter) self.timer.write_now("Finished Fourier filtering") # For the contribution from the derivative of the projectors dP_aqvMi = self.calculate_dP_aqvMi(wfs) # Equilibrium atomic Hamiltonian matrix (projector coefficients) dH_asp = pickle.load(open(self.name + '.eq.pckl'))[1] # Check that the grid is the same as in the calculator assert np.all(V1t_xG.shape[-3:] == (gd.N_c + gd.pbc_c - 1)), \ "Mismatch in grids." # Calculate < i k | grad H | j k >, i.e. matrix elements in Bloch basis # List for supercell matrices; g_xNNMM = [] self.timer.write_now("Calculating gradient of PAW Hamiltonian") # Do each cartesian component separately for i, a in enumerate(self.indices): for v in range(3): # Corresponding array index x = 3 * i + v V1t_G = V1t_xG[x] self.timer.write_now("%s-gradient of atom %u" % (['x', 'y', 'z'][v], a)) # Array for different k-point components g_qMM = np.zeros((len(kpt_u), nao, nao), dtype) # 1) Gradient of effective potential self.timer.write_now( "Starting gradient of effective potential") for kpt in kpt_u: # Matrix elements geff_MM = np.zeros((nao, nao), dtype) bfs.calculate_potential_matrix(V1t_G, geff_MM, q=kpt.q) tri2full(geff_MM, 'L') # Insert in array g_qMM[kpt.q] += geff_MM self.timer.write_now( "Finished gradient of effective potential") if include_pseudo: self.timer.write_now("Starting gradient of pseudo part") # 2) Gradient of non-local part (projectors) self.timer.write_now("Starting gradient of dH^a") P_aqMi = calc.wfs.P_aqMi # 2a) dH^a part has contributions from all other atoms for kpt in kpt_u: # Matrix elements gp_MM = np.zeros((nao, nao), dtype) dH1_asp = dH1_xasp[x] for a_, dH1_sp in dH1_asp.items(): dH1_ii = unpack2(dH1_sp[spin]) gp_MM += np.dot( P_aqMi[a_][kpt.q], np.dot(dH1_ii, P_aqMi[a_][kpt.q].T.conjugate())) g_qMM[kpt.q] += gp_MM self.timer.write_now("Finished gradient of dH^a") self.timer.write_now("Starting gradient of projectors") # 2b) dP^a part has only contributions from the same atoms dP_qvMi = dP_aqvMi[a] dH_ii = unpack2(dH_asp[a][spin]) for kpt in kpt_u: #XXX Sort out the sign here; conclusion -> sign = +1 ! P1HP_MM = +1 * np.dot( dP_qvMi[kpt.q][v], np.dot(dH_ii, P_aqMi[a][kpt.q].T.conjugate())) # Matrix elements gp_MM = P1HP_MM + P1HP_MM.T.conjugate() g_qMM[kpt.q] += gp_MM self.timer.write_now("Finished gradient of projectors") self.timer.write_now("Finished gradient of pseudo part") # Extract R_c=(0, 0, 0) block by Fourier transforming if kd.gamma or kd.N_c is None: g_MM = g_qMM[0] else: # Convert to array g_MM = tb.bloch_to_real_space(g_qMM, R_c=(0, 0, 0))[0] # Reshape to global unit cell indices N = np.prod(self.N_c) # Number of basis function in the primitive cell assert (nao % N) == 0, "Alarm ...!" nao_cell = nao / N g_NMNM = g_MM.reshape((N, nao_cell, N, nao_cell)) g_NNMM = g_NMNM.swapaxes(1, 2).copy() self.timer.write_now("Finished supercell matrix") if dump != 2: g_xNNMM.append(g_NNMM) else: if name is not None: fname = '%s.supercell_matrix_x_%2.2u.%s.pckl' % ( name, x, basis) else: fname = self.name + \ '.supercell_matrix_x_%2.2u.%s.pckl' % (x, basis) if kd.comm.rank == 0: fd = open(fname, 'w') M_a = self.basis_info['M_a'] nao_a = self.basis_info['nao_a'] pickle.dump((g_NNMM, M_a, nao_a), fd, 2) fd.close() self.timer.write_now("Finished gradient of PAW Hamiltonian") if dump != 2: # Collect gradients in one array self.g_xNNMM = np.array(g_xNNMM) # Dump to pickle file using binary mode together with basis info if dump and kd.comm.rank == 0: if name is not None: fname = '%s.supercell_matrix.%s.pckl' % (name, basis) else: fname = self.name + '.supercell_matrix.%s.pckl' % basis fd = open(fname, 'w') M_a = self.basis_info['M_a'] nao_a = self.basis_info['nao_a'] pickle.dump((self.g_xNNMM, M_a, nao_a), fd, 2) fd.close()