Coverage for gpaw/new/lcao/forces.py: 100%
90 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-09 00:21 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-09 00:21 +0000
1from __future__ import annotations
3from typing import Dict, Tuple
5import numpy as np
6from gpaw.core.uniform_grid import UGArray
7from gpaw.new import zips
8from gpaw.new.lcao.wave_functions import LCAOWaveFunctions
9from gpaw.new.potential import Potential
10from gpaw.typing import Array2D, Array3D
11from gpaw.utilities.blas import mmm
13Derivatives = Tuple[Array3D,
14 Array3D,
15 Dict[int, Array3D]]
18class TCIDerivatives:
19 def __init__(self, manytci, atomdist, nao: int):
20 self.manytci = manytci
21 self.atomdist = atomdist
22 self.nao = nao
24 self._derivatives_q: dict[int, Derivatives] = {}
26 def calculate_derivatives(self,
27 q: int) -> Derivatives:
28 if not self._derivatives_q:
29 dThetadR_qvMM, dTdR_qvMM = self.manytci.O_qMM_T_qMM(
30 self.atomdist.comm,
31 0, self.nao,
32 False, derivative=True)
34 self.atomdist.comm.sum(dThetadR_qvMM)
35 self.atomdist.comm.sum(dTdR_qvMM)
37 dPdR_aqvMi = self.manytci.P_aqMi(
38 self.atomdist.indices,
39 derivative=True)
41 dPdR_qavMi = [{a: dPdR_qvMi[q]
42 for a, dPdR_qvMi in dPdR_aqvMi.items()}
43 for q in range(len(dThetadR_qvMM))]
45 self._derivatives_q = {
46 q: (dThetadR_vMM, dTdR_vMM, dPdR_avMi)
47 for q, (dThetadR_vMM, dTdR_vMM, dPdR_avMi)
48 in enumerate(zips(dThetadR_qvMM, dTdR_qvMM, dPdR_qavMi))}
50 return self._derivatives_q[q]
53def add_force_contributions(wfs: LCAOWaveFunctions,
54 potential: Potential,
55 F_av: Array2D) -> None:
56 (dThetadR_vMM,
57 dTdR_vMM,
58 dPdR_avMi) = wfs.tci_derivatives.calculate_derivatives(wfs.q)
60 indices = []
61 M1 = 0
62 for a, sphere in enumerate(wfs.basis.sphere_a):
63 M2 = M1 + sphere.Mmax
64 indices.append((a, M1, M2))
65 M1 = M2
67 setups = wfs.setups
69 my_row_range = wfs.T_MM.dist.my_row_range()
70 m1, m2 = my_row_range
71 rhoT_MM = wfs.calculate_density_matrix(transposed=True)
72 erhoT_MM = wfs.calculate_density_matrix(transposed=True, eigs=True)
73 add_kinetic_term(rhoT_MM, dTdR_vMM[:, m1:m2], F_av,
74 indices, wfs.atomdist.indices, my_row_range)
75 add_pot_term(potential.vt_sR[wfs.spin], wfs.basis, wfs.q, rhoT_MM, F_av)
76 add_den_mat_term(erhoT_MM, dThetadR_vMM[:, m1:m2], F_av, indices,
77 wfs.atomdist.indices, my_row_range)
79 for b in wfs.atomdist.indices:
80 add_den_mat_paw_term(b,
81 setups[b].dO_ii,
82 wfs.P_aMi[b],
83 dPdR_avMi[b],
84 erhoT_MM,
85 indices,
86 F_av,
87 my_row_range)
89 add_atomic_density_term(b,
90 potential.dH_asii[b][wfs.spin],
91 wfs.P_aMi[b],
92 dPdR_avMi[b],
93 rhoT_MM,
94 indices,
95 F_av,
96 my_row_range)
99def add_kinetic_term(rhoT_MM, dTdR_vMM, F_av, indices, mya, my_row_range):
100 """Calculate Kinetic energy term in LCAO
102 :::
104 dT
105 _a --- -- μν
106 F += 2 Re > > ---- ρ
107 --- -- _ νμ
108 μ=a ν dR
109 μν
110 """
112 for a, M1, M2 in indices:
113 if a in mya and M2 >= my_row_range[0] and M1 <= my_row_range[1]:
114 m1 = max(M1, my_row_range[0]) - my_row_range[0]
115 m2 = min(M2, my_row_range[1]) - my_row_range[0]
116 F_av[a, :] += 2 * np.einsum('vmM, mM -> v',
117 dTdR_vMM[:, m1:m2],
118 rhoT_MM[m1:m2]).real
121def add_pot_term(vt_R: UGArray,
122 basis,
123 q: int,
124 rhoT_MM,
125 F_av) -> None:
126 """Calculate potential term"""
127 # Potential contribution
128 #
129 # ----- / d Phi (r)
130 # a \ | mu ~
131 # F += -2 Re ) | ---------- v (r) Phi (r) dr rho
132 # / | d R nu nu mu
133 # ----- / a
134 # mu in a; nu
135 #
136 F_av += basis.calculate_force_contribution(vt_R.data,
137 rhoT_MM,
138 q)
141def add_den_mat_term(erhoT_MM, dThetadR_vMM, F_av, indices, mya, my_row_range):
142 """Calculate density matrix term in LCAO"""
143 # Density matrix contribution due to basis overlap
144 #
145 # ----- d Theta
146 # a \ mu nu
147 # F += -2 Re ) ------------ E
148 # / d R nu mu
149 # ----- mu nu
150 # mu in a; nu
151 #
152 for a, M1, M2 in indices:
153 if a in mya and M2 >= my_row_range[0] and M1 <= my_row_range[1]:
154 m1 = max(M1, my_row_range[0]) - my_row_range[0]
155 m2 = min(M2, my_row_range[1]) - my_row_range[0]
156 F_av[a, :] -= 2 * np.einsum('vmM, mM -> v',
157 dThetadR_vMM[:, m1:m2],
158 erhoT_MM[m1:m2]).real
161def add_den_mat_paw_term(b, dO_ii, P_Mi, dPdR_vMi, erhoT_MM,
162 indices, F_av, my_row_range):
163 """Calcualte PAW correction"""
164 # TO DO: split this function into
165 # _get_den_mat_paw_term (which calculate Frho_av) and
166 # get_paw_correction (which calculate ZE_MM)
167 # Density matrix contribution from PAW correction
168 #
169 # ----- -----
170 # a \ a \ b
171 # F += 2 Re ) Z E - 2 Re ) Z E
172 # / mu nu nu mu / mu nu nu mu
173 # ----- -----
174 # mu nu b; mu in a; nu
175 #
176 # with
177 # b*
178 # ----- dP
179 # b \ i mu b b
180 # Z = ) -------- dS P
181 # mu nu / dR ij j nu
182 # ----- b mu
183 # ij
184 #
185 m1, m2 = my_row_range
186 dtype = P_Mi.dtype
187 Z_MM = np.zeros((m2 - m1, len(P_Mi)), dtype)
188 dOP_iM = np.zeros((len(dO_ii), len(P_Mi)), dtype)
189 mmm(1.0, dO_ii.astype(dtype), 'N', P_Mi, 'C', 0.0, dOP_iM)
190 for v in range(3):
191 mmm(1.0,
192 dPdR_vMi[v, m1:m2], 'N',
193 dOP_iM, 'N',
194 0.0, Z_MM)
195 ZE_M = np.einsum('MN, MN -> M', Z_MM, erhoT_MM).real
196 for a, M1, M2 in indices:
197 if M2 >= my_row_range[0] and M1 <= my_row_range[1]:
198 am1 = max(M1, my_row_range[0]) - my_row_range[0]
199 am2 = min(M2, my_row_range[1]) - my_row_range[0]
200 dE = 2 * ZE_M[am1:am2].sum()
201 F_av[a, v] -= dE # the "b; mu in a; nu" term
202 F_av[b, v] += dE # the "mu nu" term
205def add_atomic_density_term(b, dH_ii, P_Mi, dPdR_vMi, rhoT_MM,
206 indices, F_av, my_row_range):
207 # Atomic density contribution
208 # ----- -----
209 # a \ a \ b
210 # F += -2 Re ) A rho + 2 Re ) A rho
211 # / mu nu nu mu / mu nu nu mu
212 # ----- -----
213 # mu nu b; mu in a; nu
214 #
215 # b*
216 # ----- d P
217 # b \ i mu b b
218 # A = ) ------- dH P
219 # mu nu / d R ij j nu
220 # ----- b mu
221 # ij
222 #
223 m1, m2 = my_row_range
224 dtype = P_Mi.dtype
225 A_MM = np.zeros((m2 - m1, len(P_Mi)), dtype)
226 dHP_iM = np.zeros((len(dH_ii), len(P_Mi)), dtype)
227 mmm(1.0, dH_ii.astype(dtype), 'N', P_Mi, 'C', 0.0, dHP_iM)
228 for v in range(3):
229 mmm(1.0,
230 dPdR_vMi[v, m1:m2], 'N',
231 dHP_iM, 'N',
232 0.0, A_MM)
233 AR_M = np.einsum('MN, MN -> M', A_MM, rhoT_MM).real
234 for a, M1, M2 in indices:
235 if M2 >= my_row_range[0] and M1 <= my_row_range[1]:
236 am1 = max(M1, my_row_range[0]) - my_row_range[0]
237 am2 = min(M2, my_row_range[1]) - my_row_range[0]
238 dE = 2 * AR_M[am1:am2].sum()
239 F_av[a, v] += dE # the "b; mu in a; nu" term
240 F_av[b, v] -= dE # the "mu nu" term