def H(W): ''' H dot W where H is Harmiton operator @input: W: Expansion coefficients for Ns unconstrained wave functions, stored as an (S0 * S1 * S2, Ns) matrix @global variables Vdual: Dual potential coefficients stored as a (S0 * S1 * S2, 1) column vector. @return: matrix of shape (S0 * S1 * S2, Ns) ''' f = 2 U = np.dot(W.conj().T, op.O(W)) U_inv = np.linalg.inv(U) # each col of W_real represents W's value in sampled real space W_real = op.cI(W) n = f * op.diagouter(np.dot(W_real, U_inv), W_real) exc = excVWN(n) exc_prime = excpVWN(n) # electrostatic potential (phi) in freqency space phi = op.poisson(n, real_phi=False) Veff = gb_Vdual \ + op.cJdag(op.O(phi)) \ + op.cJdag(op.O(op.cJ(exc))) \ + op.diagprod(exc_prime, op.cJdag(op.O(op.cJ(n)))) HW_potential = op.cIdag(op.diagprod(Veff, op.cI(W))) HW_kinetic = -0.5 * op.L(W) HW = HW_kinetic + HW_potential return HW
def getE(W): ''' get the energy from expansion coefficients for Ns unconstrained wave function @input: W: Expansion coefficients for Ns unconstrained wave functions, stored as an (S0 * S1 * S2, Ns) matrix @global variables Vdual: Dual potential coefficients stored as a (S0 * S1 * S2, 1) column vector. @return: E: Energies summed over Ns states ''' f = 2 U = np.dot(W.conj().T, op.O(W)) U_inv = np.linalg.inv(U) # each col of W_real represents W's value in sampled real space W_real = op.cI(W) n = f * op.diagouter(np.dot(W_real, U_inv), W_real) E_potential = np.dot(gb_Vdual.T, n) E_kinetic = -0.5 * np.trace(np.dot(W.conj().T, op.L(np.dot(W, U_inv)))) * f # electrostatic potential (phi) in freqency space phi = op.poisson(n, real_phi=False) # E_hartree = 0.5 * np.dot(n.conj().T, op.cJdag(op.O(phi))) E_hartree = 0.5 * np.dot(op.cJ(n).conj().T, op.O(phi)) exc = excVWN(n) # the Exc is negative here, which seems problematic. Exc = np.dot(op.cJ(n).conj().T, op.O(op.cJ(exc))) E = (E_potential + E_kinetic + E_hartree + Exc)[0, 0] E = E.real return E
def getgrad(W): ''' get gradient dE/dW ''' U = np.dot(W.conj().T, op.O(W)) U_inv = np.linalg.inv(U) HW = H(W) t1 = np.dot(op.O(W), U_inv) # (#. sample points, Ns) t2 = np.dot(t1, np.dot(W.conj().T, HW)) t3 = HW - t2 t4 = np.dot(t3, U_inv) return t4
def orthonormalizeW(W): ''' orthonormalize W so that Wdag dot O(W) is identity matrix ''' U = np.dot(W.conj().T, op.O(W)) U_inv = np.linalg.inv(U) U_inv_sqrt = linalg.sqrtm(U_inv) W_orthonormal = np.dot(W, U_inv_sqrt) return W_orthonormal
def test_orthonormalizeW(): ns = 4 W = np.random.rand(np.prod(global_vars.S), ns) + 1j * \ np.random.rand(np.prod(global_vars.S), ns) W = orthonormalizeW(W) # Overlap matrix should equal to identity matrix up to machine precison Overlap = np.dot(W.conj().T, op.O(W)) assert np.linalg.norm( np.sum(Overlap - np.identity(ns))) < 1e-6, 'ERROR in orthonormalizeW'
def getE(W): ''' get the energy from expansion coefficients for Ns unconstrained wave function @input: W: Expansion coefficients for Ns unconstrained wave functions, stored as an (S0 * S1 * S2, Ns) matrix @global variables Vdual: Dual potential coefficients stored as a (S0 * S1 * S2, 1) column vector. @return: E: Energies summed over Ns states ''' U = np.dot(W.conj().T, op.O(W)) U_inv = np.linalg.inv(U) W_real = op.cI( W) # each col of W_real represents W's value in sampled real space n = op.diagouter(np.dot(W_real, U_inv), W_real) E_potential = np.dot(gb_Vdual.T, n) E_kinetic = -0.5 * np.trace(np.dot(W.conj().T, op.L(np.dot(W, U_inv)))) E = (E_potential + E_kinetic)[0, 0] E = E.real return E
sum_over_cell = lambda x: np.sum(x) * np.linalg.det(global_vars.R) / \ (global_vars.S[0] * global_vars.S[1] * global_vars.S[2]) # sum(xdv) sigma1, sigma2 = 0.75, 0.5 g1 = gen_gaussian3(dr, sigma1) g2 = gen_gaussian3(dr, sigma2) n = g2 - g1 if global_vars.is_debug: print(sum_over_cell(g1)) print(sum_over_cell(g2)) net_charge = sum_over_cell(n) print('net charge is {:.4f}'.format(net_charge)) assert net_charge < 1e-3, 'Current charge is not zero' phi = op.cI(op.Linv(-4 * np.pi * op.O(op.cJ(n)))) # Due to rounding, tiny imaginary parts creep into the solution. Eliminate # by taking the real part. phi = np.real(phi) # Unum = (0.5 * np.real(np.dot(op.cJ(phi).conj().T, op.O(op.cJ(n)))))[0, 0] Unum = (0.5 * np.dot(phi.T, n)[0]) * (np.linalg.det(global_vars.R) / np.prod(global_vars.S)) Uanal = ((1 / sigma1 + 1 / sigma2) / 2 - np.sqrt(2) / np.sqrt(sigma1**2 + sigma2**2)) / np.sqrt(np.pi) res_deviation = np.abs(Unum - Uanal) print('Deviation between numerical and analytical results is {:.4f}'.format( res_deviation)) if global_vars.is_debug: print(Unum) print(Uanal)
import numpy as np import matplotlib.pyplot as plt import global_vars import op from scipy import linalg import vis lattice_center = np.reshape(np.sum(global_vars.R / 2, axis=1), (1, -1)) dr = np.sqrt(np.sum((global_vars.r - lattice_center)**2, axis=1)) V = 2 * (dr**2) gb_Vdual = op.cJdag(op.O(op.cJ(V))) def getE(W): ''' get the energy from expansion coefficients for Ns unconstrained wave function @input: W: Expansion coefficients for Ns unconstrained wave functions, stored as an (S0 * S1 * S2, Ns) matrix @global variables Vdual: Dual potential coefficients stored as a (S0 * S1 * S2, 1) column vector. @return: E: Energies summed over Ns states ''' U = np.dot(W.conj().T, op.O(W)) U_inv = np.linalg.inv(U) W_real = op.cI( W) # each col of W_real represents W's value in sampled real space n = op.diagouter(np.dot(W_real, U_inv), W_real) E_potential = np.dot(gb_Vdual.T, n) E_kinetic = -0.5 * np.trace(np.dot(W.conj().T, op.L(np.dot(W, U_inv))))