Coverage for gpaw/new/pw/paw_poisson.py: 89%

102 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-07-20 00:19 +0000

1"""PAW Poisson-solvers. 

2 

3Adds smooth compensation charges to the pseudo density. 

4""" 

5from __future__ import annotations 

6 

7import numpy as np 

8from gpaw.atom.radialgd import EquidistantRadialGridDescriptor as RGD 

9from gpaw.atom.shapefunc import shape_functions 

10from gpaw.core import PWArray, PWDesc 

11from gpaw.core.atom_arrays import AtomArrays, AtomDistribution 

12from gpaw.gpu import cupy as cp 

13from gpaw.setup import Setups 

14from gpaw.spline import Spline 

15 

16 

17class PAWPoissonSolver: 

18 def __init__(self, 

19 poisson_solver): 

20 self.poisson_solver = poisson_solver 

21 

22 def dipole_layer_correction(self) -> None: 

23 return self.poisson_solver.dipole_layer_correction() 

24 

25 def move(self, 

26 relpos_ac: np.ndarray, 

27 atomdist: AtomDistribution) -> None: 

28 raise NotImplementedError 

29 

30 def solve(self, 

31 nt_g: PWArray, 

32 Q_aL: AtomArrays, 

33 vt0_g: PWArray, 

34 vHt_h: PWArray | None = None) -> tuple[float, 

35 PWArray, 

36 AtomArrays]: 

37 raise NotImplementedError 

38 

39 

40class SlowPAWPoissonSolver(PAWPoissonSolver): 

41 """Solve Poisson-equation on very fine grid.""" 

42 def __init__(self, 

43 pwg: PWDesc, 

44 # cutoff_a, 

45 setups: Setups, 

46 poisson_solver, 

47 relpos_ac: np.ndarray, 

48 atomdist: AtomDistribution, 

49 xp=np): 

50 super().__init__(poisson_solver) 

51 self.xp = xp 

52 self.pwg = pwg 

53 self.pwg0 = pwg.new(comm=None) # not distributed 

54 self.pwh = poisson_solver.pw 

55 self.ghat_aLh = setups.create_compensation_charges( 

56 self.pwh, relpos_ac, atomdist, xp) 

57 self.h_g, self.g_r = self.pwh.map_indices(self.pwg0) 

58 if xp is cp: 

59 self.h_g = cp.asarray(self.h_g) 

60 self.g_r = [cp.asarray(g) for g in self.g_r] 

61 

62 def move(self, 

63 relpos_ac: np.ndarray, 

64 atomdist: AtomDistribution) -> None: 

65 self.ghat_aLh.move(relpos_ac, atomdist) 

66 

67 def solve(self, 

68 nt0_g: PWArray, 

69 Q_aL: AtomArrays, 

70 vt0_g: PWArray, 

71 vHt_h: PWArray | None = None) -> tuple[float, 

72 PWArray, 

73 AtomArrays]: 

74 charge_h = self.pwh.zeros(xp=self.xp) 

75 self.ghat_aLh.add_to(charge_h, Q_aL) 

76 pwg = self.pwg 

77 

78 if pwg.comm.rank == 0: 

79 for rank, g in enumerate(self.g_r): 

80 if rank == 0: 

81 charge_h.data[self.h_g] += nt0_g.data[g] 

82 else: 

83 pwg.comm.send(nt0_g.data[g], rank) 

84 else: 

85 data = self.xp.empty(len(self.h_g), complex) 

86 pwg.comm.receive(data, 0) 

87 charge_h.data[self.h_g] += data 

88 

89 if vHt_h is None: 

90 vHt_h = self.pwh.zeros(xp=self.xp) 

91 

92 e_coulomb = self.poisson_solver.solve(vHt_h, charge_h) 

93 

94 if pwg.comm.rank == 0: 

95 for rank, g in enumerate(self.g_r): 

96 if rank == 0: 

97 vt0_g.data[g] += vHt_h.data[self.h_g] 

98 else: 

99 data = self.xp.empty(len(g), complex) 

100 pwg.comm.receive(data, rank) 

101 vt0_g.data[g] += data 

102 else: 

103 pwg.comm.send(vHt_h.data[self.h_g], 0) 

104 

105 V_aL = self.ghat_aLh.integrate(vHt_h) 

106 

107 return e_coulomb, vHt_h, V_aL 

108 

109 def force_contribution(self, Q_aL, vHt_h, nt_g): 

110 force_av = self.xp.zeros((len(Q_aL), 3)) 

111 

112 F_avL = self.ghat_aLh.derivative(vHt_h) 

113 for a, dF_vL in F_avL.items(): 

114 force_av[a] += dF_vL @ Q_aL[a] 

115 

116 return force_av 

117 

118 def stress_contribution(self, vHt_h, Q_aL): 

119 return self.ghat_aLh.stress_contribution(vHt_h, Q_aL) 

120 

121 

122class SimplePAWPoissonSolver(PAWPoissonSolver): 

123 """For testing only!""" 

124 def __init__(self, 

125 pwg: PWDesc, 

126 cutoff_a, 

127 poisson_solver, 

128 relpos_ac: np.ndarray, 

129 atomdist: AtomDistribution, 

130 xp=np): 

131 self.xp = xp 

132 self.pwg = pwg 

133 self.pwg0 = pwg.new(comm=None) # not distributed 

134 self.poisson_solver = poisson_solver 

135 d = 0.005 

136 rgd = RGD(d, int(10.0 / d)) 

137 cache: dict[float, list[Spline]] = {} 

138 ghat_al = [] 

139 for rc in cutoff_a: 

140 if rc in cache: 

141 ghat_l = cache[rc] 

142 else: 

143 g_lg = shape_functions(rgd, 'gauss', rc, lmax=2) 

144 ghat_l = [rgd.spline(g_g, l=l) for l, g_g in enumerate(g_lg)] 

145 cache[rc] = ghat_l 

146 ghat_al.append(ghat_l) 

147 

148 self.ghat_aLg = pwg.atom_centered_functions( 

149 ghat_al, relpos_ac, atomdist=atomdist, xp=xp) 

150 

151 def solve(self, 

152 nt0_g: PWArray, 

153 Q_aL: AtomArrays, 

154 vt0_g: PWArray, 

155 vHt_g: PWArray | None = None): 

156 

157 charge_g = self.pwg.empty(xp=self.xp) 

158 charge_g.scatter_from(nt0_g) 

159 self.ghat_aLg.add_to(charge_g, Q_aL) 

160 pwg = self.pwg 

161 if vHt_g is None: 

162 vHt_g = pwg.empty(xp=self.xp) 

163 e_coulomb = self.poisson_solver.solve(vHt_g, charge_g) 

164 vHt0_g = vHt_g.gather() 

165 if pwg.comm.rank == 0: 

166 vt0_g.data += vHt0_g.data 

167 V_aL = self.ghat_aLg.integrate(vHt_g) 

168 return e_coulomb, vHt_g, V_aL 

169 

170 def force_contribution(self, Q_aL, vHt_g, nt_g): 

171 force_av = self.xp.zeros((len(Q_aL), 3)) 

172 

173 F_avL = self.ghat_aLg.derivative(vHt_g) 

174 for a, dF_vL in F_avL.items(): 

175 force_av[a] += dF_vL @ Q_aL[a] 

176 return force_av