Coverage for eminus/xc/utils.py: 84.39%

173 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-02 10:16 +0000

1# SPDX-FileCopyrightText: 2023 The eminus developers 

2# SPDX-License-Identifier: Apache-2.0 

3"""Utility functions for exchange-correlation functionals.""" 

4 

5import inspect 

6import sys 

7 

8import numpy as np 

9 

10from eminus import config 

11from eminus.logger import log 

12from eminus.utils import add_maybe_none 

13 

14from .gga_c_chachiyo import gga_c_chachiyo, gga_c_chachiyo_spin 

15from .gga_c_pbe import gga_c_pbe, gga_c_pbe_spin 

16from .gga_c_pbe_sol import gga_c_pbe_sol, gga_c_pbe_sol_spin 

17from .gga_x_chachiyo import gga_x_chachiyo, gga_x_chachiyo_spin 

18from .gga_x_pbe import gga_x_pbe, gga_x_pbe_spin 

19from .gga_x_pbe_sol import gga_x_pbe_sol, gga_x_pbe_sol_spin 

20from .lda_c_chachiyo import lda_c_chachiyo, lda_c_chachiyo_spin 

21from .lda_c_chachiyo_mod import lda_c_chachiyo_mod, lda_c_chachiyo_mod_spin 

22from .lda_c_pw import lda_c_pw, lda_c_pw_spin 

23from .lda_c_pw_mod import lda_c_pw_mod, lda_c_pw_mod_spin 

24from .lda_c_vwn import lda_c_vwn, lda_c_vwn_spin 

25from .lda_x import lda_x, lda_x_spin 

26from .lda_xc_corr_ksdt import lda_xc_corr_ksdt 

27from .lda_xc_gdsmfb import lda_xc_gdsmfb, lda_xc_gdsmfb_spin 

28from .lda_xc_ksdt import lda_xc_ksdt, lda_xc_ksdt_spin 

29 

30 

31def get_xc(xc, n_spin, Nspin, dn_spin=None, tau=None, xc_params=None, dens_threshold=0): 

32 """Handle and get exchange-correlation functionals. 

33 

34 Args: 

35 xc: Exchange and correlation identifier. 

36 n_spin: Real-space electronic densities per spin channel. 

37 Nspin: Number of spin states. 

38 

39 Keyword Args: 

40 dn_spin: Real-space gradient of densities per spin channel. 

41 tau: Real-space kinetic energy densities per spin channel. 

42 xc_params: Exchange-correlation functional parameters. 

43 dens_threshold: Do not treat densities smaller than the threshold. 

44 

45 Returns: 

46 Exchange-correlation energy density and potential. 

47 """ 

48 if isinstance(xc, str): 

49 xc = parse_functionals(xc) 

50 f_exch, f_corr = xc 

51 if xc_params is None: 

52 xc_params = {} 

53 

54 # Only use non-zero values of the density 

55 n = np.sum(n_spin, axis=0) 

56 nz_mask = np.where(n > dens_threshold) 

57 n_nz = n[nz_mask] 

58 # Zeta is only needed for non-zero values of the density 

59 zeta_nz = get_zeta(n_spin[:, nz_mask]) 

60 # dn_spin is only needed for non-zero values of the density 

61 if dn_spin is not None: 

62 dn_spin_nz = dn_spin[:, nz_mask[0], :] 

63 else: 

64 dn_spin_nz = None 

65 

66 def handle_functional(fxc): 

67 """Calculate a given functional fxc, same for exchange and correlation.""" 

68 # Calculate with the libxc extra... 

69 if ":" in fxc: 

70 from eminus.extras.libxc import libxc_functional 

71 

72 fxc = fxc.split(":")[-1] 

73 exc, vxc, vsigma, vtau = libxc_functional(fxc, n_spin, Nspin, dn_spin, tau, xc_params) 

74 # ...or use an internal functional 

75 else: 

76 if Nspin == 2 and fxc != "mock_xc": 

77 fxc += "_spin" 

78 exc_nz, vxc_nz, vsigma_nz = IMPLEMENTED[fxc]( 

79 n_nz, zeta=zeta_nz, dn_spin=dn_spin_nz, Nspin=Nspin, **xc_params 

80 ) 

81 # Map the non-zero values back to the right dimension 

82 exc = np.zeros_like(n) 

83 exc[nz_mask] = exc_nz 

84 vxc = np.zeros_like(n_spin) 

85 for s in range(Nspin): 

86 vxc[s, nz_mask] = vxc_nz[s] 

87 if vsigma_nz is not None: 

88 vsigma = np.zeros((len(vsigma_nz), len(exc))) 

89 for i in range(len(vsigma)): 

90 vsigma[i, nz_mask] = vsigma_nz[i] 

