Coverage for tbkit/wannier.py: 100%

241 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-15 06:13 +0000

1r""" 

2Wannier functions of a group of bands of a :class:`tbkit.kspace.KSpace` 

3model, built in two steps (three for entangled bands): 

4 

5* **Disentanglement** (when there are more bands than Wannier functions): 

6 at each k, the :math:`n_W`-dimensional subspace of the states in an outer 

7 energy window that is smoothest across the mesh, i.e. that minimizes 

8 :math:`\Omega_I`, keeping the states of an optional frozen window exactly 

9 (Souza, Marzari and Vanderbilt, Phys. Rev. B 65, 035109 (2001)). 

10* **Projection** onto trial orbitals :math:`|g_n\rangle` localized in the 

11 home cell: :math:`A_{mn}(\mathbf{k}) = \langle u_{m\mathbf{k}}|g_n\rangle`, 

12 made unitary by Lowdin orthogonalization, 

13 :math:`U(\mathbf{k}) = A(A^\dagger A)^{-1/2}` (Marzari, Mostofi, Yates, 

14 Souza and Vanderbilt, Rev. Mod. Phys. 84, 1419 (2012), Sec. III.A). 

15* **Maximal localization**: conjugate-gradient descent of the spread 

16 :math:`\Omega = \sum_n \langle r^2\rangle_n - |\bar{\mathbf{r}}_n|^2` 

17 over the gauge :math:`U(\mathbf{k})`, from the overlaps 

18 :math:`M^{(\mathbf{k},\mathbf{b})}_{mn} = \langle u_{m\mathbf{k}}|u_{n\mathbf{k}+\mathbf{b}}\rangle` 

19 between neighbouring points of the k-mesh (Marzari and Vanderbilt, Phys. 

20 Rev. B 56, 12847 (1997)). 

21 

22The Wannier functions are the Fourier transforms of the rotated Bloch 

23states, on the :math:`N_1\times N_2\times\dots` cells of the k-mesh (the 

24supercell on which they are periodic). Their Hamiltonian 

25:math:`H_{mn}(\mathbf{R}) = \langle W_{m\mathbf{0}}|H|W_{n\mathbf{R}}\rangle` 

26is a tight-binding model of its own (*WannierFunctions.kspace*), which 

27interpolates the bands between the mesh points (Wannier interpolation). 

28 

29A band group with a nonzero Chern number has no exponentially localized 

30Wannier functions (Thouless, J. Phys. C 17, L325 (1984); Brouder et al., 

31Phys. Rev. Lett. 98, 046402 (2007)): any projection vanishes somewhere in 

32the Brillouin zone, and the spread grows with the mesh instead of 

33converging. 

34""" 

35from __future__ import annotations 

36 

37import numpy as np 

38from numpy.typing import ArrayLike, NDArray 

39 

40import tbkit.error_handling as error_handling 

41from tbkit.bridges import _finite_sites, finite_system 

42from tbkit.io import _hr_kspace 

43from tbkit.kspace import KSpace 

44 

45 

46class WannierFunctions(): 

