def calculate_field( gd, rho_g, bgef_v, phi_g, ef_vg, fe_g, # preallocated numpy arrays nv=3, poisson_nn=3, poisson_relax='J', gradient_n=3, poisson_eps=1e-20): dtype = rho_g.dtype yes_complex = dtype == complex phi_g[:] = 0.0 ef_vg[:] = 0.0 fe_g[:] = 0.0 tmp_g = gd.zeros(dtype=float) # Poissonsolver poissonsolver = PoissonSolver(nn=poisson_nn, relax=poisson_relax, eps=poisson_eps) poissonsolver.set_grid_descriptor(gd) poissonsolver.initialize() # Potential, real part poissonsolver.solve(tmp_g, rho_g.real.copy()) phi_g += tmp_g # Potential, imag part if yes_complex: tmp_g[:] = 0.0 poissonsolver.solve(tmp_g, rho_g.imag.copy()) phi_g += 1.0j * tmp_g # Gradient gradient = [Gradient(gd, v, scale=1.0, n=gradient_n) for v in range(nv)] for v in range(nv): # Electric field, real part gradient[v].apply(-phi_g.real, tmp_g) ef_vg[v] += tmp_g # Electric field, imag part if yes_complex: gradient[v].apply(-phi_g.imag, tmp_g) ef_vg[v] += 1.0j * tmp_g # Electric field enhancement tmp_g[:] = 0.0 # total electric field norm bgefnorm = 0.0 # background electric field norm for v in range(nv): tmp_g += np.absolute(bgef_v[v] + ef_vg[v])**2 bgefnorm += np.absolute(bgef_v[v])**2 tmp_g = np.sqrt(tmp_g) bgefnorm = np.sqrt(bgefnorm) fe_g[:] = tmp_g / bgefnorm
class ZerothOrder1(State): def __init__(self, calculator): self.calculator = calculator State.__init__(self, calculator.gd) self.pw = PlaneWave(self.gd) # get effective potential hamiltonian = self.calculator.hamiltonian if 1: self.vt_G = hamiltonian.vt_sG[0] # XXX treat spin else: self.vt_G = np.where(hamiltonian.vt_sG[0] > 0, 0.0, hamiltonian.vt_sG[0]) self.intvt = self.gd.integrate(self.vt_G) print('# int(vt_G)=', self.intvt, np.sometrue(self.vt_G > 0)) self.solve() def get_grid(self, k_c, r0): print('Correction:', self.gd.integrate(self.corrt_G)) if not hasattr(self, 'written'): print('r0', r0, r0 * Bohr) dR = 0.3 AI = AngularIntegral(r0 * Bohr, self.gd, dR=dR) r_R = AI.radii() psi_R = AI.average(self.corrt_G) v_R = AI.average(self.vt_G) f = open('radial_dR' + str(dR) + '.dat', 'w') print('# R v(R) psi(R)', file=f) for r, psi, v in zip(r_R, psi_R, v_R): print(r, psi, v, file=f) f.close() self.written = True return self.pw.get_grid(k_c, r0) - 1e6 * self.corrt_G def solve(self): hamiltonian = self.calculator.hamiltonian self.poisson = PoissonSolver(nn=hamiltonian.poisson.nn) self.poisson.set_grid_descriptor(self.gd) self.poisson.initialize() corrt_G = self.gd.empty() self.poisson.solve(corrt_G, self.vt_G, charge=None) corrt_G /= (2 * pi) ** (5. / 2) self.corrt_G = corrt_G
class CoulombNEW: def __init__(self, gd, setups, spos_ac, fft=False): assert gd.comm.size == 1 self.rhot1_G = gd.empty() self.rhot2_G = gd.empty() self.pot_G = gd.empty() self.dv = gd.dv if fft: self.poisson = FFTPoissonSolver() else: self.poisson = PoissonSolver(name='fd', nn=3) self.poisson.set_grid_descriptor(gd) self.setups = setups # Set coarse ghat self.Ghat = LFC(gd, [setup.ghat_l for setup in setups], integral=sqrt(4 * pi)) self.Ghat.set_positions(spos_ac) def calculate(self, nt1_G, nt2_G, P1_ap, P2_ap): I = 0.0 self.rhot1_G[:] = nt1_G self.rhot2_G[:] = nt2_G Q1_aL = {} Q2_aL = {} for a, P1_p in P1_ap.items(): P2_p = P2_ap[a] setup = self.setups[a] # Add atomic corrections to integral I += 2 * np.dot(P1_p, np.dot(setup.M_pp, P2_p)) # Add compensation charges to pseudo densities Q1_aL[a] = np.dot(P1_p, setup.Delta_pL) Q2_aL[a] = np.dot(P2_p, setup.Delta_pL) self.Ghat.add(self.rhot1_G, Q1_aL) self.Ghat.add(self.rhot2_G, Q2_aL) # Add coulomb energy of compensated pseudo densities to integral self.poisson.solve(self.pot_G, self.rhot2_G, charge=None, eps=1e-12, zero_initial_phi=True) I += np.vdot(self.rhot1_G, self.pot_G) * self.dv return I * Hartree
class ZerothOrder1(State): def __init__(self, calculator): self.calculator = calculator State.__init__(self, calculator.gd) self.pw = PlaneWave(self.gd) # get effective potential hamiltonian = self.calculator.hamiltonian if 1: self.vt_G = hamiltonian.vt_sG[0] # XXX treat spin else: self.vt_G = np.where(hamiltonian.vt_sG[0] > 0, 0.0, hamiltonian.vt_sG[0]) self.intvt = self.gd.integrate(self.vt_G) print('# int(vt_G)=', self.intvt, np.sometrue(self.vt_G > 0)) self.solve() def get_grid(self, k_c, r0): print('Correction:', self.gd.integrate(self.corrt_G)) if not hasattr(self, 'written'): print('r0', r0, r0 * Bohr) dR = 0.3 AI = AngularIntegral(r0 * Bohr, self.gd, dR=dR) r_R = AI.radii() psi_R = AI.average(self.corrt_G) v_R = AI.average(self.vt_G) f = open('radial_dR' + str(dR) + '.dat', 'w') print('# R v(R) psi(R)', file=f) for r, psi, v in zip(r_R, psi_R, v_R): print(r, psi, v, file=f) f.close() self.written = True return self.pw.get_grid(k_c, r0) - 1e6 * self.corrt_G def solve(self): hamiltonian = self.calculator.hamiltonian self.poisson = PoissonSolver('fd', nn=hamiltonian.poisson.nn) self.poisson.set_grid_descriptor(self.gd) self.poisson.initialize() corrt_G = self.gd.empty() self.poisson.solve(corrt_G, self.vt_G, charge=None) corrt_G /= (2 * pi)**(5. / 2) self.corrt_G = corrt_G
class CoulombNEW: def __init__(self, gd, setups, spos_ac, fft=False): assert gd.comm.size == 1 self.rhot1_G = gd.empty() self.rhot2_G = gd.empty() self.pot_G = gd.empty() self.dv = gd.dv if fft: self.poisson = FFTPoissonSolver() else: self.poisson = PoissonSolver(nn=3) self.poisson.set_grid_descriptor(gd) self.poisson.initialize() self.setups = setups # Set coarse ghat self.Ghat = LFC(gd, [setup.ghat_l for setup in setups], integral=sqrt(4 * pi)) self.Ghat.set_positions(spos_ac) def calculate(self, nt1_G, nt2_G, P1_ap, P2_ap): I = 0.0 self.rhot1_G[:] = nt1_G self.rhot2_G[:] = nt2_G Q1_aL = {} Q2_aL = {} for a, P1_p in P1_ap.items(): P2_p = P2_ap[a] setup = self.setups[a] # Add atomic corrections to integral I += 2 * np.dot(P1_p, np.dot(setup.M_pp, P2_p)) # Add compensation charges to pseudo densities Q1_aL[a] = np.dot(P1_p, setup.Delta_pL) Q2_aL[a] = np.dot(P2_p, setup.Delta_pL) self.Ghat.add(self.rhot1_G, Q1_aL) self.Ghat.add(self.rhot2_G, Q2_aL) # Add coulomb energy of compensated pseudo densities to integral self.poisson.solve(self.pot_G, self.rhot2_G, charge=None, eps=1e-12, zero_initial_phi=True) I += np.vdot(self.rhot1_G, self.pot_G) * self.dv return I * Hartree
class HybridXC(HybridXCBase): def __init__(self, name, hybrid=None, xc=None, finegrid=False, unocc=False, omega=None, excitation=None, excited=0, stencil=2): """Mix standard functionals with exact exchange. finegrid: boolean Use fine grid for energy functional evaluations ? unocc: boolean Apply vxx also to unoccupied states ? omega: float RSF mixing parameter excitation: string: Apply operator for improved virtual orbitals to unocc states? Possible modes: singlet: excitations to singlets triplet: excitations to triplets average: average between singlets and tripletts see f.e. http://dx.doi.org/10.1021/acs.jctc.8b00238 excited: number Band to excite from - counted from H**O downwards """ self.finegrid = finegrid self.unocc = unocc self.excitation = excitation self.excited = excited HybridXCBase.__init__(self, name, hybrid=hybrid, xc=xc, omega=omega, stencil=stencil) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, density, hamiltonian, wfs, occupations): assert wfs.kd.gamma self.xc.initialize(density, hamiltonian, wfs, occupations) self.kpt_comm = wfs.kd.comm self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.exx_s = np.zeros(self.nspins) self.ekin_s = np.zeros(self.nspins) self.nocc_s = np.empty(self.nspins, int) self.gd = density.gd self.redistributor = density.redistributor use_charge_center = hamiltonian.poisson.use_charge_center # XXX How do we construct a copy of the Poisson solver of the # Hamiltonian? We don't know what class it is, etc., but gd # may differ. # XXX One might consider using a charged centered compensation # charge for the PoissonSolver in the case of EXX as standard self.poissonsolver = PoissonSolver('fd', eps=1e-11, use_charge_center=use_charge_center) # self.poissonsolver = hamiltonian.poisson if self.finegrid: self.finegd = self.gd.refine() # XXX Taking restrictor from Hamiltonian will not work in PW mode, # will it? I think this supports only real-space mode. # self.restrictor = hamiltonian.restrictor self.restrictor = Transformer(self.finegd, self.gd, 3) self.interpolator = Transformer(self.gd, self.finegd, 3) else: self.finegd = self.gd self.ghat = LFC(self.finegd, [setup.ghat_l for setup in density.setups], integral=np.sqrt(4 * np.pi), forces=True) self.poissonsolver.set_grid_descriptor(self.finegd) if self.rsf == 'Yukawa': omega2 = self.omega**2 self.screened_poissonsolver = HelmholtzSolver( k2=-omega2, eps=1e-11, nn=3, use_charge_center=use_charge_center) self.screened_poissonsolver.set_grid_descriptor(self.finegd) def set_positions(self, spos_ac): self.ghat.set_positions(spos_ac) def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Note that the quantities passed are on the # density/Hamiltonian grids! # They may be distributed differently from own quantities. self.ekin = self.kpt_comm.sum(self.ekin_s.sum()) return exc + self.kpt_comm.sum(self.exx_s.sum()) def calculate_exx(self): for kpt in self.kpt_u: self.apply_orbital_dependent_hamiltonian(kpt, kpt.psit_nG) def apply_orbital_dependent_hamiltonian(self, kpt, psit_nG, Htpsit_nG=None, dH_asp=None): if kpt.f_n is None: return deg = 2 // self.nspins # Spin degeneracy hybrid = self.hybrid P_ani = kpt.P_ani setups = self.setups is_cam = self.is_cam vt_g = self.finegd.empty() if self.gd is not self.finegd: vt_G = self.gd.empty() if self.rsf == 'Yukawa': y_vt_g = self.finegd.empty() # if self.gd is not self.finegd: # y_vt_G = self.gd.empty() nocc = int(ceil(kpt.f_n.sum())) // (3 - self.nspins) if self.excitation is not None: ex_band = nocc - self.excited - 1 if self.excitation == 'singlet': ex_weight = -1 elif self.excitation == 'triplet': ex_weight = +1 else: ex_weight = 0 if self.unocc or self.excitation is not None: nbands = len(kpt.f_n) else: nbands = nocc self.nocc_s[kpt.s] = nocc if Htpsit_nG is not None: kpt.vt_nG = self.gd.empty(nbands) kpt.vxx_ani = {} kpt.vxx_anii = {} for a, P_ni in P_ani.items(): I = P_ni.shape[1] kpt.vxx_ani[a] = np.zeros((nbands, I)) kpt.vxx_anii[a] = np.zeros((nbands, I, I)) exx = 0.0 ekin = 0.0 # XXXX nbands can be different numbers on different cpus! # That means some will execute the loop and others not. # And deadlocks with augment-grids. # Determine pseudo-exchange for n1 in range(nbands): psit1_G = psit_nG[n1] f1 = kpt.f_n[n1] / deg for n2 in range(n1, nbands): psit2_G = psit_nG[n2] f2 = kpt.f_n[n2] / deg if n1 != n2 and f1 == 0 and f1 == f2: continue # Don't work on double unocc. bands # Double count factor: dc = (1 + (n1 != n2)) * deg nt_G, rhot_g = self.calculate_pair_density( n1, n2, psit_nG, P_ani) vt_g[:] = 0.0 # XXXXX This will go wrong because we are solving the # Poisson equation on the distribution of gd, not finegd # Or maybe it's fixed now self.poissonsolver.solve(vt_g, -rhot_g, charge=-float(n1 == n2), eps=1e-12, zero_initial_phi=True) vt_g *= hybrid if self.rsf == 'Yukawa': y_vt_g[:] = 0.0 self.screened_poissonsolver.solve(y_vt_g, -rhot_g, charge=-float(n1 == n2), eps=1e-12, zero_initial_phi=True) if is_cam: # Cam like correction y_vt_g *= self.cam_beta else: y_vt_g *= hybrid vt_g -= y_vt_g if self.gd is self.finegd: vt_G = vt_g else: self.restrictor.apply(vt_g, vt_G) # Integrate the potential on fine and coarse grids int_fine = self.finegd.integrate(vt_g * rhot_g) int_coarse = self.gd.integrate(vt_G * nt_G) if self.gd.comm.rank == 0: # only add to energy on master CPU exx += 0.5 * dc * f1 * f2 * int_fine ekin -= dc * f1 * f2 * int_coarse if Htpsit_nG is not None: Htpsit_nG[n1] += f2 * vt_G * psit2_G if n1 == n2: kpt.vt_nG[n1] = f1 * vt_G if self.excitation is not None and n1 == ex_band: Htpsit_nG[nocc:] += f1 * vt_G * psit_nG[nocc:] else: if self.excitation is None or n1 != ex_band \ or n2 < nocc: Htpsit_nG[n2] += f1 * vt_G * psit1_G else: Htpsit_nG[n2] += f1 * ex_weight * vt_G * psit1_G # Update the vxx_uni and vxx_unii vectors of the nuclei, # used to determine the atomic hamiltonian, and the # residuals v_aL = self.ghat.dict() self.ghat.integrate(vt_g, v_aL) for a, v_L in v_aL.items(): v_ii = unpack(np.dot(setups[a].Delta_pL, v_L)) v_ni = kpt.vxx_ani[a] v_nii = kpt.vxx_anii[a] P_ni = P_ani[a] v_ni[n1] += f2 * np.dot(v_ii, P_ni[n2]) if n1 != n2: if self.excitation is None or n1 != ex_band or \ n2 < nocc: v_ni[n2] += f1 * np.dot(v_ii, P_ni[n1]) else: v_ni[n2] += f1 * ex_weight * \ np.dot(v_ii, P_ni[n1]) else: # XXX Check this: v_nii[n1] = f1 * v_ii if self.excitation is not None and n1 == ex_band: for nuoc in range(nocc, nbands): v_ni[nuoc] += f1 * \ np.dot(v_ii, P_ni[nuoc]) def calculate_vv(ni, D_ii, M_pp, weight, addme=False): """Calculate the local corrections depending on Mpp.""" dexx = 0 dekin = 0 if not addme: addsign = -2.0 else: addsign = 2.0 for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += M_pp[p13, p24] * D_ii[i3, i4] p12 = packed_index(i1, i2, ni) if Htpsit_nG is not None: dH_p[p12] += addsign * weight / \ deg * A / ((i1 != i2) + 1) dekin += 2 * weight / deg * D_ii[i1, i2] * A dexx -= weight / deg * D_ii[i1, i2] * A return (dexx, dekin) # Apply the atomic corrections to the energy and the Hamiltonian # matrix for a, P_ni in P_ani.items(): setup = setups[a] if Htpsit_nG is not None: # Add non-trivial corrections the Hamiltonian matrix h_nn = symmetrize( np.inner(P_ni[:nbands], kpt.vxx_ani[a][:nbands])) ekin -= np.dot(kpt.f_n[:nbands], h_nn.diagonal()) dH_p = dH_asp[a][kpt.s] # Get atomic density and Hamiltonian matrices D_p = self.density.D_asp[a][kpt.s] D_ii = unpack2(D_p) ni = len(D_ii) # Add atomic corrections to the valence-valence exchange energy # -- # > D C D # -- ii iiii ii (dexx, dekin) = calculate_vv(ni, D_ii, setup.M_pp, hybrid) ekin += dekin exx += dexx if self.rsf is not None: Mg_pp = setup.calculate_yukawa_interaction(self.omega) if is_cam: (dexx, dekin) = calculate_vv(ni, D_ii, Mg_pp, self.cam_beta, addme=True) else: (dexx, dekin) = calculate_vv(ni, D_ii, Mg_pp, hybrid, addme=True) ekin -= dekin exx -= dexx # Add valence-core exchange energy # -- # > X D # -- ii ii if setup.X_p is not None: exx -= hybrid * np.dot(D_p, setup.X_p) if Htpsit_nG is not None: dH_p -= hybrid * setup.X_p ekin += hybrid * np.dot(D_p, setup.X_p) if self.rsf == 'Yukawa' and setup.X_pg is not None: if is_cam: thybrid = self.cam_beta # 0th order else: thybrid = hybrid exx += thybrid * np.dot(D_p, setup.X_pg) if Htpsit_nG is not None: dH_p += thybrid * setup.X_pg ekin -= thybrid * np.dot(D_p, setup.X_pg) elif self.rsf == 'Yukawa' and setup.X_pg is None: thybrid = exp(-3.62e-2 * self.omega) # educated guess if is_cam: thybrid *= self.cam_beta else: thybrid *= hybrid exx += thybrid * np.dot(D_p, setup.X_p) if Htpsit_nG is not None: dH_p += thybrid * setup.X_p ekin -= thybrid * np.dot(D_p, setup.X_p) # Add core-core exchange energy if kpt.s == 0: if self.rsf is None or is_cam: if is_cam: exx += self.cam_alpha * setup.ExxC else: exx += hybrid * setup.ExxC self.exx_s[kpt.s] = self.gd.comm.sum(exx) self.ekin_s[kpt.s] = self.gd.comm.sum(ekin) def correct_hamiltonian_matrix(self, kpt, H_nn): if not hasattr(kpt, 'vxx_ani'): return # if self.gd.comm.rank > 0: # H_nn[:] = 0.0 nocc = self.nocc_s[kpt.s] nbands = len(kpt.vt_nG) for a, P_ni in kpt.P_ani.items(): H_nn[:nbands, :nbands] += symmetrize( np.inner(P_ni[:nbands], kpt.vxx_ani[a])) # self.gd.comm.sum(H_nn) if not self.unocc or self.excitation is not None: H_nn[:nocc, nocc:] = 0.0 H_nn[nocc:, :nocc] = 0.0 def calculate_pair_density(self, n1, n2, psit_nG, P_ani): Q_aL = {} for a, P_ni in P_ani.items(): P1_i = P_ni[n1] P2_i = P_ni[n2] D_ii = np.outer(P1_i, P2_i.conj()).real D_p = pack(D_ii) Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL) nt_G = psit_nG[n1] * psit_nG[n2] if self.finegd is self.gd: nt_g = nt_G else: nt_g = self.finegd.empty() self.interpolator.apply(nt_G, nt_g) rhot_g = nt_g.copy() self.ghat.add(rhot_g, Q_aL) return nt_G, rhot_g def add_correction(self, kpt, psit_xG, Htpsit_xG, P_axi, c_axi, n_x, calculate_change=False): if kpt.f_n is None: return if self.unocc or self.excitation is not None: nocc = len(kpt.vt_nG) else: nocc = self.nocc_s[kpt.s] if calculate_change: for x, n in enumerate(n_x): if n < nocc: Htpsit_xG[x] += kpt.vt_nG[n] * psit_xG[x] for a, P_xi in P_axi.items(): c_axi[a][x] += np.dot(kpt.vxx_anii[a][n], P_xi[x]) else: for a, c_xi in c_axi.items(): c_xi[:nocc] += kpt.vxx_ani[a][:nocc] def rotate(self, kpt, U_nn): if kpt.f_n is None: return U_nn = U_nn.T.copy() nocc = self.nocc_s[kpt.s] if len(kpt.vt_nG) == nocc: U_nn = U_nn[:nocc, :nocc] gemm(1.0, kpt.vt_nG.copy(), U_nn, 0.0, kpt.vt_nG) for v_ni in kpt.vxx_ani.values(): gemm(1.0, v_ni.copy(), U_nn, 0.0, v_ni) for v_nii in kpt.vxx_anii.values(): gemm(1.0, v_nii.copy(), U_nn, 0.0, v_nii)
class HybridXC(HybridXCBase): def __init__(self, name, hybrid=None, xc=None, finegrid=False, unocc=False): """Mix standard functionals with exact exchange. finegrid: boolean Use fine grid for energy functional evaluations ? unocc: boolean Apply vxx also to unoccupied states ? """ self.finegrid = finegrid self.unocc = unocc HybridXCBase.__init__(self, name, hybrid, xc) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, density, hamiltonian, wfs, occupations): assert wfs.gamma self.xc.initialize(density, hamiltonian, wfs, occupations) self.kpt_comm = wfs.kpt_comm self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.exx_s = np.zeros(self.nspins) self.ekin_s = np.zeros(self.nspins) self.nocc_s = np.empty(self.nspins, int) if self.finegrid: self.poissonsolver = hamiltonian.poisson self.ghat = density.ghat self.interpolator = density.interpolator self.restrictor = hamiltonian.restrictor else: self.poissonsolver = PoissonSolver(eps=1e-11) self.poissonsolver.set_grid_descriptor(density.gd) self.poissonsolver.initialize() self.ghat = LFC(density.gd, [setup.ghat_l for setup in density.setups], integral=np.sqrt(4 * np.pi), forces=True) self.gd = density.gd self.finegd = self.ghat.gd def set_positions(self, spos_ac): if not self.finegrid: self.ghat.set_positions(spos_ac) def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) self.ekin = self.kpt_comm.sum(self.ekin_s.sum()) return exc + self.kpt_comm.sum(self.exx_s.sum()) def calculate_exx(self): for kpt in self.kpt_u: self.apply_orbital_dependent_hamiltonian(kpt, kpt.psit_nG) def apply_orbital_dependent_hamiltonian(self, kpt, psit_nG, Htpsit_nG=None, dH_asp=None): if kpt.f_n is None: return deg = 2 // self.nspins # Spin degeneracy hybrid = self.hybrid P_ani = kpt.P_ani setups = self.setups vt_g = self.finegd.empty() if self.gd is not self.finegd: vt_G = self.gd.empty() nocc = int(kpt.f_n.sum()) // (3 - self.nspins) if self.unocc: nbands = len(kpt.f_n) else: nbands = nocc self.nocc_s[kpt.s] = nocc if Htpsit_nG is not None: kpt.vt_nG = self.gd.empty(nbands) kpt.vxx_ani = {} kpt.vxx_anii = {} for a, P_ni in P_ani.items(): I = P_ni.shape[1] kpt.vxx_ani[a] = np.zeros((nbands, I)) kpt.vxx_anii[a] = np.zeros((nbands, I, I)) exx = 0.0 ekin = 0.0 # Determine pseudo-exchange for n1 in range(nbands): psit1_G = psit_nG[n1] f1 = kpt.f_n[n1] / deg for n2 in range(n1, nbands): psit2_G = psit_nG[n2] f2 = kpt.f_n[n2] / deg # Double count factor: dc = (1 + (n1 != n2)) * deg nt_G, rhot_g = self.calculate_pair_density(n1, n2, psit_nG, P_ani) vt_g[:] = 0.0 iter = self.poissonsolver.solve(vt_g, -rhot_g, charge=-float(n1 == n2), eps=1e-12, zero_initial_phi=True) vt_g *= hybrid if self.gd is self.finegd: vt_G = vt_g else: self.restrictor.apply(vt_g, vt_G) # Integrate the potential on fine and coarse grids int_fine = self.finegd.integrate(vt_g * rhot_g) int_coarse = self.gd.integrate(vt_G * nt_G) if self.gd.comm.rank == 0: # only add to energy on master CPU exx += 0.5 * dc * f1 * f2 * int_fine ekin -= dc * f1 * f2 * int_coarse if Htpsit_nG is not None: Htpsit_nG[n1] += f2 * vt_G * psit2_G if n1 == n2: kpt.vt_nG[n1] = f1 * vt_G else: Htpsit_nG[n2] += f1 * vt_G * psit1_G # Update the vxx_uni and vxx_unii vectors of the nuclei, # used to determine the atomic hamiltonian, and the # residuals v_aL = self.ghat.dict() self.ghat.integrate(vt_g, v_aL) for a, v_L in v_aL.items(): v_ii = unpack(np.dot(setups[a].Delta_pL, v_L)) v_ni = kpt.vxx_ani[a] v_nii = kpt.vxx_anii[a] P_ni = P_ani[a] v_ni[n1] += f2 * np.dot(v_ii, P_ni[n2]) if n1 != n2: v_ni[n2] += f1 * np.dot(v_ii, P_ni[n1]) else: # XXX Check this: v_nii[n1] = f1 * v_ii # Apply the atomic corrections to the energy and the Hamiltonian matrix for a, P_ni in P_ani.items(): setup = setups[a] if Htpsit_nG is not None: # Add non-trivial corrections the Hamiltonian matrix h_nn = symmetrize(np.inner(P_ni[:nbands], kpt.vxx_ani[a][:nbands])) ekin -= np.dot(kpt.f_n[:nbands], h_nn.diagonal()) dH_p = dH_asp[a][kpt.s] # Get atomic density and Hamiltonian matrices D_p = self.density.D_asp[a][kpt.s] D_ii = unpack2(D_p) ni = len(D_ii) # Add atomic corrections to the valence-valence exchange energy # -- # > D C D # -- ii iiii ii for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] p12 = packed_index(i1, i2, ni) if Htpsit_nG is not None: dH_p[p12] -= 2 * hybrid / deg * A / ((i1 != i2) + 1) ekin += 2 * hybrid / deg * D_ii[i1, i2] * A exx -= hybrid / deg * D_ii[i1, i2] * A # Add valence-core exchange energy # -- # > X D # -- ii ii if setup.X_p is not None: exx -= hybrid * np.dot(D_p, setup.X_p) if Htpsit_nG is not None: dH_p -= hybrid * setup.X_p ekin += hybrid * np.dot(D_p, setup.X_p) # Add core-core exchange energy if kpt.s == 0: exx += hybrid * setup.ExxC self.exx_s[kpt.s] = self.gd.comm.sum(exx) self.ekin_s[kpt.s] = self.gd.comm.sum(ekin) def correct_hamiltonian_matrix(self, kpt, H_nn): if not hasattr(kpt, 'vxx_ani'): return if self.gd.comm.rank > 0: H_nn[:] = 0.0 nocc = self.nocc_s[kpt.s] nbands = len(kpt.vt_nG) for a, P_ni in kpt.P_ani.items(): H_nn[:nbands, :nbands] += symmetrize(np.inner(P_ni[:nbands], kpt.vxx_ani[a])) self.gd.comm.sum(H_nn) H_nn[:nocc, nocc:] = 0.0 H_nn[nocc:, :nocc] = 0.0 def calculate_pair_density(self, n1, n2, psit_nG, P_ani): Q_aL = {} for a, P_ni in P_ani.items(): P1_i = P_ni[n1] P2_i = P_ni[n2] D_ii = np.outer(P1_i, P2_i.conj()).real D_p = pack(D_ii) Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL) nt_G = psit_nG[n1] * psit_nG[n2] if self.finegd is self.gd: nt_g = nt_G else: nt_g = self.finegd.empty() self.interpolator.apply(nt_G, nt_g) rhot_g = nt_g.copy() self.ghat.add(rhot_g, Q_aL) return nt_G, rhot_g def add_correction(self, kpt, psit_xG, Htpsit_xG, P_axi, c_axi, n_x, calculate_change=False): if kpt.f_n is None: return nocc = self.nocc_s[kpt.s] if calculate_change: for x, n in enumerate(n_x): if n < nocc: Htpsit_xG[x] += kpt.vt_nG[n] * psit_xG[x] for a, P_xi in P_axi.items(): c_axi[a][x] += np.dot(kpt.vxx_anii[a][n], P_xi[x]) else: for a, c_xi in c_axi.items(): c_xi[:nocc] += kpt.vxx_ani[a][:nocc] def rotate(self, kpt, U_nn): if kpt.f_n is None: return nocc = self.nocc_s[kpt.s] if len(kpt.vt_nG) == nocc: U_nn = U_nn[:nocc, :nocc] gemm(1.0, kpt.vt_nG.copy(), U_nn, 0.0, kpt.vt_nG) for v_ni in kpt.vxx_ani.values(): gemm(1.0, v_ni.copy(), U_nn, 0.0, v_ni) for v_nii in kpt.vxx_anii.values(): gemm(1.0, v_nii.copy(), U_nn, 0.0, v_nii)
class HybridXC(HybridXCBase): def __init__(self, name, hybrid=None, xc=None, finegrid=False, unocc=False): """Mix standard functionals with exact exchange. finegrid: boolean Use fine grid for energy functional evaluations ? unocc: boolean Apply vxx also to unoccupied states ? """ self.finegrid = finegrid self.unocc = unocc HybridXCBase.__init__(self, name, hybrid, xc) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, density, hamiltonian, wfs, occupations): assert wfs.kd.gamma self.xc.initialize(density, hamiltonian, wfs, occupations) self.kpt_comm = wfs.kd.comm self.nspins = wfs.nspins self.setups = wfs.setups self.density = density self.kpt_u = wfs.kpt_u self.exx_s = np.zeros(self.nspins) self.ekin_s = np.zeros(self.nspins) self.nocc_s = np.empty(self.nspins, int) if self.finegrid: self.poissonsolver = hamiltonian.poisson self.ghat = density.ghat self.interpolator = density.interpolator self.restrictor = hamiltonian.restrictor else: self.poissonsolver = PoissonSolver(eps=1e-11) self.poissonsolver.set_grid_descriptor(density.gd) self.poissonsolver.initialize() self.ghat = LFC(density.gd, [setup.ghat_l for setup in density.setups], integral=np.sqrt(4 * np.pi), forces=True) self.gd = density.gd self.finegd = self.ghat.gd def set_positions(self, spos_ac): if not self.finegrid: self.ghat.set_positions(spos_ac) def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) self.ekin = self.kpt_comm.sum(self.ekin_s.sum()) return exc + self.kpt_comm.sum(self.exx_s.sum()) def calculate_exx(self): for kpt in self.kpt_u: self.apply_orbital_dependent_hamiltonian(kpt, kpt.psit_nG) def apply_orbital_dependent_hamiltonian(self, kpt, psit_nG, Htpsit_nG=None, dH_asp=None): if kpt.f_n is None: return deg = 2 // self.nspins # Spin degeneracy hybrid = self.hybrid P_ani = kpt.P_ani setups = self.setups vt_g = self.finegd.empty() if self.gd is not self.finegd: vt_G = self.gd.empty() nocc = int(kpt.f_n.sum()) // (3 - self.nspins) if self.unocc: nbands = len(kpt.f_n) else: nbands = nocc self.nocc_s[kpt.s] = nocc if Htpsit_nG is not None: kpt.vt_nG = self.gd.empty(nbands) kpt.vxx_ani = {} kpt.vxx_anii = {} for a, P_ni in P_ani.items(): I = P_ni.shape[1] kpt.vxx_ani[a] = np.zeros((nbands, I)) kpt.vxx_anii[a] = np.zeros((nbands, I, I)) exx = 0.0 ekin = 0.0 # Determine pseudo-exchange for n1 in range(nbands): psit1_G = psit_nG[n1] f1 = kpt.f_n[n1] / deg for n2 in range(n1, nbands): psit2_G = psit_nG[n2] f2 = kpt.f_n[n2] / deg # Double count factor: dc = (1 + (n1 != n2)) * deg nt_G, rhot_g = self.calculate_pair_density( n1, n2, psit_nG, P_ani) vt_g[:] = 0.0 iter = self.poissonsolver.solve(vt_g, -rhot_g, charge=-float(n1 == n2), eps=1e-12, zero_initial_phi=True) vt_g *= hybrid if self.gd is self.finegd: vt_G = vt_g else: self.restrictor.apply(vt_g, vt_G) # Integrate the potential on fine and coarse grids int_fine = self.finegd.integrate(vt_g * rhot_g) int_coarse = self.gd.integrate(vt_G * nt_G) if self.gd.comm.rank == 0: # only add to energy on master CPU exx += 0.5 * dc * f1 * f2 * int_fine ekin -= dc * f1 * f2 * int_coarse if Htpsit_nG is not None: Htpsit_nG[n1] += f2 * vt_G * psit2_G if n1 == n2: kpt.vt_nG[n1] = f1 * vt_G else: Htpsit_nG[n2] += f1 * vt_G * psit1_G # Update the vxx_uni and vxx_unii vectors of the nuclei, # used to determine the atomic hamiltonian, and the # residuals v_aL = self.ghat.dict() self.ghat.integrate(vt_g, v_aL) for a, v_L in v_aL.items(): v_ii = unpack(np.dot(setups[a].Delta_pL, v_L)) v_ni = kpt.vxx_ani[a] v_nii = kpt.vxx_anii[a] P_ni = P_ani[a] v_ni[n1] += f2 * np.dot(v_ii, P_ni[n2]) if n1 != n2: v_ni[n2] += f1 * np.dot(v_ii, P_ni[n1]) else: # XXX Check this: v_nii[n1] = f1 * v_ii # Apply the atomic corrections to the energy and the Hamiltonian matrix for a, P_ni in P_ani.items(): setup = setups[a] if Htpsit_nG is not None: # Add non-trivial corrections the Hamiltonian matrix h_nn = symmetrize( np.inner(P_ni[:nbands], kpt.vxx_ani[a][:nbands])) ekin -= np.dot(kpt.f_n[:nbands], h_nn.diagonal()) dH_p = dH_asp[a][kpt.s] # Get atomic density and Hamiltonian matrices D_p = self.density.D_asp[a][kpt.s] D_ii = unpack2(D_p) ni = len(D_ii) # Add atomic corrections to the valence-valence exchange energy # -- # > D C D # -- ii iiii ii for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] p12 = packed_index(i1, i2, ni) if Htpsit_nG is not None: dH_p[p12] -= 2 * hybrid / deg * A / ((i1 != i2) + 1) ekin += 2 * hybrid / deg * D_ii[i1, i2] * A exx -= hybrid / deg * D_ii[i1, i2] * A # Add valence-core exchange energy # -- # > X D # -- ii ii if setup.X_p is not None: exx -= hybrid * np.dot(D_p, setup.X_p) if Htpsit_nG is not None: dH_p -= hybrid * setup.X_p ekin += hybrid * np.dot(D_p, setup.X_p) # Add core-core exchange energy if kpt.s == 0: exx += hybrid * setup.ExxC self.exx_s[kpt.s] = self.gd.comm.sum(exx) self.ekin_s[kpt.s] = self.gd.comm.sum(ekin) def correct_hamiltonian_matrix(self, kpt, H_nn): if not hasattr(kpt, 'vxx_ani'): return if self.gd.comm.rank > 0: H_nn[:] = 0.0 nocc = self.nocc_s[kpt.s] nbands = len(kpt.vt_nG) for a, P_ni in kpt.P_ani.items(): H_nn[:nbands, :nbands] += symmetrize( np.inner(P_ni[:nbands], kpt.vxx_ani[a])) self.gd.comm.sum(H_nn) H_nn[:nocc, nocc:] = 0.0 H_nn[nocc:, :nocc] = 0.0 def calculate_pair_density(self, n1, n2, psit_nG, P_ani): Q_aL = {} for a, P_ni in P_ani.items(): P1_i = P_ni[n1] P2_i = P_ni[n2] D_ii = np.outer(P1_i, P2_i.conj()).real D_p = pack(D_ii) Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL) nt_G = psit_nG[n1] * psit_nG[n2] if self.finegd is self.gd: nt_g = nt_G else: nt_g = self.finegd.empty() self.interpolator.apply(nt_G, nt_g) rhot_g = nt_g.copy() self.ghat.add(rhot_g, Q_aL) return nt_G, rhot_g def add_correction(self, kpt, psit_xG, Htpsit_xG, P_axi, c_axi, n_x, calculate_change=False): if kpt.f_n is None: return nocc = self.nocc_s[kpt.s] if calculate_change: for x, n in enumerate(n_x): if n < nocc: Htpsit_xG[x] += kpt.vt_nG[n] * psit_xG[x] for a, P_xi in P_axi.items(): c_axi[a][x] += np.dot(kpt.vxx_anii[a][n], P_xi[x]) else: for a, c_xi in c_axi.items(): c_xi[:nocc] += kpt.vxx_ani[a][:nocc] def rotate(self, kpt, U_nn): if kpt.f_n is None: return nocc = self.nocc_s[kpt.s] if len(kpt.vt_nG) == nocc: U_nn = U_nn[:nocc, :nocc] gemm(1.0, kpt.vt_nG.copy(), U_nn, 0.0, kpt.vt_nG) for v_ni in kpt.vxx_ani.values(): gemm(1.0, v_ni.copy(), U_nn, 0.0, v_ni) for v_nii in kpt.vxx_anii.values(): gemm(1.0, v_nii.copy(), U_nn, 0.0, v_nii)
Nc = (N, N, N) # Number of grid points along each axis gd = GridDescriptor(Nc, (a, a, a), 0) # Grid-descriptor object solver = PoissonSolver(nn=3, use_charge_center=True) solver.set_grid_descriptor(gd) solver.initialize() gauss = Gaussian(gd, a=inv_width, center=center_of_charge) test_poisson = Gaussian(gd, a=inv_width, center=center_of_charge) # /-------------------------------------------------\ # | Check if Gaussian potentials are made correctly | # \-------------------------------------------------/ # Array for storing the potential pot = gd.zeros(dtype=float, global_array=False) solver.load_gauss() vg = test_poisson.get_gauss_pot(0) # Get analytic functions ng = gauss.get_gauss(0) # vg = solver.phi_gauss # Solve potential numerically niter = solver.solve(pot, ng, charge=1.0, zero_initial_phi=False) # Determine residual # residual = norm(pot - vg) residual = gd.integrate((pot - vg)**2)**0.5 print('residual %s' % ( residual)) assert residual < 1e-5 # Better than 5.x # mpirun -np 2 python gauss_func.py --gpaw-parallel --gpaw-debug
class OmegaMatrix: """ Omega matrix in Casidas linear response formalism Parameters - calculator: the calculator object the ground state calculation - kss: the Kohn-Sham singles object - xc: the exchange correlation approx. to use - derivativeLevel: which level i of d^i Exc/dn^i to use - numscale: numeric epsilon for derivativeLevel=0,1 - filehandle: the oject can be read from a filehandle - txt: output stream or file name - finegrid: level of fine grid to use. 0: nothing, 1 for poisson only, 2 everything on the fine grid """ def __init__(self, calculator=None, kss=None, xc=None, derivativeLevel=None, numscale=0.001, filehandle=None, txt=None, finegrid=2, eh_comm=None, ): if not txt and calculator: txt = calculator.txt self.txt, firsttime = initialize_text_stream(txt, mpi.rank) if eh_comm == None: eh_comm = mpi.serial_comm self.eh_comm = eh_comm if filehandle is not None: self.kss = kss self.read(fh=filehandle) return None self.fullkss = kss self.finegrid = finegrid if calculator is None: return self.paw = calculator wfs = self.paw.wfs # handle different grid possibilities self.restrict = None self.poisson = PoissonSolver(nn=self.paw.hamiltonian.poisson.nn) if finegrid: self.poisson.set_grid_descriptor(self.paw.density.finegd) self.poisson.initialize() self.gd = self.paw.density.finegd if finegrid == 1: self.gd = wfs.gd else: self.poisson.set_grid_descriptor(wfs.gd) self.poisson.initialize() self.gd = wfs.gd self.restrict = Transformer(self.paw.density.finegd, wfs.gd, self.paw.input_parameters.stencils[1] ).apply if xc == 'RPA': xc = None # enable RPA as keyword if xc is not None: self.xc = XC(xc) self.xc.initialize(self.paw.density, self.paw.hamiltonian, wfs, self.paw.occupations) # check derivativeLevel if derivativeLevel is None: derivativeLevel= \ self.xc.get_functional().get_max_derivative_level() self.derivativeLevel = derivativeLevel # change the setup xc functional if needed # the ground state calculation may have used another xc if kss.npspins > kss.nvspins: spin_increased = True else: spin_increased = False else: self.xc = None self.numscale = numscale self.singletsinglet = False if kss.nvspins<2 and kss.npspins<2: # this will be a singlet to singlet calculation only self.singletsinglet=True nij = len(kss) self.Om = np.zeros((nij,nij)) self.get_full() def get_full(self): self.paw.timer.start('Omega RPA') self.get_rpa() self.paw.timer.stop() if self.xc is not None: self.paw.timer.start('Omega XC') self.get_xc() self.paw.timer.stop() self.eh_comm.sum(self.Om) self.full = self.Om def get_xc(self): """Add xc part of the coupling matrix""" # shorthands paw = self.paw wfs = paw.wfs gd = paw.density.finegd comm = gd.comm eh_comm = self.eh_comm fg = self.finegrid is 2 kss = self.fullkss nij = len(kss) Om_xc = self.Om # initialize densities # nt_sg is the smooth density on the fine grid with spin index if kss.nvspins==2: # spin polarised ground state calc. nt_sg = paw.density.nt_sg else: # spin unpolarised ground state calc. if kss.npspins==2: # construct spin polarised densities nt_sg = np.array([.5*paw.density.nt_sg[0], .5*paw.density.nt_sg[0]]) else: nt_sg = paw.density.nt_sg # check if D_sp have been changed before D_asp = self.paw.density.D_asp for a, D_sp in D_asp.items(): if len(D_sp) != kss.npspins: if len(D_sp) == 1: D_asp[a] = np.array([0.5 * D_sp[0], 0.5 * D_sp[0]]) else: D_asp[a] = np.array([D_sp[0] + D_sp[1]]) # restrict the density if needed if fg: nt_s = nt_sg else: nt_s = self.gd.zeros(nt_sg.shape[0]) for s in range(nt_sg.shape[0]): self.restrict(nt_sg[s], nt_s[s]) gd = paw.density.gd # initialize vxc or fxc if self.derivativeLevel==0: raise NotImplementedError if kss.npspins==2: v_g=nt_sg[0].copy() else: v_g=nt_sg.copy() elif self.derivativeLevel==1: pass elif self.derivativeLevel==2: fxc_sg = np.zeros(nt_sg.shape) self.xc.calculate_fxc(gd, nt_sg, fxc_sg) else: raise ValueError('derivativeLevel can only be 0,1,2') ## self.paw.my_nuclei = [] ns=self.numscale xc=self.xc print >> self.txt, 'XC',nij,'transitions' for ij in range(eh_comm.rank, nij, eh_comm.size): print >> self.txt,'XC kss['+'%d'%ij+']' timer = Timer() timer.start('init') timer2 = Timer() if self.derivativeLevel >= 1: # vxc is available # We use the numerical two point formula for calculating # the integral over fxc*n_ij. The results are # vvt_s smooth integral # nucleus.I_sp atom based correction matrices (pack2) # stored on each nucleus timer2.start('init v grids') vp_s=np.zeros(nt_s.shape,nt_s.dtype.char) vm_s=np.zeros(nt_s.shape,nt_s.dtype.char) if kss.npspins == 2: # spin polarised nv_s = nt_s.copy() nv_s[kss[ij].pspin] += ns * kss[ij].get(fg) xc.calculate(gd, nv_s, vp_s) nv_s = nt_s.copy() nv_s[kss[ij].pspin] -= ns * kss[ij].get(fg) xc.calculate(gd, nv_s, vm_s) else: # spin unpolarised nv = nt_s + ns * kss[ij].get(fg) xc.calculate(gd, nv, vp_s) nv = nt_s - ns * kss[ij].get(fg) xc.calculate(gd, nv, vm_s) vvt_s = (0.5 / ns) * (vp_s - vm_s) timer2.stop() # initialize the correction matrices timer2.start('init v corrections') I_asp = {} for a, P_ni in wfs.kpt_u[kss[ij].spin].P_ani.items(): # create the modified density matrix Pi_i = P_ni[kss[ij].i] Pj_i = P_ni[kss[ij].j] P_ii = np.outer(Pi_i, Pj_i) # we need the symmetric form, hence we can pack P_p = pack(P_ii) D_sp = self.paw.density.D_asp[a].copy() D_sp[kss[ij].pspin] -= ns * P_p setup = wfs.setups[a] I_sp = np.zeros_like(D_sp) self.xc.calculate_paw_correction(setup, D_sp, I_sp) I_sp *= -1.0 D_sp = self.paw.density.D_asp[a].copy() D_sp[kss[ij].pspin] += ns * P_p self.xc.calculate_paw_correction(setup, D_sp, I_sp) I_sp /= 2.0 * ns I_asp[a] = I_sp timer2.stop() timer.stop() t0 = timer.get_time('init') timer.start(ij) for kq in range(ij,nij): weight = self.weight_Kijkq(ij, kq) if self.derivativeLevel == 0: # only Exc is available if kss.npspins==2: # spin polarised nv_g = nt_sg.copy() nv_g[kss[ij].pspin] += kss[ij].get(fg) nv_g[kss[kq].pspin] += kss[kq].get(fg) Excpp = xc.get_energy_and_potential( nv_g[0], v_g, nv_g[1], v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] += kss[ij].get(fg) nv_g[kss[kq].pspin] -= kss[kq].get(fg) Excpm = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] -=\ kss[ij].get(fg) nv_g[kss[kq].pspin] +=\ kss[kq].get(fg) Excmp = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] -= \ kss[ij].get(fg) nv_g[kss[kq].pspin] -=\ kss[kq].get(fg) Excpp = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) else: # spin unpolarised nv_g=nt_sg + ns*kss[ij].get(fg)\ + ns*kss[kq].get(fg) Excpp = xc.get_energy_and_potential(nv_g,v_g) nv_g=nt_sg + ns*kss[ij].get(fg)\ - ns*kss[kq].get(fg) Excpm = xc.get_energy_and_potential(nv_g,v_g) nv_g=nt_sg - ns*kss[ij].get(fg)\ + ns*kss[kq].get(fg) Excmp = xc.get_energy_and_potential(nv_g,v_g) nv_g=nt_sg - ns*kss[ij].get(fg)\ - ns*kss[kq].get(fg) Excmm = xc.get_energy_and_potential(nv_g,v_g) Om_xc[ij,kq] += weight *\ 0.25*(Excpp-Excmp-Excpm+Excmm)/(ns*ns) elif self.derivativeLevel == 1: # vxc is available timer2.start('integrate') Om_xc[ij,kq] += weight*\ self.gd.integrate(kss[kq].get(fg)* vvt_s[kss[kq].pspin]) timer2.stop() timer2.start('integrate corrections') Exc = 0. for a, P_ni in wfs.kpt_u[kss[kq].spin].P_ani.items(): # create the modified density matrix Pk_i = P_ni[kss[kq].i] Pq_i = P_ni[kss[kq].j] P_ii = np.outer(Pk_i, Pq_i) # we need the symmetric form, hence we can pack # use pack as I_sp used pack2 P_p = pack(P_ii) Exc += np.dot(I_asp[a][kss[kq].pspin], P_p) Om_xc[ij, kq] += weight * self.gd.comm.sum(Exc) timer2.stop() elif self.derivativeLevel == 2: # fxc is available if kss.npspins==2: # spin polarised Om_xc[ij,kq] += weight *\ gd.integrate(kss[ij].get(fg) * kss[kq].get(fg) * fxc_sg[kss[ij].pspin, kss[kq].pspin]) else: # spin unpolarised Om_xc[ij,kq] += weight *\ gd.integrate(kss[ij].get(fg) * kss[kq].get(fg) * fxc_sg) # XXX still numeric derivatives for local terms timer2.start('integrate corrections') Exc = 0. for a, P_ni in wfs.kpt_u[kss[kq].spin].P_ani.items(): # create the modified density matrix Pk_i = P_ni[kss[kq].i] Pq_i = P_ni[kss[kq].j] P_ii = np.outer(Pk_i, Pq_i) # we need the symmetric form, hence we can pack # use pack as I_sp used pack2 P_p = pack(P_ii) Exc += np.dot(I_asp[a][kss[kq].pspin], P_p) Om_xc[ij, kq] += weight * self.gd.comm.sum(Exc) timer2.stop() if ij != kq: Om_xc[kq,ij] = Om_xc[ij,kq] timer.stop() ## timer2.write() if ij < (nij-1): print >> self.txt,'XC estimated time left',\ self.time_left(timer, t0, ij, nij) def Coulomb_integral_kss(self, kss_ij, kss_kq, phit, rhot, timer=None): # smooth part if timer: timer.start('integrate') I = self.gd.integrate(rhot * phit) if timer: timer.stop() timer.start('integrate corrections 2') wfs = self.paw.wfs Pij_ani = wfs.kpt_u[kss_ij.spin].P_ani Pkq_ani = wfs.kpt_u[kss_kq.spin].P_ani # Add atomic corrections Ia = 0.0 for a, Pij_ni in Pij_ani.items(): Pi_i = Pij_ni[kss_ij.i] Pj_i = Pij_ni[kss_ij.j] Dij_ii = np.outer(Pi_i, Pj_i) Dij_p = pack(Dij_ii) Pk_i = Pkq_ani[a][kss_kq.i] Pq_i = Pkq_ani[a][kss_kq.j] Dkq_ii = np.outer(Pk_i, Pq_i) Dkq_p = pack(Dkq_ii) C_pp = wfs.setups[a].M_pp # ---- # 2 > P P C P P # ---- ip jr prst ks qt # prst Ia += 2.0*np.dot(Dkq_p, np.dot(C_pp, Dij_p)) I += self.gd.comm.sum(Ia) if timer: timer.stop() return I def get_rpa(self): """calculate RPA part of the omega matrix""" # shorthands kss=self.fullkss finegrid=self.finegrid wfs = self.paw.wfs eh_comm = self.eh_comm # calculate omega matrix nij = len(kss) print >> self.txt,'RPA',nij,'transitions' Om = self.Om for ij in range(eh_comm.rank, nij, eh_comm.size): print >> self.txt,'RPA kss['+'%d'%ij+']=', kss[ij] timer = Timer() timer.start('init') timer2 = Timer() # smooth density including compensation charges timer2.start('with_compensation_charges 0') rhot_p = kss[ij].with_compensation_charges( finegrid is not 0) timer2.stop() # integrate with 1/|r_1-r_2| timer2.start('poisson') phit_p = np.zeros(rhot_p.shape, rhot_p.dtype.char) self.poisson.solve(phit_p, rhot_p, charge=None) timer2.stop() timer.stop() t0 = timer.get_time('init') timer.start(ij) if finegrid == 1: rhot = kss[ij].with_compensation_charges() phit = self.gd.zeros() ## print "shapes 0=",phit.shape,rhot.shape self.restrict(phit_p,phit) else: phit = phit_p rhot = rhot_p for kq in range(ij,nij): if kq != ij: # smooth density including compensation charges timer2.start('kq with_compensation_charges') rhot = kss[kq].with_compensation_charges( finegrid is 2) timer2.stop() pre = 2 * sqrt(kss[ij].get_energy() * kss[kq].get_energy() * kss[ij].get_weight() * kss[kq].get_weight() ) I = self.Coulomb_integral_kss(kss[ij], kss[kq], rhot, phit, timer2) Om[ij,kq] = pre * I if ij == kq: Om[ij,kq] += kss[ij].get_energy()**2 else: Om[kq,ij]=Om[ij,kq] timer.stop() ## timer2.write() if ij < (nij-1): t = timer.get_time(ij) # time for nij-ij calculations t = .5*t*(nij-ij) # estimated time for n*(n+1)/2, n=nij-(ij+1) print >> self.txt,'RPA estimated time left',\ self.timestring(t0*(nij-ij-1)+t) def singlets_triplets(self): """Split yourself into singlet and triplet transitions""" assert(self.fullkss.npspins == 2) assert(self.fullkss.nvspins == 1) # strip kss from down spins skss = KSSingles() tkss = KSSingles() map = [] for ij, ks in enumerate(self.fullkss): if ks.pspin == ks.spin: skss.append((ks + ks) / sqrt(2)) tkss.append((ks - ks) / sqrt(2)) map.append(ij) nkss = len(skss) # define the singlet and the triplet omega-matrixes sOm = OmegaMatrix(kss=skss) sOm.full = np.empty((nkss, nkss)) tOm = OmegaMatrix(kss=tkss) tOm.full = np.empty((nkss, nkss)) for ij in range(nkss): for kl in range(nkss): sOm.full[ij, kl] = (self.full[map[ij], map[kl]] + self.full[map[ij], nkss + map[kl]]) tOm.full[ij, kl] = (self.full[map[ij], map[kl]] - self.full[map[ij], nkss + map[kl]]) return sOm, tOm def timestring(self, t): ti = int(t + 0.5) td = ti // 86400 st = '' if td > 0: st += '%d' % td + 'd' ti -= td * 86400 th = ti // 3600 if th > 0: st += '%d' % th + 'h' ti -= th * 3600 tm = ti // 60 if tm > 0: st += '%d' % tm + 'm' ti -= tm * 60 st += '%d' % ti + 's' return st def time_left(self, timer, t0, ij, nij): t = timer.get_time(ij) # time for nij-ij calculations t = .5 * t * (nij - ij) # estimated time for n*(n+1)/2, n=nij-(ij+1) return self.timestring(t0 * (nij - ij - 1) + t) def get_map(self, istart=None, jend=None, energy_range=None): """Return the reduction map for the given requirements""" self.istart = istart self.jend = jend if istart is None and jend is None and energy_range is None: return None, self.fullkss # reduce the matrix print >> self.txt,'# diagonalize: %d transitions original'\ % len(self.fullkss) if energy_range is None: if istart is None: istart = self.kss.istart if self.fullkss.istart > istart: raise RuntimeError('istart=%d has to be >= %d' % (istart, self.kss.istart)) if jend is None: jend = self.kss.jend if self.fullkss.jend < jend: raise RuntimeError('jend=%d has to be <= %d' % (jend, self.kss.jend)) else: try: emin, emax = energy_range except: emax = energy_range emin = 0. emin /= Hartree emax /= Hartree map= [] kss = KSSingles() for ij, k in zip(range(len(self.fullkss)), self.fullkss): if energy_range is None: if k.i >= istart and k.j <= jend: kss.append(k) map.append(ij) else: if k.energy >= emin and k.energy < emax: kss.append(k) map.append(ij) kss.update() print >> self.txt, '# diagonalize: %d transitions now' % len(kss) return map, kss def diagonalize(self, istart=None, jend=None, energy_range=None, TDA=False): """Evaluate Eigenvectors and Eigenvalues:""" if TDA: raise NotImplementedError map, kss = self.get_map(istart, jend, energy_range) nij = len(kss) if map is None: evec = self.full.copy() else: evec = np.zeros((nij,nij)) for ij in range(nij): for kq in range(nij): evec[ij,kq] = self.full[map[ij],map[kq]] assert(len(evec) > 0) self.eigenvectors = evec self.eigenvalues = np.zeros((len(kss))) self.kss = kss diagonalize(self.eigenvectors, self.eigenvalues) def Kss(self, kss=None): """Set and get own Kohn-Sham singles""" if kss is not None: self.fullkss = kss if(hasattr(self,'fullkss')): return self.fullkss else: return None def read(self, filename=None, fh=None): """Read myself from a file""" if fh is None: f = open(filename, 'r') else: f = fh f.readline() nij = int(f.readline()) full = np.zeros((nij,nij)) for ij in range(nij): l = f.readline().split() for kq in range(ij,nij): full[ij,kq] = float(l[kq-ij]) full[kq,ij] = full[ij,kq] self.full = full if fh is None: f.close() def write(self, filename=None, fh=None): """Write current state to a file.""" if mpi.rank == mpi.MASTER: if fh is None: f = open(filename, 'w') else: f = fh f.write('# OmegaMatrix\n') nij = len(self.fullkss) f.write('%d\n' % nij) for ij in range(nij): for kq in range(ij,nij): f.write(' %g' % self.full[ij,kq]) f.write('\n') if fh is None: f.close() def weight_Kijkq(self, ij, kq): """weight for the coupling matrix terms""" kss = self.fullkss return 2.*sqrt( kss[ij].get_energy() * kss[kq].get_energy() * kss[ij].get_weight() * kss[kq].get_weight() ) def __str__(self): str='<OmegaMatrix> ' if hasattr(self,'eigenvalues'): str += 'dimension '+ ('%d'%len(self.eigenvalues)) str += "\neigenvalues: " for ev in self.eigenvalues: str += ' ' + ('%f'%(sqrt(ev) * Hartree)) return str
class OmegaMatrix: """ Omega matrix in Casidas linear response formalism Parameters - calculator: the calculator object the ground state calculation - kss: the Kohn-Sham singles object - xc: the exchange correlation approx. to use - derivativeLevel: which level i of d^i Exc/dn^i to use - numscale: numeric epsilon for derivativeLevel=0,1 - filehandle: the oject can be read from a filehandle - txt: output stream or file name - finegrid: level of fine grid to use. 0: nothing, 1 for poisson only, 2 everything on the fine grid """ def __init__( self, calculator=None, kss=None, xc=None, derivativeLevel=None, numscale=0.001, filehandle=None, txt=None, finegrid=2, eh_comm=None, ): if not txt and calculator: txt = calculator.txt self.txt, firsttime = initialize_text_stream(txt, mpi.rank) if eh_comm == None: eh_comm = mpi.serial_comm self.eh_comm = eh_comm if filehandle is not None: self.kss = kss self.read(fh=filehandle) return None self.fullkss = kss self.finegrid = finegrid if calculator is None: return self.paw = calculator wfs = self.paw.wfs # handle different grid possibilities self.restrict = None self.poisson = PoissonSolver(nn=self.paw.hamiltonian.poisson.nn) if finegrid: self.poisson.set_grid_descriptor(self.paw.density.finegd) self.poisson.initialize() self.gd = self.paw.density.finegd if finegrid == 1: self.gd = wfs.gd else: self.poisson.set_grid_descriptor(wfs.gd) self.poisson.initialize() self.gd = wfs.gd self.restrict = Transformer( self.paw.density.finegd, wfs.gd, self.paw.input_parameters.stencils[0]).apply if xc == 'RPA': xc = None # enable RPA as keyword if xc is not None: self.xc = XC(xc) self.xc.initialize(self.paw.density, self.paw.hamiltonian, wfs, self.paw.occupations) # check derivativeLevel if derivativeLevel is None: derivativeLevel= \ self.xc.get_functional().get_max_derivative_level() self.derivativeLevel = derivativeLevel # change the setup xc functional if needed # the ground state calculation may have used another xc if kss.npspins > kss.nvspins: spin_increased = True else: spin_increased = False else: self.xc = None self.numscale = numscale self.singletsinglet = False if kss.nvspins < 2 and kss.npspins < 2: # this will be a singlet to singlet calculation only self.singletsinglet = True nij = len(kss) self.Om = np.zeros((nij, nij)) self.get_full() def get_full(self): self.paw.timer.start('Omega RPA') self.get_rpa() self.paw.timer.stop() if self.xc is not None: self.paw.timer.start('Omega XC') self.get_xc() self.paw.timer.stop() self.eh_comm.sum(self.Om) self.full = self.Om def get_xc(self): """Add xc part of the coupling matrix""" # shorthands paw = self.paw wfs = paw.wfs gd = paw.density.finegd comm = gd.comm eh_comm = self.eh_comm fg = self.finegrid is 2 kss = self.fullkss nij = len(kss) Om_xc = self.Om # initialize densities # nt_sg is the smooth density on the fine grid with spin index if kss.nvspins == 2: # spin polarised ground state calc. nt_sg = paw.density.nt_sg else: # spin unpolarised ground state calc. if kss.npspins == 2: # construct spin polarised densities nt_sg = np.array( [.5 * paw.density.nt_sg[0], .5 * paw.density.nt_sg[0]]) else: nt_sg = paw.density.nt_sg # check if D_sp have been changed before D_asp = self.paw.density.D_asp for a, D_sp in D_asp.items(): if len(D_sp) != kss.npspins: if len(D_sp) == 1: D_asp[a] = np.array([0.5 * D_sp[0], 0.5 * D_sp[0]]) else: D_asp[a] = np.array([D_sp[0] + D_sp[1]]) # restrict the density if needed if fg: nt_s = nt_sg else: nt_s = self.gd.zeros(nt_sg.shape[0]) for s in range(nt_sg.shape[0]): self.restrict(nt_sg[s], nt_s[s]) gd = paw.density.gd # initialize vxc or fxc if self.derivativeLevel == 0: raise NotImplementedError if kss.npspins == 2: v_g = nt_sg[0].copy() else: v_g = nt_sg.copy() elif self.derivativeLevel == 1: pass elif self.derivativeLevel == 2: raise NotImplementedError if kss.npspins == 2: fxc = d2Excdnsdnt(nt_sg[0], nt_sg[1]) else: fxc = d2Excdn2(nt_sg) else: raise ValueError('derivativeLevel can only be 0,1,2') ## self.paw.my_nuclei = [] ns = self.numscale xc = self.xc print >> self.txt, 'XC', nij, 'transitions' for ij in range(eh_comm.rank, nij, eh_comm.size): print >> self.txt, 'XC kss[' + '%d' % ij + ']' timer = Timer() timer.start('init') timer2 = Timer() if self.derivativeLevel == 1: # vxc is available # We use the numerical two point formula for calculating # the integral over fxc*n_ij. The results are # vvt_s smooth integral # nucleus.I_sp atom based correction matrices (pack2) # stored on each nucleus timer2.start('init v grids') vp_s = np.zeros(nt_s.shape, nt_s.dtype.char) vm_s = np.zeros(nt_s.shape, nt_s.dtype.char) if kss.npspins == 2: # spin polarised nv_s = nt_s.copy() nv_s[kss[ij].pspin] += ns * kss[ij].get(fg) xc.calculate(gd, nv_s, vp_s) nv_s = nt_s.copy() nv_s[kss[ij].pspin] -= ns * kss[ij].get(fg) xc.calculate(gd, nv_s, vm_s) else: # spin unpolarised nv = nt_s + ns * kss[ij].get(fg) xc.calculate(gd, nv, vp_s) nv = nt_s - ns * kss[ij].get(fg) xc.calculate(gd, nv, vm_s) vvt_s = (0.5 / ns) * (vp_s - vm_s) timer2.stop() # initialize the correction matrices timer2.start('init v corrections') I_asp = {} for a, P_ni in wfs.kpt_u[kss[ij].spin].P_ani.items(): # create the modified density matrix Pi_i = P_ni[kss[ij].i] Pj_i = P_ni[kss[ij].j] P_ii = np.outer(Pi_i, Pj_i) # we need the symmetric form, hence we can pack P_p = pack(P_ii) D_sp = self.paw.density.D_asp[a].copy() D_sp[kss[ij].pspin] -= ns * P_p setup = wfs.setups[a] I_sp = np.zeros_like(D_sp) setup.xc_correction.calculate(self.xc, D_sp, I_sp) I_sp *= -1.0 D_sp = self.paw.density.D_asp[a].copy() D_sp[kss[ij].pspin] += ns * P_p setup.xc_correction.calculate(self.xc, D_sp, I_sp) I_sp /= 2.0 * ns I_asp[a] = I_sp timer2.stop() timer.stop() t0 = timer.get_time('init') timer.start(ij) for kq in range(ij, nij): weight = self.weight_Kijkq(ij, kq) if self.derivativeLevel == 0: # only Exc is available if kss.npspins == 2: # spin polarised nv_g = nt_sg.copy() nv_g[kss[ij].pspin] +=\ kss[ij].get(fg) nv_g[kss[kq].pspin] +=\ kss[kq].get(fg) Excpp = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] +=\ kss[ij].get(fg) nv_g[kss[kq].pspin] -= \ kss[kq].get(fg) Excpm = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] -=\ kss[ij].get(fg) nv_g[kss[kq].pspin] +=\ kss[kq].get(fg) Excmp = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] -= \ kss[ij].get(fg) nv_g[kss[kq].pspin] -=\ kss[kq].get(fg) Excpp = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) else: # spin unpolarised nv_g=nt_sg + ns*kss[ij].get(fg)\ + ns*kss[kq].get(fg) Excpp = xc.get_energy_and_potential(nv_g, v_g) nv_g=nt_sg + ns*kss[ij].get(fg)\ - ns*kss[kq].get(fg) Excpm = xc.get_energy_and_potential(nv_g, v_g) nv_g=nt_sg - ns*kss[ij].get(fg)\ + ns*kss[kq].get(fg) Excmp = xc.get_energy_and_potential(nv_g, v_g) nv_g=nt_sg - ns*kss[ij].get(fg)\ - ns*kss[kq].get(fg) Excmm = xc.get_energy_and_potential(nv_g, v_g) Om_xc[ij,kq] += weight *\ 0.25*(Excpp-Excmp-Excpm+Excmm)/(ns*ns) elif self.derivativeLevel == 1: # vxc is available timer2.start('integrate') Om_xc[ij,kq] += weight*\ self.gd.integrate(kss[kq].get(fg)* vvt_s[kss[kq].pspin]) timer2.stop() timer2.start('integrate corrections') Exc = 0. for a, P_ni in wfs.kpt_u[kss[kq].spin].P_ani.items(): # create the modified density matrix Pk_i = P_ni[kss[kq].i] Pq_i = P_ni[kss[kq].j] P_ii = np.outer(Pk_i, Pq_i) # we need the symmetric form, hence we can pack # use pack as I_sp used pack2 P_p = pack(P_ii) Exc += np.dot(I_asp[a][kss[kq].pspin], P_p) Om_xc[ij, kq] += weight * self.gd.comm.sum(Exc) timer2.stop() elif self.derivativeLevel == 2: # fxc is available if kss.npspins == 2: # spin polarised Om_xc[ij,kq] += weight *\ gd.integrate(kss[ij].get(fg)* kss[kq].get(fg)* fxc[kss[ij].pspin,kss[kq].pspin]) else: # spin unpolarised Om_xc[ij,kq] += weight *\ gd.integrate(kss[ij].get(fg)* kss[kq].get(fg)* fxc) if ij != kq: Om_xc[kq, ij] = Om_xc[ij, kq] timer.stop() ## timer2.write() if ij < (nij - 1): t = timer.get_time(ij) # time for nij-ij calculations t = .5 * t * (nij - ij ) # estimated time for n*(n+1)/2, n=nij-(ij+1) print >> self.txt,'XC estimated time left',\ self.timestring(t0*(nij-ij-1)+t) def get_rpa(self): """calculate RPA part of the omega matrix""" # shorthands kss = self.fullkss finegrid = self.finegrid wfs = self.paw.wfs eh_comm = self.eh_comm # calculate omega matrix nij = len(kss) print >> self.txt, 'RPA', nij, 'transitions' Om = self.Om for ij in range(eh_comm.rank, nij, eh_comm.size): print >> self.txt, 'RPA kss[' + '%d' % ij + ']=', kss[ij] timer = Timer() timer.start('init') timer2 = Timer() # smooth density including compensation charges timer2.start('with_compensation_charges 0') rhot_p = kss[ij].with_compensation_charges(finegrid is not 0) timer2.stop() # integrate with 1/|r_1-r_2| timer2.start('poisson') phit_p = np.zeros(rhot_p.shape, rhot_p.dtype.char) self.poisson.solve(phit_p, rhot_p, charge=None) timer2.stop() timer.stop() t0 = timer.get_time('init') timer.start(ij) if finegrid == 1: rhot = kss[ij].with_compensation_charges() phit = self.gd.zeros() ## print "shapes 0=",phit.shape,rhot.shape self.restrict(phit_p, phit) else: phit = phit_p rhot = rhot_p for kq in range(ij, nij): if kq != ij: # smooth density including compensation charges timer2.start('kq with_compensation_charges') rhot = kss[kq].with_compensation_charges(finegrid is 2) timer2.stop() timer2.start('integrate') pre = 2. * sqrt(kss[ij].get_energy() * kss[kq].get_energy() * kss[ij].get_weight() * kss[kq].get_weight()) Om[ij, kq] = pre * self.gd.integrate(rhot * phit) ## print "int=",Om[ij,kq] timer2.stop() # Add atomic corrections timer2.start('integrate corrections 2') Ia = 0. for a, P_ni in wfs.kpt_u[kss[ij].spin].P_ani.items(): Pi_i = P_ni[kss[ij].i] Pj_i = P_ni[kss[ij].j] Dij_ii = np.outer(Pi_i, Pj_i) Dij_p = pack(Dij_ii) Pkq_ni = wfs.kpt_u[kss[kq].spin].P_ani[a] Pk_i = Pkq_ni[kss[kq].i] Pq_i = Pkq_ni[kss[kq].j] Dkq_ii = np.outer(Pk_i, Pq_i) Dkq_p = pack(Dkq_ii) C_pp = wfs.setups[a].M_pp # ---- # 2 > P P C P P # ---- ip jr prst ks qt # prst Ia += 2.0 * np.dot(Dkq_p, np.dot(C_pp, Dij_p)) timer2.stop() Om[ij, kq] += pre * self.gd.comm.sum(Ia) if ij == kq: Om[ij, kq] += kss[ij].get_energy()**2 else: Om[kq, ij] = Om[ij, kq] timer.stop() ## timer2.write() if ij < (nij - 1): t = timer.get_time(ij) # time for nij-ij calculations t = .5 * t * (nij - ij ) # estimated time for n*(n+1)/2, n=nij-(ij+1) print >> self.txt,'RPA estimated time left',\ self.timestring(t0*(nij-ij-1)+t) def singlets_triplets(self): """Split yourself into singlet and triplet transitions""" assert (self.fullkss.npspins == 2) assert (self.fullkss.nvspins == 1) # strip kss from down spins skss = KSSingles() tkss = KSSingles() map = [] for ij, ks in enumerate(self.fullkss): if ks.pspin == ks.spin: skss.append((ks + ks) / sqrt(2)) tkss.append((ks - ks) / sqrt(2)) map.append(ij) nkss = len(skss) # define the singlet and the triplet omega-matrixes sOm = OmegaMatrix(kss=skss) sOm.full = np.empty((nkss, nkss)) tOm = OmegaMatrix(kss=tkss) tOm.full = np.empty((nkss, nkss)) for ij in range(nkss): for kl in range(nkss): sOm.full[ij, kl] = (self.full[map[ij], map[kl]] + self.full[map[ij], nkss + map[kl]]) tOm.full[ij, kl] = (self.full[map[ij], map[kl]] - self.full[map[ij], nkss + map[kl]]) return sOm, tOm def timestring(self, t): ti = int(t + 0.5) td = ti // 86400 st = '' if td > 0: st += '%d' % td + 'd' ti -= td * 86400 th = ti // 3600 if th > 0: st += '%d' % th + 'h' ti -= th * 3600 tm = ti // 60 if tm > 0: st += '%d' % tm + 'm' ti -= tm * 60 st += '%d' % ti + 's' return st def get_map(self, istart=None, jend=None, energy_range=None): """Return the reduction map for the given requirements""" self.istart = istart self.jend = jend if istart is None and jend is None and energy_range is None: return None, self.fullkss # reduce the matrix print >> self.txt,'# diagonalize: %d transitions original'\ % len(self.fullkss) if energy_range is None: if istart is None: istart = self.kss.istart if self.fullkss.istart > istart: raise RuntimeError('istart=%d has to be >= %d' % (istart, self.kss.istart)) if jend is None: jend = self.kss.jend if self.fullkss.jend < jend: raise RuntimeError('jend=%d has to be <= %d' % (jend, self.kss.jend)) else: try: emin, emax = energy_range except: emax = energy_range emin = 0. emin /= Hartree emax /= Hartree map = [] kss = KSSingles() for ij, k in zip(range(len(self.fullkss)), self.fullkss): if energy_range is None: if k.i >= istart and k.j <= jend: kss.append(k) map.append(ij) else: if k.energy >= emin and k.energy < emax: kss.append(k) map.append(ij) kss.update() print >> self.txt, '# diagonalize: %d transitions now' % len(kss) return map, kss def diagonalize(self, istart=None, jend=None, energy_range=None): """Evaluate Eigenvectors and Eigenvalues:""" map, kss = self.get_map(istart, jend, energy_range) nij = len(kss) if map is None: evec = self.full.copy() else: evec = np.zeros((nij, nij)) for ij in range(nij): for kq in range(nij): evec[ij, kq] = self.full[map[ij], map[kq]] self.eigenvectors = evec self.eigenvalues = np.zeros((len(kss))) self.kss = kss diagonalize(self.eigenvectors, self.eigenvalues) def Kss(self, kss=None): """Set and get own Kohn-Sham singles""" if kss is not None: self.fullkss = kss if (hasattr(self, 'fullkss')): return self.fullkss else: return None def read(self, filename=None, fh=None): """Read myself from a file""" if fh is None: f = open(filename, 'r') else: f = fh f.readline() nij = int(f.readline()) full = np.zeros((nij, nij)) for ij in range(nij): l = f.readline().split() for kq in range(ij, nij): full[ij, kq] = float(l[kq - ij]) full[kq, ij] = full[ij, kq] self.full = full if fh is None: f.close() def write(self, filename=None, fh=None): """Write current state to a file.""" if mpi.rank == mpi.MASTER: if fh is None: f = open(filename, 'w') else: f = fh f.write('# OmegaMatrix\n') nij = len(self.fullkss) f.write('%d\n' % nij) for ij in range(nij): for kq in range(ij, nij): f.write(' %g' % self.full[ij, kq]) f.write('\n') if fh is None: f.close() def weight_Kijkq(self, ij, kq): """weight for the coupling matrix terms""" kss = self.fullkss return 2. * sqrt(kss[ij].get_energy() * kss[kq].get_energy() * kss[ij].get_weight() * kss[kq].get_weight()) def __str__(self): str = '<OmegaMatrix> ' if hasattr(self, 'eigenvalues'): str += 'dimension ' + ('%d' % len(self.eigenvalues)) str += "\neigenvalues: " for ev in self.eigenvalues: str += ' ' + ('%f' % (sqrt(ev) * Hartree)) return str