91 else: 

92 vsigma = None 

93 # There are no internal meta-GGAs 

94 vtau = None 

95 return exc, vxc, vsigma, vtau 

96 

97 ex, vx, vsigmax, vtaux = handle_functional(f_exch) # Calculate the exchange part 

98 ec, vc, vsigmac, vtauc = handle_functional(f_corr) # Calculate the correlation part 

99 return ex + ec, vx + vc, add_maybe_none(vsigmax, vsigmac), add_maybe_none(vtaux, vtauc) 

100 

101 

102def get_exc(xc, n_spin, Nspin, dn_spin=None, tau=None, xc_params=None, dens_threshold=0): 

103 """Get the exchange-correlation energy density. 

104 

105 This is a convenience function to interface :func:`~eminus.xc.utils.get_xc`. 

106 

107 Args: 

108 xc: Exchange and correlation identifier. 

109 n_spin: Real-space electronic densities per spin channel. 

110 Nspin: Number of spin states. 

111 

112 Keyword Args: 

113 dn_spin: Real-space gradient of densities per spin channel. 

114 tau: Real-space kinetic energy densities per spin channel. 

115 xc_params: Exchange-correlation functional parameters. 

116 dens_threshold: Do not treat densities smaller than the threshold. 

117 

118 Returns: 

119 Exchange-correlation energy potential. 

120 """ 

121 exc, _, _, _ = get_xc(xc, n_spin, Nspin, dn_spin, tau, xc_params, dens_threshold) 

122 return exc 

123 

124 

125def get_vxc(xc, n_spin, Nspin, dn_spin=None, tau=None, xc_params=None, dens_threshold=0): 

126 """Get the exchange-correlation potential. 

127 

128 This is a convenience function to interface :func:`~eminus.xc.utils.get_xc`. 

129 

130 Args: 

131 xc: Exchange and correlation identifier. 

132 n_spin: Real-space electronic densities per spin channel. 

133 Nspin: Number of spin states. 

134 

135 Keyword Args: 

136 dn_spin: Real-space gradient of densities per spin channel. 

137 tau: Real-space kinetic energy densities per spin channel. 

138 xc_params: Exchange-correlation functional parameters. 

139 dens_threshold: Do not treat densities smaller than the threshold. 

140 

141 Returns: 

142 Exchange-correlation energy density. 

143 """ 

144 _, vxc, vsigma, vtau = get_xc(xc, n_spin, Nspin, dn_spin, tau, xc_params, dens_threshold) 

145 return vxc, vsigma, vtau 

146 

147 

148def parse_functionals(xc): 

149 """Parse exchange-correlation functional strings to the internal format. 

150 

151 Args: 

152 xc: Exchange and correlation identifier, separated by a comma. 

153 

154 Returns: 

155 Exchange and correlation string. 

156 """ 

157 # Check for combined aliases 

158 try: 

159 # Remove underscores when looking up in the dictionary 

160 xc_ = xc.replace("_", "") 

161 xc = ALIAS[xc_] 

162 except KeyError: 

163 pass 

164 

165 # Parse functionals 

166 functionals = [] 

167 for f in xc.split(","): 

168 if ":" in f or f in IMPLEMENTED: 

169 f_xc = f 

170 elif not f: 

171 f_xc = "mock_xc" 

172 else: 

173 try: 

174 # Remove underscores when looking up in the dictionary 

175 f_ = f.replace("_", "") 

176 f_xc = XC_MAP[f_] 

177 except KeyError: 

178 log.exception(f'No functional found for "{f}".') 

179 raise 

180 functionals.append(f_xc) 

181 

182 # If only one or no functional has been parsed append with mock functionals 

183 functionals.extend("mock_xc" for _ in range(2 - len(functionals))) 

184 return functionals 

185 

186 

187def parse_xc_type(xc): 

188 """Parse functional strings to identify the corresponding functional type. 

189 

190 Args: 

191 xc: Exchange and correlation identifier, separated by a comma. 

192 

193 Returns: 

194 Functional type. 

195 """ 

196 xc_type = [] 

197 for func in xc: 

198 if ":" in func: 

199 xc_id = func.split(":")[-1] 

200 # Try to parse the functional using pylibxc at first 

201 try: 

202 family = parse_xc_libxc(xc_id) 

203 # Otherwise parse it with PySCF 

204 except (ImportError, AssertionError): 

205 family = parse_xc_pyscf(xc_id) 

206 

207 if family == 1: 

208 xc_type.append("lda") 

209 elif family == 2: 

210 xc_type.append("gga") 

211 elif family == 4: 

212 xc_type.append("meta-gga") 

213 else: 

214 msg = "Unsupported functional family." 

215 raise NotImplementedError(msg) 