47 r''' 

48 Wannier functions of a group of bands, see *wannierize*. 

49 

50 :ivar functions: Complex ndarray, shape (n_wann, N*norb). Amplitudes 

51 of each Wannier function on the orbitals of the 

52 :math:`N = N_1N_2\dots` cells of the k-mesh, indexed as the rows of 

53 *KSpace.finite_ham(nk)* (and the sites of *system*). Each one is a 

54 lattice translate chosen to sit in the middle of the sample. 

55 :ivar positions: Real ndarray, shape (N*norb, space_dim). Position of each row. 

56 :ivar centers: Real ndarray, shape (n_wann, space_dim). Centres 

57 :math:`\bar{\mathbf{r}}_n` of *functions* (Cartesian). 

58 :ivar spreads: Real ndarray, shape (n_wann,). Spreads 

59 :math:`\langle r^2\rangle_n - |\bar{\mathbf{r}}_n|^2` (squared 

60 length), along the primitive vectors only for a chain or a 2D 

61 lattice in 3D. 

62 :ivar omega_i: Real number. Gauge-invariant part :math:`\Omega_I` of the total spread. 

63 :ivar omega_d: Real number. Diagonal part :math:`\tilde\Omega_D`. 

64 :ivar omega_od: Real number. Off-diagonal part :math:`\tilde\Omega_{OD}`. 

65 :ivar gauge: Complex ndarray, shape (N, len(bands), n_wann). 

66 :math:`U(\mathbf{k})` over the k-mesh (flattened as *KSpace.mesh_grid*), 

67 acting on the eigenvectors of *bands*: unitary for an isolated 

68 group, otherwise the disentangled subspace (orthonormal columns, 

69 zero on the states outside the window) times the unitary gauge. 

70 :ivar history: Real ndarray. Total spread after the projection and after each step. 

71 :ivar converged: Boolean. Whether the spread changed by less than *tol*. 

72 :ivar min_singular_value: Real number. Smallest singular value of the 

73 projection :math:`A(\mathbf{k})` over the mesh: close to zero when the 

74 trial orbitals miss the bands somewhere (as they must for a Chern band). 

75 :ivar dis_history: Real ndarray. :math:`\Omega_I` after the projection 

76 and after each disentanglement step (empty for an isolated group). 

77 :ivar dis_converged: Boolean. Whether :math:`\Omega_I` changed by 

78 less than *dis_tol* (True for an isolated group). 

79 :ivar ham_k: Complex ndarray, shape (N, n_wann, n_wann). Hamiltonian 

80 :math:`U^\dagger(\mathbf{k})\,\mathrm{diag}(E)\,U(\mathbf{k})` 

81 in the gauge of the Wannier functions, over the k-mesh. 

82 ''' 

83 

84 def __init__(self, ks, n_cells, functions, positions, centers, spreads, omega_i, 

85 omega_d, omega_od, gauge, history, converged, min_singular_value, 

86 dis_history=None, dis_converged=True, ham_k=None, shift=None) -> None: 

87 self._ks = ks 

88 self.n_cells = n_cells 

89 self.functions = functions 

90 self.positions = positions 

91 self.centers = centers 

92 self.spreads = spreads 

93 self.omega_i = omega_i 

94 self.omega_d = omega_d 

95 self.omega_od = omega_od 

96 self.gauge = gauge 

97 self.history = history 

98 self.converged = converged 

99 self.min_singular_value = min_singular_value 

100 self.dis_history = np.array([]) if dis_history is None else dis_history 

101 self.dis_converged = dis_converged 

102 self.ham_k = ham_k 

103 self._shift = shift # lattice vectors (integers) moving each function to the middle cell 

104 self._system = None 

105 

106 @property 

107 def omega(self) -> float: 

108 r''' 

109 Total spread :math:`\Omega = \Omega_I + \tilde\Omega_D + \tilde\Omega_{OD}`, 

110 the sum of *spreads*. 

111 ''' 

112 return float(np.sum(self.spreads)) 

113 

114 @property 

115 def system(self): 

116 ''' 

117 The finite *System* (open boundaries) whose sites carry *functions*: 

118 *bridges.finite_system(ks, nk)*, built on first access. Spinless 

119 models only (for a spinful one, use *positions*). 

120 ''' 

121 if self._system is None: 

122 self._system = finite_system(self._ks, self.n_cells) 

123 return self._system 

124 

125 def kspace(self, cutoff: float = 1e-10) -> KSpace: 

126 r''' 

127 The tight-binding model of the Wannier functions (Wannier 

128 interpolation): one orbital per function, at its centre (folded 

129 into the home cell), and the matrix elements 

130 

131 .. math:: 

132 

133 H_{mn}(\mathbf{R}) = \langle W_{m\mathbf{0}}|H|W_{n\mathbf{R}}\rangle 

134 = \frac{1}{N}\sum_{\mathbf{k}} e^{-i\mathbf{k}\cdot\mathbf{R}}\, 

135 [U^\dagger(\mathbf{k})\,\mathrm{diag}(E)\,U(\mathbf{k})]_{mn} 

136 

137 as onsite energies and hoppings. Each :math:`\mathbf{R}` is the 

138 image on the :math:`N_1\times N_2\times\dots` supercell closest to 

139 the bond, :math:`|\mathbf{R} + \boldsymbol\tau_n - \boldsymbol\tau_m|` 

140 minimal (equidistant images share the element, as 

141 Wannier90's ``use_ws_distance``). Its bands equal the Wannierized 

142 ones on the k-mesh: all of them for an isolated group, those in the 

143 frozen window after disentanglement. Between the mesh points they 

144 converge exponentially with the mesh, as the functions decay. The 

145 model is the one *io.read_wannier90* would read from Wannier90's 

146 ``seedname_hr.dat``. 

147 

148 :param cutoff: Positive real number or zero. Default value 1e-10. 

149 Drop the hoppings with :math:`|t| \le` *cutoff*. 

150 

151 :returns: 

152 * **ks** -- **KSpace** instance, Hermitian, with n_wann orbitals. 

153 

154 Example usage:: 

155 

156 wf = wannierize(ks, list(range(ks.norb)), trial=[0, 1], frozen=(-3., 0.)) 

157 wk = wf.kspace() 

158 dist, en = wk.k_path(high_symmetry_path(ks.lat)[0], 50) 

159 ''' 

