Coverage for gpaw/electrostatic_potential.py: 28%
79 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-19 00:19 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-19 00:19 +0000
1from __future__ import annotations
3from math import pi, sqrt
4from typing import TYPE_CHECKING
6import numpy as np
7from ase.units import Bohr, Ha
9from gpaw.atom.shapefunc import shape_functions
10from gpaw.core.arrays import DistributedArrays
11from gpaw.core.atom_arrays import AtomArrays
12from gpaw.core.uniform_grid import UGArray
13from gpaw.setup import Setups
14from gpaw.typing import Array1D, ArrayLike2D
15from gpaw.utilities import pack_density
17if TYPE_CHECKING:
18 from gpaw.new.calculation import DFTCalculation
21class ElectrostaticPotential:
22 def __init__(self,
23 vHt_x: DistributedArrays,
24 W_aL: AtomArrays,
25 Q_aL: AtomArrays,
26 D_asii: AtomArrays,
27 relpos_ac: ArrayLike2D,
28 setups: Setups):
29 self.vHt_x = vHt_x
30 self.W_aL = W_aL
31 self.Q_aL = Q_aL
32 self.D_asii = D_asii
33 self.relpos_ac = relpos_ac
34 self.setups = setups
36 # Caching of interpolated pseudo-potential:
37 self._grid_spacing = -1.0
38 self._vHt_R: UGArray | None = None
40 @classmethod
41 def from_calculation(cls, calculation: DFTCalculation):
42 density = calculation.density
43 potential, _, W_aL = calculation.pot_calc.calculate(density)
44 Q_aL = density.calculate_compensation_charge_coefficients()
45 return cls(potential.vHt_x,
46 W_aL,
47 Q_aL,
48 density.D_asii,
49 calculation.relpos_ac,
50 calculation.setups)
52 def atomic_potentials(self) -> Array1D:
53 W_aL = self.W_aL.gather(broadcast=True)
54 return W_aL.data[::9] * (Ha / (4 * pi)**0.5)
56 def atomic_corrections(self):
57 """Calculate PAW correction to average electrostatic potential."""
58 dEH_a = np.zeros(len(self.setups))
59 for a, D_sii in self.D_asii.items():
60 setup = self.setups[a]
61 D_p = pack_density(D_sii.sum(0))
62 dEH_a[a] = setup.dEH0 + setup.dEH_p @ D_p
63 self.D_asii.layout.atomdist.comm.sum(dEH_a)
64 return dEH_a * Ha * Bohr**3
66 def pseudo_potential(self,
67 grid_spacing: float = 0.05, # Ang
68 ) -> UGArray:
69 return self._pseudo_potential(grid_spacing / Bohr).scaled(Bohr, Ha)
71 def _pseudo_potential(self,
72 grid_spacing: float, # Bohr
73 ) -> UGArray:
74 if grid_spacing == self._grid_spacing:
75 assert self._vHt_R is not None
76 return self._vHt_R
78 vHt_x = self.vHt_x
79 if isinstance(self.vHt_x, UGArray):
80 vHt_x = self.vHt_x.to_pbc_grid()
81 grid = vHt_x.desc.uniform_grid_with_grid_spacing(grid_spacing / Bohr)
82 self._vHt_R = vHt_x.interpolate(grid=grid)
83 self._grid_spacing = grid_spacing
84 return self._vHt_R
86 def all_electron_potential(self,
87 grid_spacing: float = 0.05, # Ang
88 rcgauss: float = 0.02, # Ang
89 npoints: int = 200) -> UGArray:
90 """Interpolate electrostatic potential.
92 Return value in eV.
94 ae: bool
95 Add PAW correction to get the all-electron potential.
96 rcgauss: float
97 Width of gaussian (in Angstrom) used to represent the nuclear
98 charge.
99 """
100 vHt_R = self._pseudo_potential(grid_spacing / Bohr).copy()
102 dv_a = []
103 for a, D_sii in self.D_asii.items():
104 setup = self.setups[a]
105 c = setup.xc_correction
106 rgd = c.rgd
107 params = setup.data.shape_function.copy()
108 params['lmax'] = 0
109 ghat_g = shape_functions(rgd, **params)[0]
110 Z_g = shape_functions(rgd, 'gauss', rcgauss, lmax=0)[0] * setup.Z
111 D_p = pack_density(D_sii.sum(axis=0))
112 D_q = D_p @ c.B_pqL[:, :, 0]
113 dn_g = D_q @ (c.n_qg - c.nt_qg) * sqrt(4 * pi)
114 dn_g += 4 * pi * (c.nc_g - c.nct_g)
115 dn_g -= Z_g
116 dn_g -= self.Q_aL[a][0] * ghat_g * sqrt(4 * pi)
117 dv_g = rgd.poisson(dn_g) / sqrt(4 * pi)
118 dv_g[1:] /= rgd.r_g[1:]
119 dv_g[0] = dv_g[1]
120 dv_g[-1] = 0.0
121 dv_a.append([rgd.spline(dv_g, points=npoints)])
123 dv_aR = vHt_R.desc.atom_centered_functions(dv_a, self.relpos_ac)
124 dv_aR.add_to(vHt_R)
125 return vHt_R.scaled(Bohr, Ha)