Coverage for gpaw/dscf.py: 59%

142 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-07-09 00:21 +0000

1# Copyright (C) 2008 CAMd 

2# Please see the accompanying LICENSE file for further information. 

3 

4"""This module is used in delta self-consistent field (dSCF) calculations 

5 

6dSCF is a simple 'ad hoc' method to estimating excitation energies within 

7DFT. The only difference to ordinary DFT is that one or more electrons(s) 

8are forced to occupy one or more predefined orbitals. The only restriction 

9on these orbitals is that they must be linear combinations of available 

10Kohn-Sham orbitals. 

11 

12""" 

13 

14import sys 

15import numpy as np 

16 

17from ase.parallel import paropen 

18from gpaw.utilities import devnull 

19from ase.units import Ha 

20 

21from gpaw.occupations import FermiDiracCalculator 

22 

23 

24def dscf_calculation(paw, orbitals, atoms): 

25 """Helper function to prepare a calculator for a dSCF calculation 

26 

27 Parameters 

28 ========== 

29 orbitals: list of lists 

30 Orbitals which one wants to occupy. The format is 

31 orbitals = [[1.0,orb1,0],[1.0,orb2,1],...], where 1.0 is the no. 

32 of electrons, orb1 and orb2 are the orbitals (see MolecularOrbitals 

33 below for an example of an orbital class). 0 and 1 represents the 

34 spin (up and down). This number is ignored in a spin-paired 

35 calculation. 

36 

37 Example 

38 ======= 

39 

40 :: 

41 

42 atoms.calc = calc 

43 e_gs = atoms.get_potential_energy() #ground state energy 

44 sigma_star=MolecularOrbitals(calc, molecule=[0,1], 

45 w=[[1.,0.,0.,0.],[-1.,0.,0.,0.]]) 

46 dscf_calculation(calc, [[1.0,sigma_star,1]], atoms) 

47 e_exc = atoms.get_potential_energy() #excitation energy 

48 

49 """ 

50 

51 # If the calculator has not been initialized the occupation object 

52 # is None 

53 if paw.wfs is None: 

54 paw.initialize(atoms) 

55 assert paw.wfs.mode != 'pw' 

56 occ = paw.wfs.occupations 

57 if isinstance(occ, OccupationsDSCF): 

58 occ.orbitals = orbitals 

59 else: 

60 if occ.name == 'zero-width': 

61 occ = FermiDiracCalculator(1e-4, occ.parallel_layout) 

62 new_occ = OccupationsDSCF(occ, paw, orbitals) 

63 paw.wfs.occupations = new_occ 

64 # If the calculator has already converged (for the ground state), 

65 # reset self-consistency and let the density be updated right away 

66 if paw.scf.converged: 

67 paw.scf.niter_fixdensity = 0 

68 paw.scf.reset() 

69 

70 

71class OccupationsDSCF: 

72 """Occupation class. 

73 

74 Corresponds to the ordinary FermiDirac class in occupation.py. Only 

75 difference is that it forces some electrons in the supplied orbitals 

76 in stead of placing all the electrons by a Fermi-Dirac distribution. 

77 """ 

78 

79 def __init__(self, occ, calc, orbitals): 

80 self.occ = occ 

81 self.extrapolate_factor = occ.extrapolate_factor 

82 self.wfs = calc.wfs 

83 self.orbitals = orbitals 

84 self.norbitals = len(self.orbitals) 

85 

86 self.cnoe = 0.0 

87 for orb in self.orbitals: 

88 self.cnoe += orb[0] 

89 

90 def calculate(self, 

91 nelectrons, 

92 eigenvalues, 

93 weights, 

94 fermi_levels_guess, 

95 fix_fermi_level=False): 

96 assert not fix_fermi_level 

97 f_qn, fermi_levels, e_entropy = self.occ.calculate( 

98 nelectrons - self.cnoe, 

99 eigenvalues, 

100 weights, 

101 fermi_levels_guess) 

102 

103 # Get the expansion coefficients c_un for each dscf-orbital 

104 # and incorporate their respective occupations into kpt.ne_o 

105 c_oun = [] 

106 for orb in self.orbitals: 

107 c_oun.append(orb[1].expand([e / Ha for e in fermi_levels], 

108 self.wfs, 

109 self.occ.todict().get('fixmagmom'))) 

110 

111 for u, kpt in enumerate(self.wfs.kpt_u): 

112 kpt.ne_o = np.zeros(self.norbitals, dtype=float) 

113 kpt.c_on = np.zeros((self.norbitals, len(kpt.eps_n)), 

114 dtype=complex) 

115 

116 for o, orb in enumerate(self.orbitals): 

117 # TODO XXX false if orb[0]<0 since abs(c_n)**2>0 

118 # kpt.c_on[o,:] = abs(orb[0])**0.5 * c_oun[o][u] 

119 

120 kpt.ne_o[o] = orb[0] 

121 kpt.c_on[o, :] = c_oun[o][u] 

122 

123 if self.wfs.nspins == 2: 