160 error_handling.positive_real_zero(cutoff, 'cutoff') 

161 ks, nk = self._ks, tuple(self.n_cells) 

162 dim, n_k, n_w = ks.dim, int(np.prod(nk)), len(self.spreads) 

163 prim = np.array(ks.lat.prim_vec, dtype='f8') 

164 # orbital m is W_m(T_m), the translate of the Fourier transform W_m(0) 

165 # (the functions before their move to the middle cell) in the home cell 

166 centre = self.centers - self._shift @ prim 

167 cell = np.floor(centre @ np.array(ks.rec_vec, dtype='f8').T / (2 * np.pi) + 1e-8).astype(int) 

168 tau = centre - cell @ prim 

169 # H'_mn(R) = H_mn(R + T_n - T_m): a phase on the mesh, then the Fourier transform 

170 fracs = np.array(np.meshgrid(*[np.arange(n) / n for n in nk], indexing='ij')).reshape(dim, -1).T 

171 dt = cell[None, :, :] - cell[:, None, :] # T_n - T_m, shape (n_w, n_w, dim) 

172 ham = self.ham_k * np.exp(-2j * np.pi * np.einsum('ka,mna->kmn', fracs, dt)) 

173 ham_r = np.fft.fftn(ham.reshape(nk + (n_w, n_w)), axes=tuple(range(dim))) / n_k 

174 # the image R = j + N s closest to each bond, j centred on 0 

