Coverage for gpaw/pw/hamiltonian.py: 100%
112 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
1import numpy as np
2from gpaw.arraydict import ArrayDict
3from gpaw.density import Density
4from gpaw.external import NoExternalPotential
5from gpaw.hamiltonian import Hamiltonian
6from gpaw.pw.lfc import PWLFC
7from gpaw.pw.poisson import (ChargedReciprocalSpacePoissonSolver,
8 ReciprocalSpacePoissonSolver)
9from gpaw.typing import Array3D
12class ReciprocalSpaceHamiltonian(Hamiltonian):
13 def __init__(self, gd, finegd, pd2, pd3, nspins, collinear,
14 setups, timer, xc, world, xc_redistributor,
15 vext=None,
16 psolver=None, redistributor=None, realpbc_c=None,
17 charge=0.0):
19 assert redistributor is not None # XXX should not be like this
21 if vext is None:
22 vext = NoExternalPotential()
24 Hamiltonian.__init__(self, gd, finegd, nspins, collinear, setups,
25 timer, xc, world, vext=vext,
26 redistributor=redistributor)
28 self.vbar = PWLFC([[setup.vbar] for setup in setups], pd2)
29 self.pd2 = pd2
30 self.pd3 = pd3
31 self.xc_redistributor = xc_redistributor
32 self.charge = charge
34 self.vHt_q = pd3.empty()
36 if psolver is None:
37 psolver = {}
38 elif not isinstance(psolver, dict):
39 psolver = psolver.todict()
41 if psolver.get('name') == 'nointeraction':
42 self.poisson = ReciprocalSpacePoissonSolver(pd3, charge, 0.0)
43 else:
44 if charge == 0.0 or realpbc_c.any():
45 self.poisson = ReciprocalSpacePoissonSolver(pd3, charge)
46 else:
47 self.poisson = ChargedReciprocalSpacePoissonSolver(pd3, charge)
49 if 'dipolelayer' in psolver:
50 direction = psolver['dipolelayer']
51 assert len(psolver) == 1
52 from gpaw.dipole_correction import DipoleCorrection
53 self.poisson = DipoleCorrection(self.poisson, direction)
54 self.poisson.check_direction(gd, realpbc_c)
55 else:
56 assert not psolver
58 self.npoisson = 0
60 self.vbar_Q = None
61 self.vt_Q = None
62 self.estress = None
64 def __str__(self):
65 s = Hamiltonian.__str__(self)
66 if self.charge != 0.0:
67 s += f'Poisson solver:\n {self.poisson}\n'
68 return s
70 @property
71 def xc_gd(self):
72 if self.xc_redistributor is None:
73 return self.finegd
74 return self.xc_redistributor.aux_gd
76 def set_positions(self, spos_ac, atom_partition):
77 Hamiltonian.set_positions(self, spos_ac, atom_partition)
78 self.vbar_Q = self.pd2.zeros()
79 self.vbar.add(self.vbar_Q)
81 def update_pseudo_potential(self, dens):
82 ebar = self.pd2.integrate(self.vbar_Q, dens.nt_Q,
83 global_integral=False)
84 with self.timer('Poisson'):
85 epot = self.poisson.solve(self.vHt_q, dens)
86 epot /= self.finegd.comm.size
88 self.vt_Q = self.vbar_Q.copy()
90 dens.map23.add_to1(self.vt_Q, self.vHt_q)
92 # vt_sG[:] = pd2.ifft(vt_Q)
93 eext = self.vext.update_potential_pw(self, dens)
95 self.timer.start('XC 3D grid')
97 nt_xg = dens.nt_xg
99 # If we have a redistributor, we want to do the
100 # good old distribute-calculate-collect:
101 redist = self.xc_redistributor
102 if redist is not None:
103 nt_xg = redist.distribute(nt_xg)
105 vxct_xg = np.zeros_like(nt_xg)
106 exc = self.xc.calculate(self.xc_gd, nt_xg, vxct_xg)
107 exc /= self.finegd.comm.size
108 if redist is not None:
109 vxct_xg = redist.collect(vxct_xg)
111 for x, (vt_G, vxct_g) in enumerate(zip(self.vt_xG, vxct_xg)):
112 vxc_G, vxc_Q = self.pd3.restrict(vxct_g, self.pd2)
113 if x < self.nspins:
114 vt_G += vxc_G
115 self.vt_Q += vxc_Q / self.nspins
116 else:
117 vt_G += vxc_G
119 self.timer.stop('XC 3D grid')
121 energies = np.array([epot, ebar, eext, exc])
122 self.estress = self.gd.comm.sum_scalar(epot + ebar)
123 return energies
125 def calculate_atomic_hamiltonians(self, density):
126 def getshape(a):
127 return sum(2 * l + 1
128 for l, _ in enumerate(self.setups[a].ghat_l)),
129 W_aL = ArrayDict(self.atomdist.aux_partition, getshape, float)
131 self.vext.update_atomic_hamiltonians_pw(self, W_aL, density)
132 return self.atomdist.to_work(self.atomdist.from_aux(W_aL))
134 def calculate_kinetic_energy(self, density):
135 ekin = 0.0
136 for vt_G, nt_G in zip(self.vt_xG, density.nt_xG):
137 ekin -= self.gd.integrate(vt_G, nt_G, global_integral=False)
138 ekin += self.gd.integrate(self.vt_sG, density.nct_G,
139 global_integral=False).sum()
140 return ekin
142 def restrict(self, in_xR, out_xR=None):
143 """Restrict array."""
144 if out_xR is None:
145 out_xR = self.gd.empty(in_xR.shape[:-3])
147 a_xR = in_xR.reshape((-1,) + in_xR.shape[-3:])
148 b_xR = out_xR.reshape((-1,) + out_xR.shape[-3:])
150 for in_R, out_R in zip(a_xR, b_xR):
151 out_R[:] = self.pd3.restrict(in_R, self.pd2)[0]
153 return out_xR
155 restrict_and_collect = restrict
157 def calculate_forces2(self, dens, ghat_aLv, nct_av, vbar_av):
158 self.vext.derivative_pw(self, ghat_aLv, dens)
159 dens.nct.derivative(self.vt_Q, nct_av)
160 self.vbar.derivative(dens.nt_Q, vbar_av)
162 def get_electrostatic_potential(self, dens: Density) -> Array3D:
163 self.poisson.solve(self.vHt_q, dens)
164 vHt_R = self.pd3.ifft(self.vHt_q, distribute=False)
165 self.pd3.comm.broadcast(vHt_R, 0)
166 return vHt_R