Coverage for gpaw/new/lcao/hamiltonian.py: 84%
99 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
3import numpy as np
4from gpaw.core.matrix import Matrix
5from gpaw.external import ExternalPotential
6from gpaw.lfc import BasisFunctions
7from gpaw.new import zips
8from gpaw.new.calculation import DFTState
9from gpaw.new.fd.pot_calc import FDPotentialCalculator
10from gpaw.new.hamiltonian import Hamiltonian
11from gpaw.new.lcao.wave_functions import LCAOWaveFunctions
12from gpaw.typing import Array2D, Array3D
15class HamiltonianMatrixCalculator:
16 def calculate_matrix(self,
17 wfs: LCAOWaveFunctions) -> Matrix:
18 raise NotImplementedError
21class CollinearHamiltonianMatrixCalculator(HamiltonianMatrixCalculator):
22 def __init__(self,
23 V_sxMM: list[np.ndarray],
24 dH_saii: list[dict[int, Array2D]],
25 basis: BasisFunctions,
26 include_kinetic: bool = True):
27 self.V_sxMM = V_sxMM
28 self.dH_saii = dH_saii
29 self.basis = basis
30 self.include_kinetic = include_kinetic
32 def calculate_matrix(self,
33 wfs: LCAOWaveFunctions) -> Matrix:
34 if self.include_kinetic:
35 return self._calculate_matrix_with_kinetic(wfs)
36 return self._calculate_matrix_without_kinetic(wfs)
38 def _calculate_potential_matrix(self,
39 wfs: LCAOWaveFunctions,
40 V_xMM: Array3D) -> Matrix:
41 data = V_xMM[0]
42 _, M = data.shape
43 if wfs.dtype == complex:
44 data = data.astype(complex)
45 V_MM = Matrix(M, M, data=data, dist=(wfs.band_comm, -1, 1))
46 if wfs.dtype == complex:
47 phase_x = np.exp(-2j * np.pi *
48 self.basis.sdisp_xc[1:] @ wfs.kpt_c)
49 V_MM.data += np.einsum('x, xMN -> MN',
50 2 * phase_x, V_xMM[1:],
51 optimize=True)
52 return V_MM
54 def _calculate_matrix_without_kinetic(self,
55 wfs: LCAOWaveFunctions,
56 V_xMM: Array3D = None,
57 dH_aii: dict[int, Array2D] = None
58 ) -> Matrix:
59 if V_xMM is None:
60 V_xMM = self.V_sxMM[wfs.spin]
61 if dH_aii is None:
62 dH_aii = self.dH_saii[wfs.spin]
64 V_MM = self._calculate_potential_matrix(wfs, V_xMM)
66 M1, M2 = V_MM.dist.my_row_range()
67 for a, dH_ii in dH_aii.items():
68 P_Mi = wfs.P_aMi[a]
69 V_MM.data += (P_Mi[M1:M2] @ dH_ii).conj() @ P_Mi.T # XXX use gemm
71 return V_MM
73 def _calculate_matrix_with_kinetic(self,
74 wfs: LCAOWaveFunctions) -> Matrix:
75 H_MM = self._calculate_matrix_without_kinetic(wfs)
76 H_MM.data += wfs.T_MM.data
78 wfs.domain_comm.sum(H_MM.data, 0)
80 if wfs.domain_comm.rank == 0:
81 if wfs.dtype == complex:
82 H_MM.add_hermitian_conjugate(scale=0.5)
83 else:
84 H_MM.tril2full()
85 return H_MM
88class NonCollinearHamiltonianMatrixCalculator(HamiltonianMatrixCalculator):
89 def __init__(self, matcalc: CollinearHamiltonianMatrixCalculator):
90 self.matcalc = matcalc
92 def calculate_matrix(self,
93 wfs: LCAOWaveFunctions) -> Matrix:
94 V_sMM = [
95 self.matcalc._calculate_matrix_without_kinetic(wfs, V_xMM, dH_aii)
96 for V_xMM, dH_aii in zips(self.matcalc.V_sxMM,
97 self.matcalc.dH_saii)]
99 V_sMM[0] += wfs.T_MM
101 assert wfs.domain_comm.size == 1
103 for V_MM in V_sMM:
104 wfs.domain_comm.sum(V_MM.data, 0)
106 if wfs.domain_comm.rank == 0:
107 if wfs.dtype == complex:
108 V_MM.add_hermitian_conjugate(scale=0.5)
109 else:
110 V_MM.tril2full()
112 _, M = V_MM.shape
113 v_MM, x_MM, y_MM, z_MM = (V_MM.data for V_MM in V_sMM)
114 H_sMsM = Matrix(2 * M, 2 * M, dtype=complex, dist=(wfs.band_comm,))
115 H_sMsM.data[:M, :M] = v_MM + z_MM
116 H_sMsM.data[:M, M:] = x_MM + 1j * y_MM
117 H_sMsM.data[M:, :M] = x_MM - 1j * y_MM
118 H_sMsM.data[M:, M:] = v_MM - z_MM
119 return H_sMsM
122class LCAOHamiltonian(Hamiltonian):
123 def __init__(self,
124 basis: BasisFunctions):
125 self.basis = basis
127 def create_hamiltonian_matrix_calculator(self,
128 potential,
129 ) -> HamiltonianMatrixCalculator:
130 V_sxMM = [self.basis.calculate_potential_matrices(vt_R.data)
131 for vt_R in potential.vt_sR.to_xp(np)]
133 dH_saii = [{a: dH_sii[s]
134 for a, dH_sii
135 in potential.dH_asii.to_xp(np).items()}
136 for s in range(len(V_sxMM))]
138 matcalc = CollinearHamiltonianMatrixCalculator(V_sxMM, dH_saii,
139 self.basis,
140 include_kinetic=True)
141 if len(V_sxMM) < 4:
142 return matcalc
144 return NonCollinearHamiltonianMatrixCalculator(matcalc)
146 def create_kick_matrix_calculator(self,
147 state: DFTState,
148 ext: ExternalPotential,
149 pot_calc: FDPotentialCalculator
150 ) -> HamiltonianMatrixCalculator:
151 from gpaw.utilities import unpack_hermitian
152 vext_r = pot_calc.vbar_r.new()
153 finegd = vext_r.desc._gd
155 vext_r.data = ext.get_potential(finegd)
156 vext_R = pot_calc.restrict(vext_r)
158 nspins = state.ibzwfs.nspins
160 V_MM = self.basis.calculate_potential_matrices(vext_R.data)
161 V_sxMM = [V_MM for s in range(nspins)]
163 W_aL = pot_calc.ghat_aLr.integrate(vext_r)
165 assert state.ibzwfs.ibz.bz.gamma_only
166 setups_a = state.ibzwfs.wfs_qs[0][0].setups
168 dH_saii = [{a: unpack_hermitian(setups_a[a].Delta_pL @ W_L)
169 for (a, W_L) in W_aL.items()}
170 for s in range(nspins)]
172 return CollinearHamiltonianMatrixCalculator(V_sxMM, dH_saii,
173 self.basis,
174 include_kinetic=False)