Coverage for gpaw/atom/atompaw.py: 98%

252 statements  

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

1from math import pi, sqrt 

2 

3import numpy as np 

4from ase.atoms import Atoms 

5from scipy.linalg import eigh 

6 

7from gpaw.calculator import GPAW 

8from gpaw.wavefunctions.base import WaveFunctions 

9from gpaw.atom.radialgd import EquidistantRadialGridDescriptor 

10from gpaw.utilities import unpack_hermitian 

11from gpaw.occupations import OccupationNumberCalculator 

12import gpaw.mpi as mpi 

13 

14 

15class MakeWaveFunctions: 

16 name = 'atompaw' 

17 interpolation = 9 

18 force_complex_dtype = False 

19 

20 def __init__(self, gd): 

21 self.gd = gd 

22 

23 def __call__(self, paw, gd, *args, **kwargs): 

24 return AtomWaveFunctions(self.gd, *args, **kwargs) 

25 

26 

27class AtomWaveFunctionsArray: 

28 def __init__(self, psit_nG): 

29 self.array = psit_nG 

30 

31 

32class AtomWaveFunctions(WaveFunctions): 

33 mode = 'atompaw' 

34 

35 def initialize(self, density, hamiltonian, spos_ac): 

36 setup = self.setups[0] 

37 bf = AtomBasisFunctions(self.gd, setup.basis_functions_J) 

38 density.initialize_from_atomic_densities(bf) 

39 hamiltonian.update(density) 

40 return 0, 0 

41 

42 def add_to_density_from_k_point(self, nt_sG, kpt): 

43 nt_sG[kpt.s] += np.dot(kpt.f_n / 4 / pi, kpt.psit_nG**2) 

44 

45 def summary(self, log): 

46 log('Mode: Spherically symmetric atomic solver') 

47 

48 

49class AtomPoissonSolver: 

50 def get_description(self): 

51 return 'Radial equidistant' 

52 

53 def set_grid_descriptor(self, gd): 

54 self.gd = gd 

55 self.relax_method = 0 

56 self.nn = 1 

57 

58 def initialize(self): 

59 pass 

60 

61 def get_stencil(self): 

62 return 'Exact' 

63 

64 def solve(self, vHt_g, rhot_g, charge=0, timer=None): 

65 r = self.gd.r_g 

66 dp = rhot_g * r * self.gd.dr_g 

67 dq = dp * r 

68 p = np.add.accumulate(dp[::-1])[::-1] 

69 q = np.add.accumulate(dq[::-1])[::-1] 

70 vHt_g[:] = 4 * pi * (p - 0.5 * dp - (q - 0.5 * dq - q[0]) / r) 

71 return 1 

72 

73 

74class AtomEigensolver: 

75 def __init__(self, gd, f_sln): 

76 self.gd = gd 

77 self.f_sln = f_sln 

78 self.error = 0.0 

79 self.initialized = False 

80 

81 def reset(self): 

82 self.initialized = False 

83 

84 def initialize(self, wfs): 

85 r = self.gd.r_g 

86 h = r[0] 

87 N = len(r) 

88 lmax = len(self.f_sln[0]) - 1 

89 

90 self.T_l = [np.eye(N) * (1.0 / h**2)] 

91 self.T_l[0].flat[1::N + 1] = -0.5 / h**2 

92 self.T_l[0].flat[N::N + 1] = -0.5 / h**2 

93 for l in range(1, lmax + 1): 

94 self.T_l.append(self.T_l[0] + np.diag(l * (l + 1) / 2.0 / r**2)) 

95 

96 self.S_l = [np.eye(N) for l in range(lmax + 1)] 

97 setup = wfs.setups[0] 

98 self.pt_j = np.array([[pt(x) * x**l for x in r] 

99 for pt, l in zip(setup.pt_j, setup.l_j)]) 

100 

101 dS_ii = setup.dO_ii 

102 i1 = 0 

103 for pt1, l1 in zip(self.pt_j, setup.l_j): 

104 i2 = 0 

105 for pt2, l2 in zip(self.pt_j, setup.l_j): 

106 if l1 == l2 and l1 <= lmax: 

107 self.S_l[l1] += (np.outer(pt1 * r, pt2 * r) * 

108 h * dS_ii[i1, i2]) 

109 i2 += 2 * l2 + 1 

110 i1 += 2 * l1 + 1 

111 

112 for kpt in wfs.kpt_u: 

113 kpt.eps_n = np.empty(wfs.bd.nbands) 

114 kpt.psit = AtomWaveFunctionsArray(self.gd.empty(wfs.bd.nbands)) 

115 kpt.projections = {0: np.zeros((wfs.bd.nbands, len(dS_ii)))} 

116 

117 self.initialized = True 

118 

119 def iterate(self, hamiltonian, wfs): 

120 if not self.initialized: 

121 self.initialize(wfs) 

122 

123 r = self.gd.r_g 