216 # Fall back to internal xc functionals 

217 elif "gga" in func: 

218 xc_type.append("gga") 

219 else: 

220 xc_type.append("lda") 

221 

222 # When mixing functional types use the higher level of theory 

223 if xc_type[0] != xc_type[1]: 

224 log.warning("Detected mixing of different functional types.") 

225 if "meta-gga" in xc_type: 

226 return "meta-gga" 

227 return "gga" 

228 return xc_type[0] 

229 

230 

231def parse_xc_libxc(xc_id): 

232 """Parse functional type by its ID using pylibxc. 

233 

234 Args: 

235 xc_id: Functional ID or identifier. 

236 

237 Returns: 

238 Functional type. 

239 """ 

240 if not config.use_pylibxc: 

241 raise AssertionError 

242 import pylibxc 

243 

244 if not xc_id.isdigit(): 

245 xc_id = pylibxc.util.xc_functional_get_number(xc_id) 

246 

247 func = pylibxc.LibXCFunctional(int(xc_id), 1) 

248 if func._needs_laplacian: 

249 msg = "meta-GGAs that need a laplacian are not supported." 

250 raise NotImplementedError(msg) 

251 return func.get_family() 

252 

253 

254def parse_xc_pyscf(xc_id): 

255 """Parse functional type by its ID using PySCF. 

256 

257 Args: 

258 xc_id: Functional ID or identifier. 

259 

260 Returns: 

261 Functional type. 

262 """ 

263 from pyscf.dft.libxc import is_gga, is_lda, is_meta_gga, needs_laplacian, XC_CODES 

264 

265 if not xc_id.isdigit(): 

266 xc_id = XC_CODES[xc_id.upper()] 

267 

268 if needs_laplacian(int(xc_id)): 

269 msg = "meta-GGAs that need a laplacian are not supported." 

270 raise NotImplementedError(msg) 

271 # Use the same values as in parse_xc_libxc 

272 if is_lda(xc_id): 

273 return 1 

274 if is_gga(xc_id): 

275 return 2 

276 if is_meta_gga(xc_id): 

277 return 4 

278 return -1 

279 

280 

281def get_xc_defaults(xc): 

282 """Get the default parameters and values for a given set of functionals. 

283 

284 Args: 

285 xc: Exchange and correlation identifier, separated by a comma. 

286 

287 Returns: 

288 Default parameters and values. 

289 """ 

290 if isinstance(xc, str): 

291 xc = parse_functionals(xc) 

292 

293 # Names of special kewyword arguments that should not be used in xc_params 

294 SPECIAL_NAMES = ["dn_spin", "Nspin"] 

295 

296 params = {} 

297 for func in xc: 

298 # If pylibxc functionals are used determine the default values from it 

299 if ":" in func: 

300 # This only works for pylibxc, not with PySCF 

301 if not config.use_pylibxc or "pylibxc" not in sys.modules: 

302 msg = "ext_params only work with pylibxc as the libxc backend, not with pyscf." 

303 raise NotImplementedError(msg) 

304 from pylibxc import LibXCFunctional 

305 

306 fxc = func.split(":")[-1] 

307 try: 

308 f_xc = LibXCFunctional(int(fxc), 1) 

309 except ValueError: 

310 f_xc = LibXCFunctional(fxc, 1) 

311 fxc_params = dict(zip(f_xc.get_ext_param_names(), f_xc.get_ext_param_default_values())) 

312 

313 # Analyze the signature for implemented functionals 

314 if func in IMPLEMENTED: 

315 sig = inspect.signature(IMPLEMENTED[func]) 

316 fxc_params = { 

317 param.name: param.default 

318 for param in sig.parameters.values() 

319 if param.default is not inspect.Parameter.empty 

320 } 

321 

322 # Remove special names from the parsed parameters 

323 for special in SPECIAL_NAMES: 

324 if special in fxc_params: 

325 del fxc_params[special] 

326 

327 # Append all parameters, warn if a parameter has been used before 

328 for name in fxc_params: 

329 if name in params: 

330 log.warning( 

331 f'External parameter "{name}" is used in the exchange AND correlation part. ' 

332 "It will be passed to both functionals if used in xc_params." 

333 ) 

334 params[name] = fxc_params[name] 

335 return params 

336 

337 

338def get_zeta(n_spin): 

339 """Calculate the relative spin polarization. 

340 

341 Args: 

342 n_spin: Real-space electronic densities per spin channel. 

343 

344 Returns: 

345 Relative spin polarization. 

346 """ 

347 # If only one spin is given return an array of ones as if the density only is in one channel 

348 if len(n_spin) == 1: 

349 return np.ones_like(n_spin[0]) 

350 return (n_spin[0] - n_spin[1]) / (n_spin[0] + n_spin[1]) 

