Coverage for gpaw/nlopt/shg.py: 74%

140 statements  

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

1import numpy as np 

2from ase.parallel import parprint 

3from ase.units import _e, Bohr, Ha, J 

4from ase.utils.timing import Timer 

5 

6from gpaw.mpi import world 

7from gpaw.nlopt.matrixel import get_derivative, get_rml 

8from gpaw.utilities.progressbar import ProgressBar 

9 

10 

11def get_shg( 

12 nlodata, 

13 freqs=[1.0], 

14 eta=0.05, 

15 pol='yyy', 

16 eshift=0.0, 

17 gauge='lg', 

18 ftol=1e-4, Etol=1e-6, 

19 band_n=None, 

20 out_name='shg.npy'): 

21 """ 

22 Calculate SHG spectrum within the independent particle approximation (IPA) 

23 for nonmagnetic semiconductors. 

24 

25 Output: shg.npy file with numpy array containing the spectrum and 

26 frequencies. 

27 

28 Parameters 

29 ---------- 

30 nlodata 

31 Data object of class NLOData. Contains energies, occupancies and 

32 momentum matrix elements. 

33 freqs 

34 Excitation frequency array (a numpy array or list). 

35 eta 

36 Broadening, a number or an array (default 0.05 eV). 

37 pol 

38 Tensor element (default 'yyy'). 

39 gauge 

40 Choose the gauge ('lg' or 'vg'). 

41 Etol, ftol 

42 Tol. in energy and fermi to consider degeneracy. 

43 band_n 

44 List of bands in the sum (default 0 to nb). 

45 out_name 

46 Output filename (default 'shg.npy'). 

47 

48 """ 

49 

50 # Start a timer 

51 timer = Timer() 

52 parprint(f'Calculating SHG spectrum (in {world.size:d} cores).') 

53 

54 # Covert inputs in eV to Ha 

55 freqs = np.array(freqs) 

56 nw = len(freqs) 

57 w_lc = (freqs + 1e-12 + 1j * eta) / Ha # Add small value to avoid 0 

58 Etol /= Ha 

59 eshift /= Ha 

60 

61 # Useful variables 

62 pol_v = ['xyz'.index(ii) for ii in pol] 

63 

64 # Use the TRS to reduce calculation time 

65 w_l = np.hstack((-w_lc[-1::-1], w_lc)) 

66 nw = 2 * nw 

67 parprint(f'Calculation in the {gauge} gauge for element {pol}.') 

68 

69 # Load the required data 

70 with timer('Load and distribute the data'): 

71 k_info = nlodata.distribute() 

72 if k_info: 

73 tmp = list(k_info.values())[0] 

74 nb = len(tmp[1]) 

75 nk = len(k_info) * world.size # Approximately 

76 if band_n is None: 

77 band_n = list(range(nb)) 

78 mem = 6 * 3 * nk * nb**2 * 16 / 2**20 

79 parprint(f'At least {mem:.2f} MB of memory is required.') 

80 

81 # Initial call to print 0% progress 

82 count = 0 

83 ncount = len(k_info) 

84 if world.rank == 0: 

85 pb = ProgressBar() 

86 

87 # Initialize the outputs 

88 sum2_l = np.zeros((nw), complex) 

89 sum3_l = np.zeros((nw), complex) 

90 

91 # Do the calculations 

92 for _, (we, f_n, E_n, p_vnn) in k_info.items(): 

93 # Which gauge 

94 if gauge == 'vg': 

95 with timer('Sum over bands'): 

96 tmp = shg_velocity_gauge( 

97 w_l, f_n, E_n, p_vnn, pol_v, 

98 band_n, ftol, Etol, eshift) 

99 elif gauge == 'lg': 

100 with timer('Position matrix elements calculation'): 

101 r_vnn, D_vnn = get_rml(E_n, p_vnn, pol_v, Etol=Etol) 

102 

103 with timer('Compute generalized derivative'): 

104 rd_vvnn = get_derivative(E_n, r_vnn, D_vnn, pol_v, Etol=Etol) 

105 

106 with timer('Sum over bands'): 

107 tmp = shg_length_gauge( 

108 w_l, f_n, E_n, r_vnn, rd_vvnn, D_vnn, pol_v, 

109 band_n, ftol, Etol, eshift) 

110 else: 

111 parprint('Gauge ' + gauge + ' not implemented.') 

112 raise NotImplementedError 

113 

114 # Add it to previous with a weight 

115 sum2_l += tmp[0] * we 

116 sum3_l += tmp[1] * we 

117 

118 # Print the progress 

119 if world.rank == 0: 

120 pb.update(count / ncount) 

121 count += 1 

122 

123 if world.rank == 0: 

124 pb.finish() 

125 with timer('Gather data from cores'): 

126 world.sum(sum2_l) 

127 world.sum(sum3_l) 

128 

129 # Make the output in SI unit 

130 chi_l = make_output(gauge, sum2_l, sum3_l) 

