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