Coverage for eminus/potentials.py: 94.12%
68 statements
« prev ^ index » next coverage.py v7.7.0, created at 2025-03-18 09:20 +0000
« prev ^ index » next coverage.py v7.7.0, created at 2025-03-18 09:20 +0000
1# SPDX-FileCopyrightText: 2021 The eminus developers
2# SPDX-License-Identifier: Apache-2.0
3"""Collection of miscellaneous potentials."""
5import inspect
7import numpy as np
8from scipy.linalg import norm
10from .gth import init_gth_loc
11from .logger import log
14def get_pot_defaults(pot):
15 """Get the default parameters and values for a given potential.
17 Args:
18 pot: Potential name.
20 Returns:
21 Default parameters and values.
22 """
23 if pot in IMPLEMENTED:
24 sig = inspect.signature(IMPLEMENTED[pot])
25 return {
26 param.name: param.default
27 for param in sig.parameters.values()
28 if param.default is not inspect.Parameter.empty
29 }
30 return {}
33def harmonic(scf, freq=2, **kwargs):
34 """Harmonic potential.
36 Can be used for quantum dot calculations.
38 Args:
39 scf: SCF object.
41 Keyword Args:
42 freq: Harmonic oscillator frequency.
43 **kwargs: Throwaway arguments.
45 Returns:
46 Harmonic potential in real-space.
47 """
48 atoms = scf.atoms
49 dr = norm(atoms.r - np.sum(atoms.a, axis=1) / 2, axis=1)
50 Vharm = 0.5 * freq**2 * dr**2
51 return atoms.Jdag(atoms.O(atoms.J(Vharm)))
54def coulomb(scf, **kwargs):
55 """All-electron Coulomb potential.
57 Reference: Bull. Lebedev Phys. Inst. 42, 329.
59 Args:
60 scf: SCF object.
62 Keyword Args:
63 **kwargs: Throwaway arguments.
65 Returns:
66 Coulomb potential in real-space.
67 """
68 atoms = scf.atoms
70 Vcoul = np.zeros_like(atoms.G2)
71 for isp in set(atoms.atom):
72 # Sum up the structure factor for every species
73 # Also get the charge, assuming all species have the same charge
74 Sf = np.zeros(len(atoms.Sf[0]), dtype=complex)
75 for ia in range(atoms.Natoms):
76 if atoms.atom[ia] == isp:
77 Sf += atoms.Sf[ia]
78 Z = atoms.Z[ia]
80 # Ignore the division by zero for the first elements
81 # One could do some proper indexing with [1:] but indexing is slow
82 with np.errstate(divide="ignore", invalid="ignore"):
83 Vsp = -4 * np.pi * Z / atoms.G2
84 Vsp[0] = 0
85 Vcoul += np.real(atoms.J(Vsp * Sf))
86 return Vcoul
89def coulomb_lr(scf, alpha=100, **kwargs):
90 """Long-range all-electron Coulomb potential.
92 Reference: J. Comput. Phys. 117, 171.
94 Args:
95 scf: SCF object.
97 Keyword Args:
98 alpha: Convergence parameter.
99 **kwargs: Throwaway arguments.
101 Returns:
102 Long-range Coulomb potential in real-space.
103 """
104 atoms = scf.atoms
106 Vcoul = np.zeros_like(atoms.G2)
107 for isp in set(atoms.atom):
108 # Sum up the structure factor for every species
109 # Also get the charge, assuming all species have the same charge
110 Sf = np.zeros(len(atoms.Sf[0]), dtype=complex)
111 for ia in range(atoms.Natoms):
112 if atoms.atom[ia] == isp:
113 Sf += atoms.Sf[ia]
114 Z = atoms.Z[ia]
116 # Ignore the division by zero for the first elements
117 # One could do some proper indexing with [1:] but indexing is slow
118 with np.errstate(divide="ignore", invalid="ignore"):
119 Vsp = -4 * np.pi * Z * np.exp(-atoms.G2 / (4 * alpha**2)) / atoms.G2
120 Vsp[0] = 0
121 Vcoul += np.real(atoms.J(Vsp * Sf))
122 return Vcoul
125def ge(scf, **kwargs):
126 """Starkloff-Joannopoulos local pseudopotential for germanium.
128 Fourier-transformed by Tomas Arias.
130 Reference: Phys. Rev. B 16, 5212.
132 Args:
133 scf: SCF object.
135 Keyword Args:
136 **kwargs: Throwaway arguments.
138 Returns:
139 Germanium pseudopotential in real-space.
140 """
141 atoms = scf.atoms
142 Z = 4 # This potential should only be used for germanium
143 lamda = 18.5
144 rc = 1.052
145 Gm = np.sqrt(atoms.G2)
147 with np.errstate(divide="ignore", invalid="ignore"):
148 Vps = (
149 -2
150 * np.pi
151 * np.exp(-np.pi * Gm / lamda)
152 * np.cos(rc * Gm)
153 * (Gm / lamda)
154 / (1 - np.exp(-2 * np.pi * Gm / lamda))
155 )
156 for n in range(5):
157 Vps = Vps + (-1) ** n * np.exp(-lamda * rc * n) / (1 + (n * lamda / Gm) ** 2)
158 Vps = Vps * 4 * np.pi * Z / Gm**2 * (1 + np.exp(-lamda * rc)) - 4 * np.pi * Z / Gm**2
160 # Special case for G=(0,0,0)
161 n = np.arange(1, 5)
162 Vps[0] = (
163 4
164 * np.pi
165 * Z
166 * (1 + np.exp(-lamda * rc))
167 * (
168 rc**2 / 2
169 + 1 / lamda**2 * (np.pi**2 / 6 + np.sum((-1) ** n * np.exp(-lamda * rc * n) / n**2))
170 )
171 )
173 Sf = np.sum(atoms.Sf, axis=0)
174 return atoms.J(Vps * Sf)
177def init_pot(scf, pot_params=None):
178 """Handle and initialize potentials.
180 Args:
181 scf: SCF object.
183 Keywords Args:
184 pot_params: Potential parameters.
186 Returns:
187 Potential in real-space.
188 """
189 if pot_params is None:
190 pot_params = {}
191 try:
192 pot = IMPLEMENTED[scf.pot](scf, **pot_params)
193 except KeyError:
194 log.exception(f'No potential found for "{scf.pot}".')
195 raise
196 return pot
199#: Map potential names with their respective implementation.
200IMPLEMENTED = {
201 "harmonic": harmonic,
202 "coulomb": coulomb,
203 "lr": coulomb_lr,
204 "ge": ge,
205 "gth": init_gth_loc,
206}