131 # A multi-col output 

132 nw = len(freqs) 

133 chi_l = chi_l[nw:] + chi_l[nw - 1::-1] 

134 shg = np.vstack((freqs, chi_l)) 

135 

136 # Save it to the file 

137 if world.rank == 0: 

138 np.save(out_name, shg) 

139 

140 # Print the timing 

141 timer.write() 

142 

143 return shg 

144 

145 

146def shg_velocity_gauge( 

147 w_l, f_n, E_n, p_vnn, pol_v, 

148 band_n=None, ftol=1e-4, Etol=1e-6, eshift=0): 

149 """ 

150 Loop over bands for computing in velocity gauge 

151 

152 Input: 

153 w_l Complex frequency array 

154 f_n Fermi levels 

155 E_n Energies 

156 p_vnn Momentum matrix elements 

157 pol_v Tensor element 

158 band_n Band list 

159 Etol, ftol Tol. in energy and fermi to consider degeneracy 

160 eshift Bandgap correction 

161 Output: 

162 sum2_l, sum3_l Output 2 and 3 bands terms 

163 """ 

164 

165 # Initialize variables 

166 nb = len(f_n) 

167 if band_n is None: 

168 band_n = list(range(nb)) 

169 sum2_l = np.zeros(w_l.size, complex) 

170 sum3_l = np.zeros(w_l.size, complex) 

171 

172 # Loop over bands 

173 for nni in band_n: 

174 for mmi in band_n: 

175 # Remove non important term using TRS 

176 if mmi <= nni: 

177 continue 

178 

179 # Useful variables 

180 fnm = f_n[nni] - f_n[mmi] 

181 Emn = E_n[mmi] - E_n[nni] + fnm * eshift 

182 

183 # Comute the 2-band term 

184 if np.abs(Emn) > Etol and np.abs(fnm) > ftol: 

185 pnml = (p_vnn[pol_v[0], nni, mmi] 

186 * (p_vnn[pol_v[1], mmi, nni] 

187 * (p_vnn[pol_v[2], mmi, mmi] 

188 - p_vnn[pol_v[2], nni, nni]) 

189 + p_vnn[pol_v[2], mmi, nni] 

190 * (p_vnn[pol_v[1], mmi, mmi] 

191 - p_vnn[pol_v[1], nni, nni])) / 2) 

192 sum2_l += 1j * fnm * np.imag(pnml) * \ 

193 (1 / (Emn**4 * (w_l - Emn)) - 

194 16 / (Emn**4 * (2 * w_l - Emn))) 

195 

196 # Loop over the last band index 

197 for lli in band_n: 

198 fnl = f_n[nni] - f_n[lli] 

199 fml = f_n[mmi] - f_n[lli] 

200 

201 # Do not do zero calculations 

202 if np.abs(fnl) < ftol and np.abs(fml) < ftol: 

203 continue 

204 

205 # Compute the susceptibility with 1/w form 

206 Eln = E_n[lli] - E_n[nni] + fnl * eshift 

207 Eml = E_n[mmi] - E_n[lli] - fml * eshift 

208 pnml = (p_vnn[pol_v[0], nni, mmi] 

209 * (p_vnn[pol_v[1], mmi, lli] 

210 * p_vnn[pol_v[2], lli, nni] 

211 + p_vnn[pol_v[2], mmi, lli] 

212 * p_vnn[pol_v[1], lli, nni])) 

213 pnml = 1j * np.imag(pnml) / 2 

214 

215 # Compute the divergence-free terms 

216 if np.abs(Emn) > Etol and np.abs( 

217 Eml) > Etol and np.abs(Eln) > Etol: 

218 ftermD = (16 / (Emn**3 * (2 * w_l - Emn)) 

219 * (fnl / (Emn - 2 * Eln) 

220 + fml / (Emn - 2 * Eml))) \ 

221 + fnl / (Eln**3 * (2 * Eln - Emn) 

222 * (w_l - Eln)) \ 

223 + fml / (Eml**3 * (2 * Eml - Emn) 

224 * (w_l - Eml)) 

225 sum3_l += pnml * ftermD 

226 

227 return sum2_l, sum3_l 

228 

229 

230def shg_length_gauge( 

231 w_l, f_n, E_n, r_vnn, rd_vvnn, D_vnn, pol_v, 

232 band_n=None, ftol=1e-4, Etol=1e-6, eshift=0): 

233 """ 

234 Loop over bands for computing in length gauge 

235 

236 Input: 

237 w_l Complex frequency array 

238 f_n Fermi levels 

239 E_n Energies 

240 r_vnn Momentum matrix elements 

241 rd_vvnn Generalized derivative of position 

242 D_vnn Velocity difference 

243 pol_v Tensor element 

244 band_n Band list 

245 Etol, ftol Tol. in energy and fermi to consider degeneracy 

246 eshift Band gap correction in eV 

247 Output: 

248 sum2_l, sum3_l Output 2 and 3 bands terms 

249 """ 