124 assert orb[2] in range(2), 'Invalid spin index' 

125 

126 if orb[2] == kpt.s: 

127 kpt.ne_o[o] *= kpt.weight 

128 else: 

129 kpt.ne_o[o] = 0.0 

130 else: 

131 kpt.ne_o[o] *= 0.5 * kpt.weight 

132 

133 return f_qn, fermi_levels, e_entropy 

134 

135 def calculate_band_energy(self, wfs): 

136 de_band = 0.0 

137 for kpt in wfs.kpt_u: 

138 for ne, c_n in zip(kpt.ne_o, kpt.c_on): 

139 de_band += ne * np.dot(np.abs(c_n)**2, kpt.eps_n) 

140 return de_band 

141 

142 

143class MolecularOrbital: 

144 r"""Class defining orbitals that should be filled in a dSCF calculation. 

145 

146 An orbital is defined through a linear combination of the atomic 

147 partial waves. In each self-consistent cycle the method expand 

148 is called. This method take the Kohn-Sham orbitals fulfilling the 

149 criteria given by Estart, Eend and nos and return the best 

150 possible expansion of the orbital in this basis. The integral 

151 of the Kohn-Sham all-electron wavefunction ``|u,n>`` (u being local spin 

152 and kpoint index) and the partial wave ``|\phi_i^a>`` is approximated 

153 by:: 

154 

155 wfs.kpt_u[u].P_ani = <\tilde p_i^a|\tilde\psi_{un}>. 

156 

157 Parameters 

158 ---------- 

159 paw: gpaw calculator instance 

160 The calculator used in the dSCF calculation. 

161 Estart: float 

162 Kohn-Sham orbitals with an energy above Efermi+Estart are used 

163 in the linear expansion. 

164 Eend: float 

165 Kohn-Sham orbitals with an energy below Efermi+Eend are used 

166 in the linear expansion. 

167 nos: int 

168 The maximum Number Of States used in the linear expansion. 

169 weights: dictionary 

170 The keys represent atoms and have values which are lists of weights 

171 of the contributing partial waves. The default value thuis corresponds 

172 to an antibonding 2\sigma orbital of atoms 0 and 1. 

173 Format:: 

174 

175 {1. atom: [weight of 1. projector function of the 1. atom, 

176 weight of 2. projector function of the 1. atom, ...], 

177 2. atom: [weight of 1. projector function of the 2. atom, 

178 weight of 2. projector function of the 2. atom, ...], 

179 ...} 

180 """ 

181 

182 def __init__(self, paw, Estart=0.0, Eend=1.e6, 

183 nos=None, weights={0: [1], 1: [-1]}): 

184 

185 self.w = weights 

186 self.Estart = Estart 

187 self.Eend = Eend 

188 self.nos = nos 

189 

190 def expand(self, epsF, wfs, fixmom): 

191 if wfs.nspins == 2 and not fixmom: 

192 epsF = epsF * 2 

193 

194 if self.nos is None: 

195 self.nos = wfs.bd.nbands 

196 

197 c_un = [] 

198 for u, kpt in enumerate(wfs.kpt_u): 

199 Porb_n = np.zeros(wfs.bd.nbands, dtype=complex) 

200 for a, P_ni in kpt.P_ani.items(): 

201 if a in self.w.keys(): 

202 for i in range(len(self.w[a])): 

203 Porb_n += self.w[a][i] * P_ni[:, i] 

204 wfs.gd.comm.sum(Porb_n) 

205 

206 # Starting from KS orbitals with largest overlap, 

207 # fill in the expansion coeffients between Estart and Eend 

208 c_n = np.zeros(wfs.bd.nbands, dtype=complex) 

209 nos = 0 

210 bandpriority = np.argsort(abs(Porb_n)**2)[::-1] 

211 

212 for n in bandpriority: 

213 if (kpt.eps_n[n] > epsF[kpt.s] + self.Estart and 

214 kpt.eps_n[n] < epsF[kpt.s] + self.Eend): 

215 c_n[n] = Porb_n[n].conj() 

216 nos += 1 

217 if nos == self.nos: 

218 break 

219 

220 # Normalize expansion coefficients 

221 c_n /= np.sqrt(sum(abs(c_n)**2)) 

222 c_un.append(c_n) 

223 

224 return c_un 

225 

226 

227class AEOrbital: 