124 h = r[0] 

125 N = len(r) 

126 lmax = len(self.f_sln[0]) - 1 

127 setup = wfs.setups[0] 

128 

129 e_n = np.zeros(N) 

130 

131 for s in range(wfs.nspins): 

132 dH_ii = unpack_hermitian(hamiltonian.dH_asp[0][s]) 

133 kpt = wfs.kpt_u[s] 

134 N1 = 0 

135 for l in range(lmax + 1): 

136 H = self.T_l[l] + np.diag(hamiltonian.vt_sg[s]) 

137 i1 = 0 

138 for pt1, l1 in zip(self.pt_j, setup.l_j): 

139 i2 = 0 

140 for pt2, l2 in zip(self.pt_j, setup.l_j): 

141 if l1 == l2 == l: 

142 H += (h * dH_ii[i1, i2] * 

143 np.outer(pt1 * r, pt2 * r)) 

144 i2 += 2 * l2 + 1 

145 i1 += 2 * l1 + 1 

146 e_n, H = eigh(H, self.S_l[l].copy()) 

147 

148 for n in range(len(self.f_sln[s][l])): 

149 N2 = N1 + 2 * l + 1 

150 kpt.eps_n[N1:N2] = e_n[n] 

151 kpt.psit_nG[N1:N2] = H[:, n] / r / sqrt(h) 

152 i1 = 0 

153 for pt, ll in zip(self.pt_j, setup.l_j): 

154 i2 = i1 + 2 * ll + 1 

155 if ll == l: 

156 P = np.dot(kpt.psit_nG[N1], pt * r**2) * h 

157 kpt.P_ani[0][N1:N2, i1:i2] = P * np.eye(2 * l + 1) 

158 i1 = i2 

159 N1 = N2 

160 

161 

162class AtomLocalizedFunctionsCollection: 

163 def __init__(self, gd, spline_aj): 

164 self.gd = gd 

165 spline = spline_aj[0][0] 

166 self.b_g = np.array([spline(r) for r in gd.r_g]) / sqrt(4 * pi) 

167 self.nfunctions = sum(2 * spline.get_angular_momentum_number() + 1 

168 for spline in spline_aj[0]) 

169 

170 def set_positions(self, spos_ac, atom_partition=None): 

171 pass 

172 

173 def add(self, a_xG, c_axi=1.0, q=-1): 

174 assert q == -1 

175 if isinstance(c_axi, float): 

176 a_xG += c_axi * self.b_g 

177 else: 

178 a_xG += c_axi[0][0] * self.b_g 

179 

180 def integrate(self, a_g, c_ai, q=-1): 

181 assert a_g.ndim == 1 

182 assert q == -1 

183 c_ai[0][0] = self.gd.integrate(a_g, self.b_g) 

184 c_ai[0][1:] = 0.0 

185 

186 def dict(self): 

187 return {0: np.empty(self.nfunctions)} 

188 

189 

190class AtomBasisFunctions: 

191 def __init__(self, gd, bfs_J): 

192 self.gd = gd 

193 self.bl_J = [] 

194 self.Mmax = 0 

195 

196 for bf in bfs_J: 

197 l = bf.get_angular_momentum_number() 

198 self.bl_J.append((np.array([bf(x) * x**l for x in gd.r_g]), l)) 

199 self.Mmax += 2 * l + 1 

200 self.atom_indices = [0] 

201 self.my_atom_indices = [0] 

202 

203 def set_positions(self, spos_ac): 

204 pass 

205 

206 def add_to_density(self, nt_sG, f_asi): 

207 i = 0 

208 for b_g, l in self.bl_J: 

209 nt_sG += f_asi[0][:, i:i + 1] * (2 * l + 1) / 4 / pi * b_g**2 

210 i += 2 * l + 1 

211 

212 

213class AtomGridDescriptor(EquidistantRadialGridDescriptor): 

214 def __init__(self, h, rcut): 

215 ng = int(float(rcut) / h + 0.5) - 1 

216 rcut = ng * h 

217 super().__init__(h, ng, h0=h) 

218 self.sdisp_cd = np.empty((3, 2)) 

219 self.comm = mpi.serial_comm 

220 self.pbc_c = np.zeros(3, bool) 

221 self.cell_cv = np.eye(3) * rcut 

222 self.N_c = np.ones(3, dtype=int) * 2 * ng 

223 self.h_cv = self.cell_cv / self.N_c 

224 self.dv = (rcut / 2 / ng)**3 

225 self.orthogonal = False 

226 self.parsize_c = (1, 1, 1) 

227 

228 def get_ranks_from_positions(self, spos_ac): 

229 return np.array([0]) 

230 

231 def refine(self): 

232 return self 

233 

234 def get_lfc(self, gd, spline_aj): 

235 return AtomLocalizedFunctionsCollection(gd, spline_aj) 

236 

237 def integrate(self, a_xg, b_xg=None, global_integral=True): 