250 

251 # Initialize variables 

252 nb = len(f_n) 

253 if band_n is None: 

254 band_n = list(range(nb)) 

255 sum2_l = np.zeros(w_l.size, complex) 

256 sum3_l = np.zeros(w_l.size, complex) 

257 

258 # Loop over bands 

259 for nni in band_n: 

260 for mmi in band_n: 

261 # Remove the non important term using TRS 

262 if mmi <= nni: 

263 continue 

264 fnm = f_n[nni] - f_n[mmi] 

265 Emn = E_n[mmi] - E_n[nni] + fnm * eshift 

266 

267 # Two band part 

268 if np.abs(fnm) > ftol: 

269 tmp = 2 * np.imag( 

270 r_vnn[pol_v[0], nni, mmi] 

271 * (rd_vvnn[pol_v[1], pol_v[2], mmi, nni] 

272 + rd_vvnn[pol_v[2], pol_v[1], mmi, nni])) \ 

273 / (Emn * (2 * w_l - Emn)) 

274 tmp += np.imag( 

275 r_vnn[pol_v[1], mmi, nni] 

276 * rd_vvnn[pol_v[2], pol_v[0], nni, mmi] 

277 + r_vnn[pol_v[2], mmi, nni] 

278 * rd_vvnn[pol_v[1], pol_v[0], nni, mmi]) \ 

279 / (Emn * (w_l - Emn)) 

280 tmp += np.imag( 

281 r_vnn[pol_v[0], nni, mmi] 

282 * (r_vnn[pol_v[1], mmi, nni] 

283 * D_vnn[pol_v[2], mmi, nni] 

284 + r_vnn[pol_v[2], mmi, nni] 

285 * D_vnn[pol_v[1], mmi, nni])) \ 

286 * (1 / (w_l - Emn) 

287 - 4 / (2 * w_l - Emn)) / Emn**2 

288 tmp -= np.imag( 

289 r_vnn[pol_v[1], mmi, nni] 

290 * rd_vvnn[pol_v[0], pol_v[2], nni, mmi] 

291 + r_vnn[pol_v[2], mmi, nni] 

292 * rd_vvnn[pol_v[0], pol_v[1], nni, mmi]) \ 

293 / (2 * Emn * (w_l - Emn)) 

294 sum2_l += 1j * fnm * tmp / 2 # 1j imag 

295 

296 # Three band term 

297 for lli in band_n: 

298 fnl = f_n[nni] - f_n[lli] 

299 fml = f_n[mmi] - f_n[lli] 

300 Eml = E_n[mmi] - E_n[lli] - fml * eshift 

301 Eln = E_n[lli] - E_n[nni] + fnl * eshift 

302 # Do not do zero calculations 

303 if (np.abs(fnm) < ftol and np.abs(fnl) < ftol 

304 and np.abs(fml) < ftol): 

305 continue 

306 if np.abs(Eln - Eml) < Etol: 

307 continue 

308 

309 rnml = np.real( 

310 r_vnn[pol_v[0], nni, mmi] 

311 * (r_vnn[pol_v[1], mmi, lli] 

312 * r_vnn[pol_v[2], lli, nni] 

313 + r_vnn[pol_v[2], mmi, lli] 

314 * r_vnn[pol_v[1], lli, nni])) / (2 * (Eln - Eml)) 

315 if np.abs(fnm) > ftol: 

316 sum3_l += 2 * fnm / (2 * w_l - Emn) * rnml 

317 if np.abs(fnl) > ftol: 

318 sum3_l += -fnl / (w_l - Eln) * rnml 

319 if np.abs(fml) > ftol: 

320 sum3_l += fml / (w_l - Eml) * rnml 

321 

322 return sum2_l, sum3_l 

323 

324 

325def make_output(gauge, sum2_l, sum3_l): 

326 """ 

327 Multiply prefactors and return second-order chi in SI units [m / V] 

328 

329 Input: 

330 gauge Chosen gauge 

331 sum2_l 2-bands term 

332 sum3_l 3-bands term 

333 Output: 

334 chi_l Output chi as an array 

335 """ 

336 

337 # 4 * pi from vacuum permittivty eps_0 in atomic units 

338 prefactor = 4.0 * np.pi 

339 # Pi factors from BZ volume 

340 prefactor /= (2.0 * np.pi)**3 

341 # atomic units [Bohr * elementary charge / Hartee] to SI units [m / V] 

342 prefactor *= (Bohr * 1e-10 * _e) / (Ha / J) 

343 

344 if gauge == 'lg': 

345 chi_l = prefactor * (1j * sum2_l + sum3_l) 

346 elif gauge == 'vg': 

347 chi_l = prefactor * 1j / 2 * (sum2_l + sum3_l) 

348 else: 

349 parprint('Gauge ' + gauge + ' not implemented.') 

350 raise NotImplementedError 

351 

352 return chi_l