228 """Class defining the orbitals that should be filled in a dSCF calculation. 

229 

230 An orbital is defined through a linear combination of KS orbitals 

231 which is determined by this class as follows: For each kpoint and spin 

232 we calculate the quantity ``c_n = <n|a>`` where ``|n>`` is the 

233 all-electron KS states in the calculation and ``|a>`` is the 

234 all-electron resonant state to be kept occupied. We can then 

235 write ``|a> = Sum(c_n|n>)`` and in each self-consistent cycle the 

236 method expand is called. This method take the Kohn-Sham 

237 orbitals fulfilling the criteria given by Estart, Eend and 

238 nos (Number Of States) and return the best possible expansion of 

239 the orbital in this basis. 

240 

241 Parameters 

242 ---------- 

243 paw: gpaw calculator instance 

244 The calculator used in the dSCF calculation. 

245 molecule: list of integers 

246 The atoms, which are a part of the molecule. 

247 Estart: float 

248 Kohn-Sham orbitals with an energy above Efermi+Estart are used 

249 in the linear expansion. 

250 Eend: float 

251 Kohn-Sham orbitals with an energy below Efermi+Eend are used 

252 in the linear expansion. 

253 nos: int 

254 The maximum Number Of States used in the linear expansion. 

255 wf_u: list of wavefunction arrays 

256 Wavefunction to be occupied on the kpts on this processor: 

257 

258 wf_u = [kpt.psit_nG[n] for kpt in calc_mol.wfs.kpt_u] 

259 

260 p_uai: list of dictionaries 

261 Projector overlaps with the wavefunction to be occupied for each 

262 kpoint. These are used when correcting to all-electron wavefunction 

263 overlaps: 

264 

265 p_uai = [dict([(mol[a], P_ni[n]) for a, P_ni in kpt.P_ani.items()]) 

266 for kpt in paw.wfs.kpt_u] 

267 

268 where mol is a list of atoms contributing to the state and n is the 

269 band. See examples in the dscf documentation on the gpaw webpage. 

270 """ 

271 

272 def __init__(self, paw, wf_u, p_uai, Estart=0.0, Eend=1.e6, nos=None, 

273 txt='-'): 

274 

275 self.wf_u = wf_u 

276 self.p_uai = p_uai 

277 self.Estart = Estart 

278 self.Eend = Eend 

279 self.nos = nos 

280 

281 if txt is None: 

282 self.txt = devnull 

283 elif txt == '-': 

284 self.txt = sys.stdout 

285 elif isinstance(txt, str): 

286 self.txt = paropen(txt, 'w') 

287 else: 

288 self.txt = txt 

289 

290 def expand(self, epsF, wfs, fixmom): 

291 

292 if wfs.mode == 'lcao': 

293 wfs.initialize_wave_functions_from_lcao() 

294 if wfs.nspins == 2 and not fixmom: 

295 epsF = epsF * 2 

296 

297 if self.nos is None: 

298 self.nos = wfs.bd.nbands 

299 

300 # Check dimension of lists 

301 if len(self.wf_u) == len(wfs.kpt_u): 

302 wf_u = self.wf_u 

303 p_uai = self.p_uai 

304 else: 

305 raise RuntimeError('List of wavefunctions has wrong size') 

306 

307 c_un = [] 

308 p_u = [] 

309 for u, kpt in enumerate(wfs.kpt_u): 

310 

311 # Inner product of pseudowavefunctions 

312 wf = np.reshape(wf_u[u], -1) 

313 Wf_n = kpt.psit_nG 

314 Wf_n = np.reshape(Wf_n, (len(Wf_n), -1)) 

315 Porb_n = np.dot(Wf_n.conj(), wf) * wfs.gd.dv 

316 

317 # Correction to obtain inner product of AE wavefunctions 

318 for a, p_i in p_uai[u].items(): 

319 for n in range(wfs.bd.nbands): 

320 for i in range(len(p_i)): 

321 for j in range(len(p_i)): 

322 Porb_n[n] += (kpt.P_ani[a][n][i].conj() * 

323 wfs.setups[a].dO_ii[i][j] * 

324 p_i[j]) 

325 wfs.gd.comm.sum(Porb_n) 

326 

327 # print 'Kpt:', kpt.k, ' Spin:', kpt.s, \ 

328 # ' Sum_n|<orb|nks>|^2:', sum(abs(Porb_n)**2) 

329 p_u.append(np.array([sum(abs(Porb_n)**2)], dtype=float)) 

330 

331 # Starting from KS orbitals with largest overlap, 

332 # fill in the expansion coeffients 

333 c_n = np.zeros(wfs.bd.nbands, dtype=complex) 

334 nos = 0 

335 bandpriority = np.argsort(abs(Porb_n)**2)[::-1] 

336 

337 for n in bandpriority: 

338 if (kpt.eps_n[n] > epsF[kpt.s] + self.Estart and 

339 kpt.eps_n[n] < epsF[kpt.s] + self.Eend): 

340 c_n[n] = Porb_n[n] 

341 nos += 1 

342 if nos == self.nos: 

343 break 

344 

345 # Normalize expansion coefficients 

346 c_n /= np.sqrt(sum(abs(c_n)**2)) 

347 c_un.append(c_n) 

348 

349 for s in range(wfs.nspins): 

350 for k in range(wfs.kd.nibzkpts): 

351 p = wfs.collect_auxiliary(p_u, k, s) 

352 if wfs.world.rank == 0: 

353 self.txt.write('Kpt: %d, Spin: %d, ' 

354 'Sum_n|<orb|nks>|^2: %f\n' % (k, s, p)) 

355 

356 return c_un