238 """Integrate function(s) in array over domain.""" 

239 if b_xg is None: 

240 return np.dot(a_xg, self.dv_g) 

241 else: 

242 return np.dot(a_xg * b_xg, self.dv_g) 

243 

244 def calculate_dipole_moment(self, rhot_g): 

245 return np.zeros(3) 

246 

247 def symmetrize(self, a_g, op_scc, ft_sc=None): 

248 pass 

249 

250 def get_grid_spacings(self): 

251 return self.h_cv.diagonal() 

252 

253 def get_size_of_global_array(self, pad=None): 

254 return np.array(len(self.N_c)) 

255 

256 def new_descriptor(self, *args, **kwargs): 

257 return self 

258 

259 def get_processor_position_from_rank(self, rank): 

260 return (0, 0, 0) 

261 

262 

263class AtomOccupations(OccupationNumberCalculator): 

264 extrapolate_factor = 0.0 

265 

266 def __init__(self, f_sln): 

267 self.f_sln = f_sln 

268 super().__init__() 

269 

270 def _calculate(self, 

271 nelectrons, 

272 eig_qn, 

273 weight_q, 

274 f_qn, 

275 fermi_level_guess, 

276 fix_fermi_level=False): 

277 for s, f_n in enumerate(f_qn): 

278 n1 = 0 

279 for l, f0_n in enumerate(self.f_sln[s]): 

280 for f in f0_n: 

281 n2 = n1 + 2 * l + 1 

282 f_n[n1:n2] = f / (2 * l + 1) / 2 

283 n1 = n2 

284 

285 return np.inf, 0.0 

286 

287 

288class AtomPAW(GPAW): 

289 def __init__(self, symbol, f_sln, h=0.05, rcut=10.0, **kwargs): 

290 assert len(f_sln) in [1, 2] 

291 self.symbol = symbol 

292 

293 gd = AtomGridDescriptor(h, rcut) 

294 super().__init__(mode=MakeWaveFunctions(gd), 

295 eigensolver=AtomEigensolver(gd, f_sln), 

296 poissonsolver=AtomPoissonSolver(), 

297 nbands=sum([(2 * l + 1) * len(f_n) 

298 for l, f_n in enumerate(f_sln[0])]), 

299 communicator=mpi.serial_comm, 

300 parallel=dict(augment_grids=False), 

301 occupations=AtomOccupations(f_sln), 

302 **kwargs) 

303 # Initialize function will raise an error unless we set a (bogus) cell 

304 self.initialize(Atoms(symbol, calculator=self, 

305 cell=np.eye(3))) 

306 self.density.charge_eps = 1e-3 

307 self.calculate(system_changes=['positions']) 

308 

309 def dry_run(self): 

310 pass 

311 

312 def state_iter(self): 

313 """Yield the tuples (l, n, f, eps, psit_G) of states. 

314 

315 Skips degenerate states.""" 

316 f_sln = self.wfs.occupations.f_sln 

317 assert len(f_sln) == 1, 'Not yet implemented with more spins' 

318 f_ln = f_sln[0] 

319 kpt = self.wfs.kpt_u[0] 

320 

321 band = 0 

322 for l, f_n in enumerate(f_ln): 

323 for n, f in enumerate(f_n): 

324 psit_G = kpt.psit_nG[band] 

325 eps = kpt.eps_n[band] 

326 yield l, n, f, eps, psit_G 

327 band += 2 * l + 1 

328 

329 def extract_basis_functions(self, n_j, l_j, basis_name='atompaw.sz'): 

330 """Create BasisFunctions object with pseudo wave functions.""" 

331 from gpaw.basis_data import Basis, BasisFunction 

332 assert self.wfs.nspins == 1 

333 d = self.wfs.gd.r_g[0] 

334 ng = self.wfs.gd.N + 1 

335 rgd = EquidistantRadialGridDescriptor(d, ng) 

336 

337 bf_j = [] 

338 for l, n, f, eps, psit_G in self.state_iter(): 

339 n = [N for N, L in zip(n_j, l_j) if L == l][n] 

340 bf_g = rgd.empty() 

341 bf_g[0] = 0.0 

342 bf_g[1:] = psit_G 

343 bf_g *= np.sign(psit_G[-1]) 

344 

345 # If there's no node at zero, we shouldn't set bf_g to zero 

346 # We'll make an ugly hack 

347 if abs(bf_g[1]) > 3.0 * abs(bf_g[2] - bf_g[1]): 

348 bf_g[0] = bf_g[1] 

349 bf = BasisFunction(n, l, self.wfs.gd.r_g[-1], bf_g, 

350 f'{n}{"spdfgh"[l]} e={eps:.3f} f={f:.3f}') 

351 bf_j.append(bf) 

352 

353 return Basis(self.symbol, basis_name, rgd=rgd, 

354 generatordata='AtomPAW', bf_j=bf_j)