Coverage for eminus/gth.py: 98.33%
120 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-02 10:16 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-02 10:16 +0000
1# SPDX-FileCopyrightText: 2021 The eminus developers
2# SPDX-License-Identifier: Apache-2.0
3"""Utilities to use Goedecker, Teter, and Hutter pseudopotentials."""
5import numpy as np
7from .io import read_gth
8from .utils import Ylm_real
11class GTH:
12 """GTH object that holds non-local projectors and parameters for all atoms in the SCF object.
14 Keyword Args:
15 scf: SCF object.
16 """
18 def __init__(self, scf=None):
19 """Initialize the GTH object."""
20 self.GTH = {} #: Dictionary with GTH parameters per atom type.
21 self.NbetaNL = 0 #: Number of projector functions for the non-local potential.
22 self.prj2beta = np.array([0]) #: Index matrix to map to the correct projector function.
23 self.betaNL = np.array([0]) #: Atomic-centered projector functions.
25 # Allow creating an empty instance (used when loading GTH objects from JSON files)
26 if scf is not None:
27 atoms = scf.atoms
29 # Set up a dictionary with all GTH parameters
30 for ia in range(atoms.Natoms):
31 # Skip if the atom is already in the dictionary
32 if atoms.atom[ia] in self.GTH:
33 continue
34 self.GTH[atoms.atom[ia]] = read_gth(atoms.atom[ia], atoms.Z[ia], psp_path=scf.psp)
36 # Initialize the non-local potential
37 NbetaNL, prj2beta, betaNL = init_gth_nonloc(atoms, self)
38 self.NbetaNL = NbetaNL
39 self.prj2beta = prj2beta
40 self.betaNL = betaNL
42 def __getitem__(self, key):
43 """Allow accessing the GTH parameters of an atom by indexing the GTH object."""
44 return self.GTH[key]
46 def __repr__(self):
47 """Print a short overview over the values stored in the GTH object."""
48 return f"NbetaNL: {self.NbetaNL}\nGTH values for: {', '.join(list(self.GTH))}"
51def init_gth_loc(scf, **kwargs):
52 """Initialize parameters to calculate local contributions of GTH pseudopotentials.
54 Reference: Phys. Rev. B 54, 1703.
56 Args:
57 scf: SCF object.
59 Keyword Args:
60 **kwargs: Throwaway arguments.
62 Returns:
63 Local GTH potential contribution.
64 """
65 atoms = scf.atoms
66 species = set(atoms.atom)
67 omega = 1 # Normally this would be det(atoms.a), but Arias notation is off by this factor
69 Vloc = np.zeros_like(atoms.G2)
70 for isp in species:
71 psp = scf.gth[isp]
72 rloc = psp["rloc"]
73 Zion = psp["Zion"]
74 c1 = psp["cloc"][0]
75 c2 = psp["cloc"][1]
76 c3 = psp["cloc"][2]
77 c4 = psp["cloc"][3]
79 rlocG2 = atoms.G2 * rloc**2
80 rlocG22 = rlocG2**2
81 exprlocG2 = np.exp(-0.5 * rlocG2)
82 # Ignore the division by zero for the first elements
83 # One could do some proper indexing with [1:] but indexing is slow
84 with np.errstate(divide="ignore", invalid="ignore"):
85 Vsp = -4 * np.pi * Zion / omega * exprlocG2 / atoms.G2 + np.sqrt(
86 (2 * np.pi) ** 3
87 ) * rloc**3 / omega * exprlocG2 * (
88 c1
89 + c2 * (3 - rlocG2)
90 + c3 * (15 - 10 * rlocG2 + rlocG22)
91 + c4 * (105 - 105 * rlocG2 + 21 * rlocG22 - rlocG2**3)
92 )
93 # Special case for G=(0,0,0), same as in QE
94 Vsp[0] = 2 * np.pi * Zion * rloc**2 + (2 * np.pi) ** 1.5 * rloc**3 * (
95 c1 + 3 * c2 + 15 * c3 + 105 * c4
96 )
98 # Sum up the structure factor for every species
99 Sf = np.zeros(len(atoms.Sf[0]), dtype=complex)
100 for ia in range(atoms.Natoms):
101 if atoms.atom[ia] == isp:
102 Sf += atoms.Sf[ia]
103 Vloc += np.real(atoms.J(Vsp * Sf))
104 return Vloc
107def init_gth_nonloc(atoms, gth):
108 """Initialize parameters to calculate non-local contributions of GTH pseudopotentials.
110 Adapted from https://github.com/f-fathurrahman/PWDFT.jl/blob/master/src/PsPotNL.jl
112 Reference: Phys. Rev. B 54, 1703.
114 Args:
115 atoms: Atoms object.
116 gth: GTH object.
118 Returns:
119 NbetaNL, prj2beta, and betaNL.
120 """
121 prj2beta = np.empty((3, atoms.Natoms, 4, 7), dtype=int)
122 prj2beta[:] = -1 # Set to an invalid index
124 NbetaNL = 0
125 for ia in range(atoms.Natoms):
126 psp = gth[atoms.atom[ia]]
127 for l in range(psp["lmax"]):
128 for m in range(-l, l + 1):
129 for iprj in range(psp["Nproj_l"][l]):
130 NbetaNL += 1
131 prj2beta[iprj, ia, l, m + psp["lmax"] - 1] = NbetaNL
133 betaNL = []
134 for ik in range(atoms.kpts.Nk):
135 betaNL_ik = np.empty((len(atoms.Gk2c[ik]), NbetaNL), dtype=complex)
136 ibeta = 0
137 gk = atoms.G[atoms.active[ik]] + atoms.kpts.k[ik]
138 Gkm = np.sqrt(atoms.Gk2c[ik])
139 for ia in range(atoms.Natoms):
140 # It is important to transform the structure factor to make both notations compatible
141 Sf = atoms.Idag(atoms.J(atoms.Sf[ia], ik), ik)
142 psp = gth[atoms.atom[ia]]
143 for l in range(psp["lmax"]):
144 for m in range(-l, l + 1):
145 for iprj in range(psp["Nproj_l"][l]):
146 betaNL_ik[:, ibeta] = (
147 (-1j) ** l
148 * Ylm_real(l, m, gk)
149 * eval_proj_G(psp, l, iprj + 1, Gkm, atoms.Omega)
150 * Sf
151 )
152 ibeta += 1
153 betaNL.append(betaNL_ik)
154 return NbetaNL, prj2beta, betaNL
157def calc_Vnonloc(scf, ik, spin, W):
158 """Calculate the non-local pseudopotential, applied on the basis functions W.
160 Adapted from https://github.com/f-fathurrahman/PWDFT.jl/blob/master/src/op_V_Ps_nloc.jl
162 Reference: Phys. Rev. B 54, 1703.
164 Args:
165 scf: SCF object.
166 ik: k-point index.
167 spin: Spin variable to track whether to do the calculation for spin up or down.
168 W: Expansion coefficients of unconstrained wave functions in reciprocal space.
170 Returns:
171 Non-local GTH potential contribution.
172 """
173 atoms = scf.atoms
175 Vpsi = np.zeros_like(W[ik][spin], dtype=complex)
176 if scf.pot != "gth" or scf.gth.NbetaNL == 0: # Only calculate the non-local part if necessary
177 return Vpsi
179 betaNL_psi = (W[ik][spin].conj().T @ scf.gth.betaNL[ik]).conj()
180 for ia in range(atoms.Natoms):
181 psp = scf.gth[atoms.atom[ia]]
182 for l in range(psp["lmax"]):
183 for m in range(-l, l + 1):
184 for iprj in range(psp["Nproj_l"][l]):
185 ibeta = scf.gth.prj2beta[iprj, ia, l, m + psp["lmax"] - 1] - 1
186 for jprj in range(psp["Nproj_l"][l]):
187 jbeta = scf.gth.prj2beta[jprj, ia, l, m + psp["lmax"] - 1] - 1
188 hij = psp["h"][l, iprj, jprj]
189 Vpsi += hij * betaNL_psi[:, jbeta] * scf.gth.betaNL[ik][:, ibeta, None]
190 # We have to multiply with the cell volume, because of different orthogonalization methods
191 return atoms.O(Vpsi)
194def eval_proj_G(psp, l, iprj, Gm, Omega):
195 """Evaluate GTH projector functions in G-space.
197 Adapted from https://github.com/f-fathurrahman/PWDFT.jl/blob/master/src/PsPot_GTH.jl
199 Reference: Phys. Rev. B 54, 1703.
201 Args:
202 psp: GTH parameters.
203 l: Angular momentum number.
204 iprj: Nproj_l index.
205 Gm: Magnitude of G-vectors.
206 Omega: Unit cell volume.
208 Returns:
209 GTH projector.
210 """
211 rrl = psp["rp"][l]
212 Gr2 = (Gm * rrl) ** 2
214 prefactor = 4 * np.pi ** (5 / 4) * np.sqrt(2 ** (l + 1) * rrl ** (2 * l + 3) / Omega)
215 Vprj = prefactor * np.exp(-0.5 * Gr2)
217 if l == 0: # s-channel
218 if iprj == 1:
219 return Vprj
220 if iprj == 2:
221 return 2 / np.sqrt(15) * (3 - Gr2) * Vprj
222 if iprj == 3:
223 return (4 / 3) / np.sqrt(105) * (15 - 10 * Gr2 + Gr2**2) * Vprj
224 elif l == 1: # p-channel
225 if iprj == 1:
226 return 1 / np.sqrt(3) * Gm * Vprj
227 if iprj == 2:
228 return 2 / np.sqrt(105) * Gm * (5 - Gr2) * Vprj
229 if iprj == 3:
230 return (4 / 3) / np.sqrt(1155) * Gm * (35 - 14 * Gr2 + Gr2**2) * Vprj
231 elif l == 2: # d-channel
232 if iprj == 1:
233 return 1 / np.sqrt(15) * Gm**2 * Vprj
234 if iprj == 2:
235 return (2 / 3) / np.sqrt(105) * Gm**2 * (7 - Gr2) * Vprj
236 elif l == 3: # f-channel
237 # Only one projector
238 return 1 / np.sqrt(105) * Gm**3 * Vprj
240 msg = f"No projector found for l={l}."
241 raise ValueError(msg)