Coverage for gpaw/new/fd/pot_calc.py: 87%
101 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-14 00:18 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-14 00:18 +0000
1from __future__ import annotations
3from math import pi
5import numpy as np
7from gpaw.core import UGDesc
8from gpaw.new import spinsum, trace, zips
9from gpaw.new.pot_calc import PotentialCalculator
12class FDPotentialCalculator(PotentialCalculator):
13 def __init__(self,
14 wf_grid: UGDesc,
15 fine_grid: UGDesc,
16 setups,
17 xc,
18 poisson_solver,
19 *,
20 relpos_ac,
21 atomdist,
22 interpolation_stencil_range=3,
23 environment=None,
24 extensions=None,
25 xp=np):
26 self.fine_grid = fine_grid
27 self.grid = wf_grid
29 self.vbar_ar = setups.create_local_potentials(fine_grid, relpos_ac,
30 atomdist, xp=xp)
31 self.ghat_aLr = setups.create_compensation_charges(fine_grid,
32 relpos_ac,
33 atomdist,
34 xp=xp)
36 self.vbar_r = fine_grid.empty(xp=xp)
37 self.vbar_ar.to_uniform_grid(out=self.vbar_r)
39 n = interpolation_stencil_range
40 self.interpolation_stencil_range = n
41 self._interpolate = wf_grid.transformer(fine_grid, n, xp=xp)
42 self._restrict = fine_grid.transformer(wf_grid, n, xp=xp)
44 self.xp = xp
46 super().__init__(xc, poisson_solver, setups,
47 relpos_ac=relpos_ac,
48 environment=environment,
49 extensions=extensions)
51 def __str__(self):
52 txt = super().__str__()
53 degree = self.interpolation_stencil_range * 2 - 1
54 name = ['linear', 'cubic', 'quintic', 'heptic'][degree // 2]
55 txt += (f'interpolation: tri-{name}' +
56 f' # {degree}. degree polynomial\n')
57 return txt
59 def interpolate(self, a_xR, a_xr=None):
60 return self._interpolate(a_xR, a_xr)
62 def restrict(self, a_xr, a_xR=None):
63 return self._restrict(a_xr, a_xR)
65 def calculate_non_selfconsistent_exc(self, xc, density):
66 nt_sr, _, _ = self._interpolate_density(density.nt_sR)
67 if density.taut_sR is not None:
68 taut_sr = self.interpolate(density.taut_sR)
69 else:
70 taut_sr = None
71 e_xc, _, _ = xc.calculate(nt_sr, taut_sr)
72 return e_xc
74 def _interpolate_density(self, nt_sR):
75 nt_sr = self.interpolate(nt_sR)
76 if not nt_sR.desc.pbc_c.all():
77 Nt1_s = nt_sR.integrate()
78 Nt2_s = nt_sr.integrate()
79 for Nt1, Nt2, nt_r in zips(Nt1_s, Nt2_s, nt_sr):
80 if float(Nt2) > 1e-14:
81 nt_r.data *= Nt1 / Nt2
82 return nt_sr, None, None
84 @trace
85 def calculate_pseudo_potential(self, density, ibzwfs, vHt_r):
86 nt_sr, _, _ = self._interpolate_density(density.nt_sR)
87 grid2 = nt_sr.desc
89 if density.taut_sR is not None:
90 taut_sr = self.interpolate(density.taut_sR)
91 else:
92 taut_sr = None
94 e_xc, vxct_sr, dedtaut_sr = self.xc.calculate(nt_sr, taut_sr)
96 charge_r = grid2.empty(xp=self.xp)
97 charge_r.data[:] = nt_sr.data[:density.ndensities].sum(axis=0)
98 nt_r = charge_r.copy()
99 e_zero = self.vbar_r.integrate(nt_r)
101 ccc_aL = density.calculate_compensation_charge_coefficients()
103 # Normalize: (LCAO basis functions may extend outside box)
104 comp_charge = (4 * pi)**0.5 * sum(float(ccc_L[0])
105 for ccc_L in ccc_aL.values())
106 comp_charge = ccc_aL.layout.atomdist.comm.sum_scalar(comp_charge)
107 pseudo_charge = charge_r.integrate()
108 if abs(pseudo_charge) > 1e-10:
109 pc = -comp_charge - density.charge + self.environment.charge
110 charge_r.data *= pc / pseudo_charge
112 self.environment.update1(charge_r)
114 self.ghat_aLr.add_to(charge_r, ccc_aL)
116 if vHt_r is None:
117 vHt_r = grid2.zeros(xp=self.xp)
118 self.poisson_solver.solve(vHt_r, charge_r)
119 e_coulomb = 0.5 * vHt_r.integrate(charge_r)
121 vt_sr = vxct_sr
122 vt_sr.data += vHt_r.data + self.vbar_r.data
124 e_env = self.environment.update2(nt_r, vHt_r, vt_sr)
126 vt_sR = self.restrict(vt_sr)
128 e_external = e_env
130 V_aL = self.ghat_aLr.integrate(vHt_r)
132 return ({'coulomb': e_coulomb,
133 'zero': e_zero,
134 'xc': e_xc,
135 'external': e_external},
136 vt_sR,
137 dedtaut_sr,
138 vHt_r,
139 V_aL,
140 np.nan)
142 def move(self, relpos_ac, atomdist):
143 super().move(relpos_ac, atomdist)
144 self.ghat_aLr.move(relpos_ac, atomdist)
145 self.vbar_ar.move(relpos_ac, atomdist)
146 self.vbar_ar.to_uniform_grid(out=self.vbar_r)
148 def force_contributions(self, Q_aL, density, potential):
149 nt_R = spinsum(density.nt_sR)
150 vt_R = spinsum(potential.vt_sR, mean=True)
151 dedtaut_sR = potential.dedtaut_sR
152 if dedtaut_sR is not None:
153 dedtaut_R = spinsum(dedtaut_sR, mean=True)
154 Ftauct_av = density.tauct_aX.derivative(dedtaut_R)
155 else:
156 Ftauct_av = None
158 nt_r = self.interpolate(nt_R)
159 if not nt_r.desc.pbc_c.all():
160 scale = nt_R.integrate() / nt_r.integrate()
161 nt_r.data *= scale
163 F_avL = self.ghat_aLr.derivative(potential.vHt_x)
164 force_av = np.zeros((len(Q_aL), 3))
165 for a, dF_vL in F_avL.items():
166 force_av[a] += dF_vL @ Q_aL[a]
168 force_av += self.environment.forces(nt_r, potential.vHt_x)
170 return (force_av,
171 density.nct_aX.derivative(vt_R),
172 Ftauct_av,
173 self.vbar_ar.derivative(nt_r),
174 self.extensions_force_av)