351 

352 

353def mock_xc(n, Nspin=1, **kwargs): 

354 """Mock exchange-correlation functional with no effect (spin-paired). 

355 

356 Args: 

357 n: Real-space electronic density. 

358 Nspin: Number of spin states. 

359 

360 Keyword Args: 

361 **kwargs: Throwaway arguments. 

362 

363 Returns: 

364 Mock exchange-correlation energy density and potential. 

365 """ 

366 zeros = np.zeros_like(n) 

367 return zeros, np.array([zeros] * Nspin), None 

368 

369 

370#: Map functional names with their respective implementation. 

371IMPLEMENTED = { 

372 i.__name__: i 

373 for i in ( 

374 mock_xc, 

375 gga_c_chachiyo, 

376 gga_c_chachiyo_spin, 

377 gga_c_pbe, 

378 gga_c_pbe_spin, 

379 gga_c_pbe_sol, 

380 gga_c_pbe_sol_spin, 

381 gga_x_chachiyo, 

382 gga_x_chachiyo_spin, 

383 gga_x_pbe, 

384 gga_x_pbe_spin, 

385 gga_x_pbe_sol, 

386 gga_x_pbe_sol_spin, 

387 lda_x, 

388 lda_x_spin, 

389 lda_c_pw, 

390 lda_c_pw_spin, 

391 lda_c_pw_mod, 

392 lda_c_pw_mod_spin, 

393 lda_c_vwn, 

394 lda_c_vwn_spin, 

395 lda_c_chachiyo, 

396 lda_c_chachiyo_spin, 

397 lda_c_chachiyo_mod, 

398 lda_c_chachiyo_mod_spin, 

399 lda_xc_corr_ksdt, 

400 lda_xc_gdsmfb, 

401 lda_xc_gdsmfb_spin, 

402 lda_xc_ksdt, 

403 lda_xc_ksdt_spin, 

404 ) 

405} 

406 

407#: Map functional shorthands to the actual functional names. 

408XC_MAP = { 

409 # lda_x 

410 "1": "lda_x", 

411 "s": "lda_x", 

412 "lda": "lda_x", 

413 "slater": "lda_x", 

414 # lda_c_vwn 

415 "7": "lda_c_vwn", 

416 "vwn": "lda_c_vwn", 

417 "vwn5": "lda_c_vwn", 

418 # lda_c_pw 

419 "12": "lda_c_pw", 

420 "pw": "lda_c_pw", 

421 "pw92": "lda_c_pw", 

422 # lda_c_pw_mod 

423 "13": "lda_c_pw_mod", 

424 "pwmod": "lda_c_pw_mod", 

425 "pw92mod": "lda_c_pw_mod", 

426 # gga_x_pbe 

427 "101": "gga_x_pbe", 

428 "pbex": "gga_x_pbe", 

429 # gga_x_pbe_sol 

430 "116": "gga_x_pbe_sol", 

431 "pbesolx": "gga_x_pbe_sol", 

432 # gga_c_pbe 

433 "130": "gga_c_pbe", 

434 "pbec": "gga_c_pbe", 

435 # gga_c_pbe_sol 

436 "133": "gga_c_pbe_sol", 

437 "pbesolc": "gga_c_pbe_sol", 

438 # lda_xc_ksdt 

439 "259": "lda_xc_ksdt", 

440 "ksdt": "lda_xc_ksdt", 

441 # lda_c_chachiyo 

442 "287": "lda_c_chachiyo", 

443 "chachiyo": "lda_c_chachiyo", 

444 # gga_x_chachiyo 

445 "298": "gga_x_chachiyo", 

446 "chachiyox": "gga_x_chachiyo", 

447 # lda_c_chachiyo_mod 

448 "307": "lda_c_chachiyo_mod", 

449 "chachiyomod": "lda_c_chachiyo_mod", 

450 # gga_c_chachiyo 

451 "309": "gga_c_chachiyo", 

452 "chachiyoc": "gga_c_chachiyo", 

453 # lda_xc_corr_ksdt 

454 "318": "lda_xc_corr_ksdt", 

455 "corrksdt": "lda_xc_corr_ksdt", 

456 # lda_xc_gdsmfb 

457 "577": "lda_xc_gdsmfb", 

458 "gdsmfb": "lda_xc_gdsmfb", 

459} 

460 

461#: Dictionary of common functional aliases. 

462ALIAS = { 

463 "svwn": "slater,vwn5", 

464 "svwn5": "slater,vwn5", 

465 "spw92": "slater,pw92mod", 

466 "pbe": "pbex,pbec", 

467 "pbesol": "pbesolx,pbesolc", 

468 "chachiyo": "chachiyox,chachiyoc", 

469}