175 j = np.array(np.meshgrid(*[(np.arange(n) + n // 2) % n - n // 2 for n in nk], 

176 indexing='ij')).reshape(dim, -1).T 

177 s = np.array(np.meshgrid(*[np.arange(-1, 2)] * dim, indexing='ij')).reshape(dim, -1).T 

178 R = (j[:, None, :] + s[None, :, :] * np.array(nk)).reshape(-1, dim) # (n_k * 3^dim, dim) 

179 bond = (R @ prim)[:, None, None, :] + tau[None, None, :, :] - tau[None, :, None, :] 

180 dist = np.linalg.norm(bond, axis=3).reshape(n_k, len(s), n_w, n_w) 

181 closest = dist <= dist.min(axis=1, keepdims=True) + 1e-6 * max(1., float(np.abs(prim).max())) 

182 weight = closest / np.sum(closest, axis=1, keepdims=True) 

183 ham_r = (ham_r.reshape(n_k, 1, n_w, n_w) * weight).reshape(-1, n_w, n_w) 

184 keep = np.any(weight.reshape(-1, n_w, n_w) > 0., axis=(1, 2)) 

185 return _hr_kspace(ks.lat.prim_vec, tau, ['a'] * n_w, R[keep], ham_r[keep], cutoff, 1e-8) 

186 

187 

188def _shells(rec: NDArray[np.float64], nk: tuple[int, ...]) -> tuple[NDArray, NDArray, NDArray]: 

189 r''' 

190 Private function. Neighbours :math:`\mathbf{b}` of a k-point on the mesh 

191 and their weights :math:`w_b`, so that 

192 :math:`\sum_b w_b b_\alpha b_\beta = \delta_{\alpha\beta}` (the 

193 finite-difference condition of Marzari and Vanderbilt 1997, Eq. B1). 

194 Shells of equal :math:`|\mathbf{b}|` are added by increasing length, 

195 skipping those that add no independent direction, until the condition 

196 is solvable (as Wannier90 does). 

197 

198 :returns: steps (integers, shape (nb, dim)), b (shape (nb, dim)), w (shape (nb,)). 

199 ''' 

200 dim = len(nk) 

201 steps = np.array(np.meshgrid(*[np.arange(-1, 2)] * dim, indexing='ij')).reshape(dim, -1).T 

202 steps = steps[np.any(steps != 0, axis=1)] 

203 b = (steps / np.array(nk)[None, :]) @ rec 

204 length = np.linalg.norm(b, axis=1) 

205 radii = np.unique(np.round(length / length.min(), 8)) 

206 upper = np.triu_indices(dim) 

207 target = np.eye(dim)[upper] 

208 chosen: list = [] 

209 columns: list = [] 

210 for r in radii: 

211 shell = np.abs(length / length.min() - r) < 1e-6 

212 column = np.einsum('ba,bc->ac', b[shell], b[shell])[upper] 

213 trial = np.array(columns + [column]).T 

214 if np.linalg.matrix_rank(trial, tol=1e-10) < len(columns) + 1: 

215 continue 

216 chosen.append(shell) 

217 columns.append(column) 

218 w, *_ = np.linalg.lstsq(trial, target, rcond=None) 

219 # always reached: the steps e_i and e_i + e_j give dim(dim+1)/2 

220 # independent columns, and the system is then square and invertible 

221 if np.allclose(trial @ w, target, atol=1e-8): 

222 break 

223 keep = np.concatenate([np.flatnonzero(s) for s in chosen]) 

224 weights = np.concatenate([np.full(np.sum(s), ws) for s, ws in zip(chosen, w)]) 

225 return steps[keep], b[keep], weights 

226 

227 

228def _spread(m, b, w, n_k): 

229 r''' 

230 Private function. Centres (shape (n_wann, dim), k coordinates), spreads 

231 and the parts :math:`\Omega_I, \tilde\Omega_D, \tilde\Omega_{OD}` from 

232 the overlaps *m* (shape (nk, nb, n_wann, n_wann)). 

233 ''' 

234 diag = np.diagonal(m, axis1=2, axis2=3) # (nk, nb, n_wann) 

235 phase = np.angle(diag) 

236 centers = -np.einsum('b,ba,kbn->na', w, b, phase) / n_k 

237 r2 = np.einsum('b,kbn->n', w, 1. - np.abs(diag) ** 2 + phase ** 2) / n_k 

238 spreads = r2 - np.sum(centers ** 2, axis=1) 

239 total = np.einsum('b,kbmn->', w, np.abs(m) ** 2) / n_k 

240 on_diag = np.einsum('b,kbn->', w, np.abs(diag) ** 2) / n_k 

241 omega_i = float(np.sum(w) * m.shape[2] - total) 

242 omega_od = float(total - on_diag) 

243 omega_d = float(np.einsum('b,kbn->', w, (phase + (b @ centers.T)[None]) ** 2) / n_k) 

244 return centers, spreads, omega_i, omega_d, omega_od 

245 

246 

247def _gradient(m, b, w, centers): 

248 r''' 

249 Private function. Gradient :math:`G(\mathbf{k})` of the spread for a 

250 gauge change :math:`U \to U e^{dW}` (Marzari and Vanderbilt 1997, 

251 Eq. 52), :math:`G = 4\sum_b w_b(\mathcal{A}[R] - \mathcal{S}[T])`: 

252 :math:`\Omega` decreases along :math:`dW = \epsilon G`, :math:`\epsilon > 0`, 

253 with :math:`R_{mn} = M_{mn}M_{nn}^*`, :math:`T_{mn} = (M_{mn}/M_{nn})q_n`, 

254 :math:`q_n = \mathrm{Im}\ln M_{nn} + \mathbf{b}\cdot\bar{\mathbf{r}}_n`, 

255 :math:`\mathcal{A}[B] = (B - B^\dagger)/2`, :math:`\mathcal{S}[B] = (B + B^\dagger)/2i`. 

256 Shape (nk, n_wann, n_wann), anti-Hermitian. 

257 ''' 

258 diag = np.diagonal(m, axis1=2, axis2=3) 

259 r = m * diag.conj()[:, :, None, :] 

260 q = np.angle(diag) + (b @ centers.T)[None] 

261 t = m / diag[:, :, None, :] * q[:, :, None, :] 

262 a_r = (r - r.conj().transpose(0, 1, 3, 2)) / 2 

263 s_t = (t + t.conj().transpose(0, 1, 3, 2)) / 2j 

264 return 4 * np.einsum('b,kbmn->kmn', w, a_r - s_t) 

265 

266 

267def _unitary_step(g: NDArray[np.complex128], eps: float) -> NDArray[np.complex128]: 

268 ''' 

269 Private function. exp(eps G) for anti-Hermitian matrices G (stacked). 

270 ''' 

271 h = 1j * g # Hermitian 

272 lam, vec = np.linalg.eigh((h + h.conj().transpose(0, 2, 1)) / 2) 

273 return (vec * np.exp(-1j * eps * lam)[:, None, :]) @ vec.conj().transpose(0, 2, 1) 

274 

275 

276def _disentangle(m0, neighbour, w, inside, frozen, a, n_w, max_iter, tol, mixing): 

277 r''' 

278 Private function. Souza-Marzari-Vanderbilt subspace selection. At each 

279 k, the :math:`n_W` states :math:`U(\mathbf{k})` (columns, in the basis 

280 of the candidate bands) are the frozen ones plus the leading 

281 eigenvectors of 

282 

283 .. math:: 

284 

285 Z(\mathbf{k}) = \sum_b w_b\, M^{(0)}_{\mathbf{k},\mathbf{b}}\, 

286 P(\mathbf{k}+\mathbf{b})\, M^{(0)\dagger}_{\mathbf{k},\mathbf{b}} 

287 

288 restricted to the free states of the window (*inside* and not *frozen*), 

289 :math:`P = UU^\dagger` from the previous step, with linear mixing of 

290 :math:`Z` (Eqs. 21-25 of SMV 2001). Each step lowers 

291 :math:`\Omega_I = \sum_b w_b(n_W - \langle\|U_\mathbf{k}^\dagger M^{(0)}U_{\mathbf{k}+\mathbf{b}}\|^2\rangle_k)`. 

292 The first subspace is the projection of the trial orbitals *a* 

293 (overlaps :math:`\langle u_m|g_n\rangle`, shape (nk, N, n_w)). 

294 

295 :returns: U (nk, N, n_w), the :math:`\Omega_I` history, converged. 

296 ''' 

297 n_k, n = inside.shape 

298 free = inside & ~frozen 

299 # the eigenvalues of Z lie in [0, sum w]: frozen states above, excluded below 

300 big = 2 * np.sum(w) + 1 

301 level = np.where(frozen, big, np.where(inside, 0., -big)) 

302 diag = np.arange(n) 

303 

304 def select(z): 

305 z = z * (free[:, :, None] & free[:, None, :]) 

306 z[:, diag, diag] += level 

307 return np.linalg.eigh(z)[1][:, :, -n_w:] 

308 

309 def z_matrix(u): 

310 mu = m0 @ u[neighbour] # (nk, nb, N, n_w) 

311 return np.einsum('b,kbmi,kbni->kmn', w, mu, mu.conj()) 

312 

313 u = select(a @ a.conj().transpose(0, 2, 1)) 

314 z = z_matrix(u) 

315 history = [float(n_w * np.sum(w) - np.einsum('kmi,kmn,kni->', u.conj(), z, u).real / n_k)] 

316 converged = max_iter == 0 

317 z_in = z 

318 for _ in range(max_iter): 

319 u = select(z_in) 

320 z = z_matrix(u) 

321 history.append(float(n_w * np.sum(w) - np.einsum('kmi,kmn,kni->', u.conj(), z, u).real / n_k)) 

322 if abs(history[-2] - history[-1]) < tol: 

323 converged = True 

324 break 

325 z_in = mixing * z + (1 - mixing) * z_in 

326 return u, np.array(history), converged 

327 

328 

329def wannierize( 

330 ks: KSpace, bands: int | list[int], trial: list[int] | ArrayLike, 

331 nk: int | tuple[int, ...] = 12, max_iter: int = 500, tol: float = 1e-10, 

332 step: float = 0.5, window: tuple[float, float] | None = None, 

333 frozen: tuple[float, float] | None = None, dis_iter: int = 2000, dis_tol: float = 1e-10, 

334 mixing: float = 0.5, 

335) -> WannierFunctions: 

336 r''' 

337 Build the maximally localized Wannier functions of a group of bands: 

338 project the Bloch states onto trial orbitals (Lowdin orthogonalized), 

339 then minimize the spread 

340 

341 .. math:: 

342 

343 \Omega = \sum_n \left[\langle r^2\rangle_n - |\bar{\mathbf{r}}_n|^2\right] 

344 = \Omega_I + \tilde\Omega_D + \tilde\Omega_{OD} 

345 

346 by conjugate gradients over the gauge :math:`U(\mathbf{k})` (Marzari 

347 and Vanderbilt, Phys. Rev. B 56, 12847 (1997); as in Wannier90, with a 

348 parabolic line search). The finite-difference 

349 formulas use the overlaps of the Bloch states at neighbouring mesh 

350 points, with the orbital positions included (as in the Wilson loops of 

351 *KSpace.wannier_centers*, so the centres agree with them). Only 

352 :math:`\tilde\Omega_D + \tilde\Omega_{OD}` depends on the gauge; 

353 :math:`\Omega_I` is fixed by the band group. 

354 

355 In 1D, the minimum has :math:`\tilde\Omega_D = \tilde\Omega_{OD} = 0` 

356 for one band, and the centre is the Berry phase over :math:`2\pi`. For 

357 a Chern band, every projection fails somewhere (*min_singular_value* 

358 tends to zero), and the spread diverges as the mesh is refined. The 

359 descent finds the local minimum nearest the projected gauge: trial 

360 orbitals that miss the bands at isolated k-points (a small 

361 *min_singular_value*) leave phase vortices that it does not remove, 

362 even in a trivial band, so choose orbitals the bands actually live on. 

363 

364 **Entangled bands.** With fewer trial orbitals :math:`n_W` than 

365 *bands* (or with a *window*), the bands need not be isolated: the 

366 Wannier functions span, at each k, the :math:`n_W`-dimensional subspace 

367 of the states of *bands* inside the outer *window* that minimizes 

368 :math:`\Omega_I` (Souza, Marzari and Vanderbilt, Phys. Rev. B 65, 

369 035109 (2001)), found by iterating from the projection of the trial 

370 orbitals, with linear *mixing*. The states inside the *frozen* window 

371 are kept exactly, so the Wannier bands (*WannierFunctions.kspace*) 

372 reproduce them. The maximal localization then runs within that 

373 subspace. Pass every band (``list(range(ks.norb))``) and select the 

374 states with the windows. 

375 

376 :param ks: **KSpace** instance, Hermitian. 

377 :param bands: Band index, or list of band indices. With one trial 

378 orbital per band and no window, the group, which must be separated 

379 in energy from the other bands over the whole mesh; otherwise the 

380 candidate bands of the disentanglement. 

381 :param trial: Trial orbitals, one per Wannier function (at most one per 

382 band): a list of orbital indices (each a single orbital of the home 

383 cell), or a complex array of shape (norb, n_wann) whose columns are 

384 combinations of the orbitals of the home cell (e.g. a bond orbital). 

385 :param nk: Positive integer, or tuple of *dim* integers. Default value 

386 12. k-mesh, at least 3 along each direction; the Wannier functions 

387 live on the same number of cells. 

388 :param max_iter: Positive integer or zero. Default value 500. Maximal 

389 number of descent steps (0 keeps the projected gauge). 

390 :param tol: Positive real number. Default value 1e-10. Convergence 

391 threshold on the change of :math:`\Omega` between steps. 

392 :param step: Positive real number. Default value 0.5. Trial step 

393 :math:`\alpha` of the line search, :math:`dW = \alpha D/4\sum_b w_b` 

394 along the search direction :math:`D`; it is halved whenever the 

395 spread would grow. 

396 :param window: Tuple of two real numbers. Default value None (every 

397 state of *bands*). Outer energy window :math:`(E_{min}, E_{max})`: 

398 the states of *bands* inside it are the candidates, at least 

399 n_wann at every k-point. 

400 :param frozen: Tuple of two real numbers. Default value None (no 

401 frozen states). Inner energy window, inside *window*: its states, 

402 at most n_wann at every k-point, are kept in the subspace. 

403 :param dis_iter: Positive integer or zero. Default value 2000. Maximal 

404 number of disentanglement steps (0 keeps the projected subspace). 

405 :param dis_tol: Positive real number. Default value 1e-10. Convergence 

406 threshold on the change of :math:`\Omega_I` between steps. 

407 :param mixing: Real in (0, 1]. Default value 0.5. Fraction of the new 

408 :math:`Z(\mathbf{k})` mixed in at each disentanglement step. 

409 

410 :returns: 

411 * **wf** -- **WannierFunctions** instance. 

412 

413 Example usage:: 

414 

415 wf = wannierize(ssh, 0, trial=[0], nk=20) 

416 wf.centers, wf.spreads, wf.functions 

417 # two Wannier functions out of four bands, the lowest two kept exactly 

418 wf = wannierize(ks, [0, 1, 2, 3], trial=[0, 1], window=(-4., 3.), frozen=(-4., 0.)) 

419 wf.kspace() # the Wannier-interpolated model 

420 ''' 

421 error_handling.kspace(ks, KSpace) 

422 error_handling.hermitian_model(ks.is_hermitian()) 

423 if isinstance(bands, int): 

424 bands = [bands] 

425 error_handling.band_indices(bands, ks.norb) 

426 error_handling.trial_orbitals(trial, ks.norb, len(bands)) 

427 error_handling.nk(nk, ks.dim) 

428 nk = (nk,) * ks.dim if isinstance(nk, int) else nk 

429 error_handling.nk_min(nk, 3) 

430 error_handling.positive_int_zero(max_iter, 'max_iter') 

431 error_handling.positive_real(tol, 'tol') 

432 error_handling.positive_real(step, 'step') 

433 if window is not None: 

434 error_handling.energy_window(window, 'window') 

435 if frozen is not None: 

436 error_handling.energy_window(frozen, 'frozen') 

437 if window is not None: 

438 error_handling.frozen_window(frozen, window) 

439 error_handling.positive_int_zero(dis_iter, 'dis_iter') 

440 error_handling.positive_real(dis_tol, 'dis_tol') 

441 error_handling.mixing(mixing) 

442 if isinstance(trial, list) and all(isinstance(o, int) for o in trial): 

443 g = np.eye(ks.norb, dtype='c16')[:, trial] 

444 else: 

445 g = np.asarray(trial, dtype='c16') 

446 

447 n_k, n_w = int(np.prod(nk)), g.shape[1] 

448 entangled = n_w < len(bands) or window is not None or frozen is not None 

449 _, kpts = ks.mesh_grid(nk) 

450 en = ks._eigs(kpts) 

451 if not entangled: 

452 error_handling.isolated_bands(en, bands) 

453 v = ks._subspaces(kpts, bands) # (n_k, norb, len(bands)) 

454 

455 # overlaps of the Bloch states with their mesh neighbours 

456 steps, b, w = _shells(ks.rec_vec_k, nk) 

457 index = np.arange(n_k).reshape(nk) 

458 neighbour = np.stack([np.roll(index, tuple(-st), axis=tuple(range(ks.dim))).ravel() 

459 for st in steps], axis=1) # (n_k, nb) 

460 tau = ks.orbital_positions() 

461 phase = np.exp(-1j * tau @ (ks.k_basis @ b.T)) # (norb, nb) 

462 m0 = np.einsum('kom,ob,kbon->kbmn', v.conj(), phase, v[neighbour]) 

463 

464 # disentanglement: the n_w-dimensional subspace, then the same steps within it 

465 energies = en[:, bands] 

466 dis_history, dis_converged = np.array([]), True 

467 u_dis = np.broadcast_to(np.eye(n_w, dtype='c16'), (n_k, n_w, n_w)) 

468 if entangled: 

469 lo, hi = window if window is not None else (-np.inf, np.inf) 

470 inside = (energies >= lo) & (energies <= hi) 

471 fixed = np.zeros_like(inside) if frozen is None else \ 

472 inside & (energies >= frozen[0]) & (energies <= frozen[1]) 

473 error_handling.window_states(int(inside.sum(axis=1).min()), int(fixed.sum(axis=1).max()), n_w) 

474 u_dis, dis_history, dis_converged = _disentangle( 

475 m0, neighbour, w, inside, fixed, v.conj().transpose(0, 2, 1) @ g, n_w, 

476 dis_iter, dis_tol, mixing) 

477 v = v @ u_dis # (n_k, norb, n_w) 

478 m0 = u_dis.conj().transpose(0, 2, 1)[:, None] @ m0 @ u_dis[neighbour] 

479 

480 # projection, made unitary: A = W s V^dag -> U = W V^dag 

481 a = v.conj().transpose(0, 2, 1) @ g 

482 w_svd, s, vh = np.linalg.svd(a) 

483 error_handling.projection(s.min()) 

484 u = w_svd @ vh 

485 

486 def overlaps(u): 

487 return u.conj().transpose(0, 2, 1)[:, None] @ m0 @ u[neighbour] 

488 

489 def trial_gauge(u, d, x): 

490 u_x = u @ _unitary_step(d, x) 

491 m_x = overlaps(u_x) 

492 c_x, s_x, *_ = _spread(m_x, b, w, n_k) 

493 return float(np.sum(s_x)), u_x, m_x, c_x 

494 

495 m = overlaps(u) 

496 centers, spreads, *_ = _spread(m, b, w, n_k) 

497 history = [float(np.sum(spreads))] 

498 eps = step / (4 * np.sum(w)) 

499 converged = max_iter == 0 

500 d, g_norm = None, 0. 

501 for it in range(max_iter): 

502 # conjugate gradients (Fletcher-Reeves, restarted every 5 steps; a 

503 # direction without descent fails the line search, which restarts) 

504 grad = _gradient(m, b, w, centers) 

505 g_new = float(np.sum(np.abs(grad) ** 2)) 

506 d = grad + g_new / g_norm * d if d is not None and it % 5 and g_norm > 0. else grad 

507 g_norm = g_new 

508 # parabola through steps 0, eps, 2 eps; keep the lowest spread found 

509 tries = [trial_gauge(u, d, eps), trial_gauge(u, d, 2 * eps)] 

510 o0, o1, o2 = history[-1], tries[0][0], tries[1][0] 

511 curv = o2 - 2 * o1 + o0 

512 if curv > 0.: 

513 x = (3 * o0 - 4 * o1 + o2) / (2 * curv) 

514 if 0. < x < 4.: 

515 tries.append(trial_gauge(u, d, x * eps)) 

516 best = min(tries, key=lambda t: t[0]) 

517 if best[0] >= o0: 

518 # no descent along d: restart from the gradient with a smaller step, 

519 # until the step is negligible (a minimum, within roundoff) 

520 eps /= 2 

521 d = None 

522 if eps < 1e-6 * step / (4 * np.sum(w)): 

523 converged = True 

524 break 

525 continue 

526 omega, u, m, centers = best 

527 history.append(omega) 

528 if o0 - omega < tol: 

529 converged = True 

530 break 

531 centers, spreads, omega_i, omega_d, omega_od = _spread(m, b, w, n_k) 

532 

533 # Wannier functions: W_n(R, o) = (1/N) sum_k e^{ik.R} (v U)_{on}(k) 

534 amp = np.fft.ifftn((v @ u).reshape(nk + (ks.norb, n_w)), axes=tuple(range(ks.dim))) 

535 # move each one so that its centre sits in the middle cell 

536 rec = np.array(ks.rec_vec, dtype='f8') 

537 cart = centers @ ks.k_basis.T 

538 frac = cart @ rec.T / (2 * np.pi) 

539 shift = np.array(nk)[None, :] // 2 - np.floor(frac + 1e-8).astype(int) # roundoff at a cell corner 

540 functions = np.zeros((n_w, n_k * ks.norb), 'c16') 

541 for n in range(n_w): 

542 moved = np.roll(amp[..., n], tuple(shift[n]), axis=tuple(range(ks.dim))) 

543 # rows of finite_ham: cell n1 + N1 n2 + N1 N2 n3, then the orbital 

544 functions[n] = np.transpose(moved, tuple(range(ks.dim))[::-1] + (ks.dim,)).ravel() 

545 cart = cart + shift @ np.array(ks.lat.prim_vec, dtype='f8') 

546 positions, _ = _finite_sites(ks, nk) 

547 # out of the plane of a 2D lattice in 3D (or off a chain), the position 

548 # operator is well defined: the centre is its expectation value 

549 perp = np.eye(ks.space_dim) - ks.k_basis @ ks.k_basis.T 

550 cart = cart + (np.abs(functions) ** 2 @ positions) @ perp 

551 gauge = u_dis @ u # (n_k, len(bands), n_w) 

552 ham_k = gauge.conj().transpose(0, 2, 1) @ (energies[:, :, None] * gauge) 

553 return WannierFunctions(ks, nk, functions, positions, cart, spreads, omega_i, omega_d, 

554 omega_od, gauge, np.array(history), converged, float(s.min()), 

555 dis_history, dis_converged, ham_k, shift)