root / ase / dft / wannier.py @ 7
Historique | Voir | Annoter | Télécharger (28,24 ko)
| 1 | 1 | tkerber | """ Maximally localized Wannier Functions
|
|---|---|---|---|
| 2 | 1 | tkerber |
|
| 3 | 1 | tkerber | Find the set of maximally localized Wannier functions
|
| 4 | 1 | tkerber | using the spread functional of Marzari and Vanderbilt
|
| 5 | 1 | tkerber | (PRB 56, 1997 page 12847).
|
| 6 | 1 | tkerber | """
|
| 7 | 1 | tkerber | import numpy as np |
| 8 | 1 | tkerber | from time import time |
| 9 | 1 | tkerber | from math import sqrt, pi |
| 10 | 1 | tkerber | from pickle import dump, load |
| 11 | 1 | tkerber | from ase.parallel import paropen |
| 12 | 1 | tkerber | from ase.calculators.dacapo import Dacapo |
| 13 | 1 | tkerber | from ase.dft.kpoints import get_monkhorst_shape |
| 14 | 1 | tkerber | from ase.transport.tools import dagger, normalize |
| 15 | 1 | tkerber | |
| 16 | 1 | tkerber | dag = dagger |
| 17 | 1 | tkerber | |
| 18 | 1 | tkerber | |
| 19 | 1 | tkerber | def gram_schmidt(U): |
| 20 | 1 | tkerber | """Orthonormalize columns of U according to the Gram-Schmidt procedure."""
|
| 21 | 1 | tkerber | for i, col in enumerate(U.T): |
| 22 | 1 | tkerber | for col2 in U.T[:i]: |
| 23 | 1 | tkerber | col -= col2 * np.dot(col2.conj(), col) |
| 24 | 1 | tkerber | col /= np.linalg.norm(col) |
| 25 | 1 | tkerber | |
| 26 | 1 | tkerber | |
| 27 | 1 | tkerber | def lowdin(U, S=None): |
| 28 | 1 | tkerber | """Orthonormalize columns of U according to the Lowdin procedure.
|
| 29 | 1 | tkerber |
|
| 30 | 1 | tkerber | If the overlap matrix is know, it can be specified in S.
|
| 31 | 1 | tkerber | """
|
| 32 | 1 | tkerber | if S is None: |
| 33 | 1 | tkerber | S = np.dot(dag(U), U) |
| 34 | 1 | tkerber | eig, rot = np.linalg.eigh(S) |
| 35 | 1 | tkerber | rot = np.dot(rot / np.sqrt(eig), dag(rot)) |
| 36 | 1 | tkerber | U[:] = np.dot(U, rot) |
| 37 | 1 | tkerber | |
| 38 | 1 | tkerber | |
| 39 | 1 | tkerber | def neighbor_k_search(k_c, G_c, kpt_kc, tol=1e-4): |
| 40 | 1 | tkerber | # search for k1 (in kpt_kc) and k0 (in alldir), such that
|
| 41 | 1 | tkerber | # k1 - k - G + k0 = 0
|
| 42 | 1 | tkerber | alldir_dc = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1], |
| 43 | 1 | tkerber | [1,1,0],[1,0,1],[0,1,1]], int) |
| 44 | 1 | tkerber | for k0_c in alldir_dc: |
| 45 | 1 | tkerber | for k1, k1_c in enumerate(kpt_kc): |
| 46 | 1 | tkerber | if np.linalg.norm(k1_c - k_c - G_c + k0_c) < tol:
|
| 47 | 1 | tkerber | return k1, k0_c
|
| 48 | 1 | tkerber | |
| 49 | 1 | tkerber | print 'Wannier: Did not find matching kpoint for kpt=', k_c |
| 50 | 1 | tkerber | print 'Probably non-uniform k-point grid' |
| 51 | 1 | tkerber | raise NotImplementedError |
| 52 | 1 | tkerber | |
| 53 | 1 | tkerber | |
| 54 | 1 | tkerber | def calculate_weights(cell_cc): |
| 55 | 1 | tkerber | """ Weights are used for non-cubic cells, see PRB **61**, 10040"""
|
| 56 | 1 | tkerber | alldirs_dc = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1], |
| 57 | 1 | tkerber | [1, 1, 0], [1, 0, 1], [0, 1, 1]], dtype=int) |
| 58 | 1 | tkerber | g = np.dot(cell_cc, cell_cc.T) |
| 59 | 1 | tkerber | # NOTE: Only first 3 of following 6 weights are presently used:
|
| 60 | 1 | tkerber | w = np.zeros(6)
|
| 61 | 1 | tkerber | w[0] = g[0, 0] - g[0, 1] - g[0, 2] |
| 62 | 1 | tkerber | w[1] = g[1, 1] - g[0, 1] - g[1, 2] |
| 63 | 1 | tkerber | w[2] = g[2, 2] - g[0, 2] - g[1, 2] |
| 64 | 1 | tkerber | w[3] = g[0, 1] |
| 65 | 1 | tkerber | w[4] = g[0, 2] |
| 66 | 1 | tkerber | w[5] = g[1, 2] |
| 67 | 1 | tkerber | # Make sure that first 3 Gdir vectors are included -
|
| 68 | 1 | tkerber | # these are used to calculate Wanniercenters.
|
| 69 | 1 | tkerber | Gdir_dc = alldirs_dc[:3]
|
| 70 | 1 | tkerber | weight_d = w[:3]
|
| 71 | 1 | tkerber | for d in range(3, 6): |
| 72 | 1 | tkerber | if abs(w[d]) > 1e-5: |
| 73 | 1 | tkerber | Gdir_dc = np.concatenate(Gdir_dc, alldirs_dc[d]) |
| 74 | 1 | tkerber | weight_d = np.concatenate(weight_d, w[d]) |
| 75 | 1 | tkerber | weight_d /= max(abs(weight_d)) |
| 76 | 1 | tkerber | return weight_d, Gdir_dc
|
| 77 | 1 | tkerber | |
| 78 | 1 | tkerber | |
| 79 | 1 | tkerber | def random_orthogonal_matrix(dim, seed=None, real=False): |
| 80 | 1 | tkerber | """Generate a random orthogonal matrix"""
|
| 81 | 1 | tkerber | if seed is not None: |
| 82 | 1 | tkerber | np.random.seed(seed) |
| 83 | 1 | tkerber | |
| 84 | 1 | tkerber | H = np.random.rand(dim, dim) |
| 85 | 1 | tkerber | np.add(dag(H), H, H) |
| 86 | 1 | tkerber | np.multiply(.5, H, H)
|
| 87 | 1 | tkerber | |
| 88 | 1 | tkerber | if real:
|
| 89 | 1 | tkerber | gram_schmidt(H) |
| 90 | 1 | tkerber | return H
|
| 91 | 1 | tkerber | else:
|
| 92 | 1 | tkerber | val, vec = np.linalg.eig(H) |
| 93 | 1 | tkerber | return np.dot(vec * np.exp(1.j * val), dag(vec)) |
| 94 | 1 | tkerber | |
| 95 | 1 | tkerber | |
| 96 | 1 | tkerber | def steepest_descent(func, step=.005, tolerance=1e-6, **kwargs): |
| 97 | 1 | tkerber | fvalueold = 0.
|
| 98 | 1 | tkerber | fvalue = fvalueold + 10
|
| 99 | 1 | tkerber | count=0
|
| 100 | 1 | tkerber | while abs((fvalue - fvalueold) / fvalue) > tolerance: |
| 101 | 1 | tkerber | fvalueold = fvalue |
| 102 | 1 | tkerber | dF = func.get_gradients() |
| 103 | 1 | tkerber | func.step(dF * step, **kwargs) |
| 104 | 1 | tkerber | fvalue = func.get_functional_value() |
| 105 | 1 | tkerber | count += 1
|
| 106 | 1 | tkerber | print 'SteepestDescent: iter=%s, value=%s' % (count, fvalue) |
| 107 | 1 | tkerber | |
| 108 | 1 | tkerber | |
| 109 | 1 | tkerber | def md_min(func, step=.25, tolerance=1e-6, verbose=False, **kwargs): |
| 110 | 1 | tkerber | if verbose:
|
| 111 | 1 | tkerber | print 'Localize with step =', step, 'and tolerance =', tolerance |
| 112 | 1 | tkerber | t = -time() |
| 113 | 1 | tkerber | fvalueold = 0.
|
| 114 | 1 | tkerber | fvalue = fvalueold + 10
|
| 115 | 1 | tkerber | count = 0
|
| 116 | 1 | tkerber | V = np.zeros(func.get_gradients().shape, dtype=complex)
|
| 117 | 1 | tkerber | while abs((fvalue - fvalueold) / fvalue) > tolerance: |
| 118 | 1 | tkerber | fvalueold = fvalue |
| 119 | 1 | tkerber | dF = func.get_gradients() |
| 120 | 1 | tkerber | V *= (dF * V.conj()).real > 0
|
| 121 | 1 | tkerber | V += step * dF |
| 122 | 1 | tkerber | func.step(V, **kwargs) |
| 123 | 1 | tkerber | fvalue = func.get_functional_value() |
| 124 | 1 | tkerber | if fvalue < fvalueold:
|
| 125 | 1 | tkerber | step *= 0.5
|
| 126 | 1 | tkerber | count += 1
|
| 127 | 1 | tkerber | if verbose:
|
| 128 | 1 | tkerber | print 'MDmin: iter=%s, step=%s, value=%s' % (count, step, fvalue) |
| 129 | 1 | tkerber | if verbose:
|
| 130 | 1 | tkerber | t += time() |
| 131 | 1 | tkerber | print '%d iterations in %0.2f seconds (%0.2f ms/iter), endstep = %s' %( |
| 132 | 1 | tkerber | count, t, t * 1000. / count, step)
|
| 133 | 1 | tkerber | |
| 134 | 1 | tkerber | |
| 135 | 1 | tkerber | def rotation_from_projection(proj_nw, fixed, ortho=True): |
| 136 | 1 | tkerber | """Determine rotation and coefficient matrices from projections
|
| 137 | 1 | tkerber |
|
| 138 | 1 | tkerber | proj_nw = <psi_n|p_w>
|
| 139 | 1 | tkerber | psi_n: eigenstates
|
| 140 | 1 | tkerber | p_w: localized function
|
| 141 | 1 | tkerber |
|
| 142 | 1 | tkerber | Nb (n) = Number of bands
|
| 143 | 1 | tkerber | Nw (w) = Number of wannier functions
|
| 144 | 1 | tkerber | M (f) = Number of fixed states
|
| 145 | 1 | tkerber | L (l) = Number of extra degrees of freedom
|
| 146 | 1 | tkerber | U (u) = Number of non-fixed states
|
| 147 | 1 | tkerber | """
|
| 148 | 1 | tkerber | |
| 149 | 1 | tkerber | Nb, Nw = proj_nw.shape |
| 150 | 1 | tkerber | M = fixed |
| 151 | 1 | tkerber | L = Nw - M |
| 152 | 1 | tkerber | |
| 153 | 1 | tkerber | U_ww = np.empty((Nw, Nw), dtype=proj_nw.dtype) |
| 154 | 1 | tkerber | U_ww[:M] = proj_nw[:M] |
| 155 | 1 | tkerber | |
| 156 | 1 | tkerber | if L > 0: |
| 157 | 1 | tkerber | proj_uw = proj_nw[M:] |
| 158 | 1 | tkerber | eig_w, C_ww = np.linalg.eigh(np.dot(dag(proj_uw), proj_uw)) |
| 159 | 1 | tkerber | C_ul = np.dot(proj_uw, C_ww[:, np.argsort(-eig_w.real)[:L]]) |
| 160 | 1 | tkerber | #eig_u, C_uu = np.linalg.eigh(np.dot(proj_uw, dag(proj_uw)))
|
| 161 | 1 | tkerber | #C_ul = C_uu[:, np.argsort(-eig_u.real)[:L]]
|
| 162 | 1 | tkerber | |
| 163 | 1 | tkerber | U_ww[M:] = np.dot(dag(C_ul), proj_uw) |
| 164 | 1 | tkerber | else:
|
| 165 | 1 | tkerber | C_ul = np.empty((Nb - M, 0))
|
| 166 | 1 | tkerber | |
| 167 | 1 | tkerber | normalize(C_ul) |
| 168 | 1 | tkerber | if ortho:
|
| 169 | 1 | tkerber | lowdin(U_ww) |
| 170 | 1 | tkerber | else:
|
| 171 | 1 | tkerber | normalize(U_ww) |
| 172 | 1 | tkerber | |
| 173 | 1 | tkerber | return U_ww, C_ul
|
| 174 | 1 | tkerber | |
| 175 | 1 | tkerber | |
| 176 | 1 | tkerber | class Wannier: |
| 177 | 1 | tkerber | """Maximally localized Wannier Functions
|
| 178 | 1 | tkerber |
|
| 179 | 1 | tkerber | Find the set of maximally localized Wannier functions using the
|
| 180 | 1 | tkerber | spread functional of Marzari and Vanderbilt (PRB 56, 1997 page
|
| 181 | 1 | tkerber | 12847).
|
| 182 | 1 | tkerber | """
|
| 183 | 1 | tkerber | |
| 184 | 1 | tkerber | def __init__(self, nwannier, calc, |
| 185 | 1 | tkerber | file=None,
|
| 186 | 1 | tkerber | nbands=None,
|
| 187 | 1 | tkerber | fixedenergy=None,
|
| 188 | 1 | tkerber | fixedstates=None,
|
| 189 | 1 | tkerber | spin=0,
|
| 190 | 1 | tkerber | initialwannier='random',
|
| 191 | 1 | tkerber | seed=None,
|
| 192 | 1 | tkerber | verbose=False):
|
| 193 | 1 | tkerber | """
|
| 194 | 1 | tkerber | Required arguments:
|
| 195 | 1 | tkerber |
|
| 196 | 1 | tkerber | ``nwannier``: The number of Wannier functions you wish to construct.
|
| 197 | 1 | tkerber | This must be at least half the number of electrons in the system
|
| 198 | 1 | tkerber | and at most equal to the number of bands in the calculation.
|
| 199 | 1 | tkerber |
|
| 200 | 1 | tkerber | ``calc``: A converged DFT calculator class.
|
| 201 | 1 | tkerber | If ``file`` arg. is not provided, the calculator *must* provide the
|
| 202 | 1 | tkerber | method ``get_wannier_localization_matrix``, and contain the
|
| 203 | 1 | tkerber | wavefunctions (save files with only the density is not enough).
|
| 204 | 1 | tkerber | If the localization matrix is read from file, this is not needed,
|
| 205 | 1 | tkerber | unless ``get_function`` or ``write_cube`` is called.
|
| 206 | 1 | tkerber |
|
| 207 | 1 | tkerber | Optional arguments:
|
| 208 | 1 | tkerber |
|
| 209 | 1 | tkerber | ``nbands``: Bands to include in localization.
|
| 210 | 1 | tkerber | The number of bands considered by Wannier can be smaller than the
|
| 211 | 1 | tkerber | number of bands in the calculator. This is useful if the highest
|
| 212 | 1 | tkerber | bands of the DFT calculation are not well converged.
|
| 213 | 1 | tkerber |
|
| 214 | 1 | tkerber | ``spin``: The spin channel to be considered.
|
| 215 | 1 | tkerber | The Wannier code treats each spin channel independently.
|
| 216 | 1 | tkerber |
|
| 217 | 1 | tkerber | ``fixedenergy`` / ``fixedstates``: Fixed part of Heilbert space.
|
| 218 | 1 | tkerber | Determine the fixed part of Hilbert space by either a maximal
|
| 219 | 1 | tkerber | energy *or* a number of bands (possibly a list for multiple
|
| 220 | 1 | tkerber | k-points).
|
| 221 | 1 | tkerber | Default is None meaning that the number of fixed states is equated
|
| 222 | 1 | tkerber | to ``nwannier``.
|
| 223 | 1 | tkerber |
|
| 224 | 1 | tkerber | ``file``: Read localization and rotation matrices from this file.
|
| 225 | 1 | tkerber |
|
| 226 | 1 | tkerber | ``initialwannier``: Initial guess for Wannier rotation matrix.
|
| 227 | 1 | tkerber | Can be 'bloch' to start from the Bloch states, 'random' to be
|
| 228 | 1 | tkerber | randomized, or a list passed to calc.get_initial_wannier.
|
| 229 | 1 | tkerber |
|
| 230 | 1 | tkerber | ``seed``: Seed for random ``initialwannier``.
|
| 231 | 1 | tkerber |
|
| 232 | 1 | tkerber | ``verbose``: True / False level of verbosity.
|
| 233 | 1 | tkerber | """
|
| 234 | 1 | tkerber | # Bloch phase sign convention
|
| 235 | 1 | tkerber | sign = -1
|
| 236 | 1 | tkerber | classname = calc.__class__.__name__ |
| 237 | 1 | tkerber | if classname in ['Dacapo', 'Jacapo']: |
| 238 | 1 | tkerber | print 'Using ' + classname |
| 239 | 1 | tkerber | sign = +1
|
| 240 | 1 | tkerber | |
| 241 | 1 | tkerber | self.nwannier = nwannier
|
| 242 | 1 | tkerber | self.calc = calc
|
| 243 | 1 | tkerber | self.spin = spin
|
| 244 | 1 | tkerber | self.verbose = verbose
|
| 245 | 1 | tkerber | self.kpt_kc = sign * calc.get_ibz_k_points()
|
| 246 | 1 | tkerber | assert len(calc.get_bz_k_points()) == len(self.kpt_kc) |
| 247 | 1 | tkerber | |
| 248 | 1 | tkerber | self.kptgrid = get_monkhorst_shape(self.kpt_kc) |
| 249 | 1 | tkerber | self.Nk = len(self.kpt_kc) |
| 250 | 1 | tkerber | self.unitcell_cc = calc.get_atoms().get_cell()
|
| 251 | 1 | tkerber | self.largeunitcell_cc = (self.unitcell_cc.T * self.kptgrid).T |
| 252 | 1 | tkerber | self.weight_d, self.Gdir_dc = calculate_weights(self.largeunitcell_cc) |
| 253 | 1 | tkerber | self.Ndir = len(self.weight_d) # Number of directions |
| 254 | 1 | tkerber | |
| 255 | 1 | tkerber | if nbands is not None: |
| 256 | 1 | tkerber | self.nbands = nbands
|
| 257 | 1 | tkerber | else:
|
| 258 | 1 | tkerber | self.nbands = calc.get_number_of_bands()
|
| 259 | 1 | tkerber | if fixedenergy is None: |
| 260 | 1 | tkerber | if fixedstates is None: |
| 261 | 1 | tkerber | self.fixedstates_k = np.array([nwannier] * self.Nk, int) |
| 262 | 1 | tkerber | else:
|
| 263 | 1 | tkerber | if type(fixedstates) is int: |
| 264 | 1 | tkerber | fixedstates = [fixedstates] * self.Nk
|
| 265 | 1 | tkerber | self.fixedstates_k = np.array(fixedstates, int) |
| 266 | 1 | tkerber | else:
|
| 267 | 1 | tkerber | # Setting number of fixed states and EDF from specified energy.
|
| 268 | 1 | tkerber | # All states below this energy (relative to Fermi level) are fixed.
|
| 269 | 1 | tkerber | fixedenergy += calc.get_fermi_level() |
| 270 | 1 | tkerber | print fixedenergy
|
| 271 | 1 | tkerber | self.fixedstates_k = np.array(
|
| 272 | 1 | tkerber | [calc.get_eigenvalues(k, spin).searchsorted(fixedenergy) |
| 273 | 1 | tkerber | for k in range(self.Nk)], int) |
| 274 | 1 | tkerber | self.edf_k = self.nwannier - self.fixedstates_k |
| 275 | 1 | tkerber | if verbose:
|
| 276 | 1 | tkerber | print 'Wannier: Fixed states : %s' % self.fixedstates_k |
| 277 | 1 | tkerber | print 'Wannier: Extra degrees of freedom: %s' % self.edf_k |
| 278 | 1 | tkerber | |
| 279 | 1 | tkerber | # Set the list of neighboring k-points k1, and the "wrapping" k0,
|
| 280 | 1 | tkerber | # such that k1 - k - G + k0 = 0
|
| 281 | 1 | tkerber | #
|
| 282 | 1 | tkerber | # Example: kpoints = (-0.375,-0.125,0.125,0.375), dir=0
|
| 283 | 1 | tkerber | # G = [0.25,0,0]
|
| 284 | 1 | tkerber | # k=0.375, k1= -0.375 : -0.375-0.375-0.25 => k0=[1,0,0]
|
| 285 | 1 | tkerber | #
|
| 286 | 1 | tkerber | # For a gamma point calculation k1 = k = 0, k0 = [1,0,0] for dir=0
|
| 287 | 1 | tkerber | if self.Nk == 1: |
| 288 | 1 | tkerber | self.kklst_dk = np.zeros((self.Ndir, 1), int) |
| 289 | 1 | tkerber | k0_dkc = self.Gdir_dc.reshape(-1, 1, 3) |
| 290 | 1 | tkerber | else:
|
| 291 | 1 | tkerber | self.kklst_dk = np.empty((self.Ndir, self.Nk), int) |
| 292 | 1 | tkerber | k0_dkc = np.empty((self.Ndir, self.Nk, 3), int) |
| 293 | 1 | tkerber | |
| 294 | 1 | tkerber | # Distance between kpoints
|
| 295 | 1 | tkerber | kdist_c = np.empty(3)
|
| 296 | 1 | tkerber | for c in range(3): |
| 297 | 1 | tkerber | # make a sorted list of the kpoint values in this direction
|
| 298 | 1 | tkerber | slist = np.argsort(self.kpt_kc[:, c], kind='mergesort') |
| 299 | 1 | tkerber | skpoints_kc = np.take(self.kpt_kc, slist, axis=0) |
| 300 | 1 | tkerber | kdist_c[c] = max([skpoints_kc[n + 1, c] - skpoints_kc[n, c] |
| 301 | 1 | tkerber | for n in range(self.Nk - 1)]) |
| 302 | 1 | tkerber | |
| 303 | 1 | tkerber | for d, Gdir_c in enumerate(self.Gdir_dc): |
| 304 | 1 | tkerber | for k, k_c in enumerate(self.kpt_kc): |
| 305 | 1 | tkerber | # setup dist vector to next kpoint
|
| 306 | 1 | tkerber | G_c = np.where(Gdir_c > 0, kdist_c, 0) |
| 307 | 1 | tkerber | if max(G_c) < 1e-4: |
| 308 | 1 | tkerber | self.kklst_dk[d, k] = k
|
| 309 | 1 | tkerber | k0_dkc[d, k] = Gdir_c |
| 310 | 1 | tkerber | else:
|
| 311 | 1 | tkerber | self.kklst_dk[d, k], k0_dkc[d, k] = \
|
| 312 | 1 | tkerber | neighbor_k_search(k_c, G_c, self.kpt_kc)
|
| 313 | 1 | tkerber | |
| 314 | 1 | tkerber | # Set the inverse list of neighboring k-points
|
| 315 | 1 | tkerber | self.invkklst_dk = np.empty((self.Ndir, self.Nk), int) |
| 316 | 1 | tkerber | for d in range(self.Ndir): |
| 317 | 1 | tkerber | for k1 in range(self.Nk): |
| 318 | 1 | tkerber | self.invkklst_dk[d, k1] = self.kklst_dk[d].tolist().index(k1) |
| 319 | 1 | tkerber | |
| 320 | 1 | tkerber | Nw = self.nwannier
|
| 321 | 1 | tkerber | Nb = self.nbands
|
| 322 | 1 | tkerber | self.Z_dkww = np.empty((self.Ndir, self.Nk, Nw, Nw), complex) |
| 323 | 1 | tkerber | self.V_knw = np.zeros((self.Nk, Nb, Nw), complex) |
| 324 | 1 | tkerber | if file is None: |
| 325 | 1 | tkerber | self.Z_dknn = np.empty((self.Ndir, self.Nk, Nb, Nb), complex) |
| 326 | 1 | tkerber | for d, dirG in enumerate(self.Gdir_dc): |
| 327 | 1 | tkerber | for k in range(self.Nk): |
| 328 | 1 | tkerber | k1 = self.kklst_dk[d, k]
|
| 329 | 1 | tkerber | k0_c = k0_dkc[d, k] |
| 330 | 1 | tkerber | self.Z_dknn[d, k] = calc.get_wannier_localization_matrix(
|
| 331 | 1 | tkerber | nbands=Nb, dirG=dirG, kpoint=k, nextkpoint=k1, |
| 332 | 1 | tkerber | G_I=k0_c, spin=self.spin)
|
| 333 | 1 | tkerber | self.initialize(file=file, initialwannier=initialwannier, seed=seed) |
| 334 | 1 | tkerber | |
| 335 | 1 | tkerber | def initialize(self, file=None, initialwannier='random', seed=None): |
| 336 | 1 | tkerber | """Re-initialize current rotation matrix.
|
| 337 | 1 | tkerber |
|
| 338 | 1 | tkerber | Keywords are identical to those of the constructor.
|
| 339 | 1 | tkerber | """
|
| 340 | 1 | tkerber | Nw = self.nwannier
|
| 341 | 1 | tkerber | Nb = self.nbands
|
| 342 | 1 | tkerber | |
| 343 | 1 | tkerber | if file is not None: |
| 344 | 1 | tkerber | self.Z_dknn, self.U_kww, self.C_kul = load(paropen(file)) |
| 345 | 1 | tkerber | elif initialwannier == 'bloch': |
| 346 | 1 | tkerber | # Set U and C to pick the lowest Bloch states
|
| 347 | 1 | tkerber | self.U_kww = np.zeros((self.Nk, Nw, Nw), complex) |
| 348 | 1 | tkerber | self.C_kul = []
|
| 349 | 1 | tkerber | for U, M, L in zip(self.U_kww, self.fixedstates_k, self.edf_k): |
| 350 | 1 | tkerber | U[:] = np.identity(Nw, complex)
|
| 351 | 1 | tkerber | if L > 0: |
| 352 | 1 | tkerber | self.C_kul.append(
|
| 353 | 1 | tkerber | np.identity(Nb - M, complex)[:, :L])
|
| 354 | 1 | tkerber | else:
|
| 355 | 1 | tkerber | self.C_kul.append([])
|
| 356 | 1 | tkerber | elif initialwannier == 'random': |
| 357 | 1 | tkerber | # Set U and C to random (orthogonal) matrices
|
| 358 | 1 | tkerber | self.U_kww = np.zeros((self.Nk, Nw, Nw), complex) |
| 359 | 1 | tkerber | self.C_kul = []
|
| 360 | 1 | tkerber | for U, M, L in zip(self.U_kww, self.fixedstates_k, self.edf_k): |
| 361 | 1 | tkerber | U[:] = random_orthogonal_matrix(Nw, seed, real=False)
|
| 362 | 1 | tkerber | if L > 0: |
| 363 | 1 | tkerber | self.C_kul.append(random_orthogonal_matrix(
|
| 364 | 1 | tkerber | Nb - M, seed=seed, real=False)[:, :L])
|
| 365 | 1 | tkerber | else:
|
| 366 | 1 | tkerber | self.C_kul.append(np.array([]))
|
| 367 | 1 | tkerber | else:
|
| 368 | 1 | tkerber | # Use initial guess to determine U and C
|
| 369 | 1 | tkerber | self.C_kul, self.U_kww = self.calc.initial_wannier( |
| 370 | 1 | tkerber | initialwannier, self.kptgrid, self.fixedstates_k, |
| 371 | 1 | tkerber | self.edf_k, self.spin) |
| 372 | 1 | tkerber | self.update()
|
| 373 | 1 | tkerber | |
| 374 | 1 | tkerber | def save(self, file): |
| 375 | 1 | tkerber | """Save information on localization and rotation matrices to file."""
|
| 376 | 1 | tkerber | dump((self.Z_dknn, self.U_kww, self.C_kul), paropen(file, 'w')) |
| 377 | 1 | tkerber | |
| 378 | 1 | tkerber | def update(self): |
| 379 | 1 | tkerber | # Update large rotation matrix V (from rotation U and coeff C)
|
| 380 | 1 | tkerber | for k, M in enumerate(self.fixedstates_k): |
| 381 | 1 | tkerber | self.V_knw[k, :M] = self.U_kww[k, :M] |
| 382 | 1 | tkerber | if M < self.nwannier: |
| 383 | 1 | tkerber | self.V_knw[k, M:] = np.dot(self.C_kul[k], self.U_kww[k, M:]) |
| 384 | 1 | tkerber | # else: self.V_knw[k, M:] = 0.0
|
| 385 | 1 | tkerber | |
| 386 | 1 | tkerber | # Calculate the Zk matrix from the large rotation matrix:
|
| 387 | 1 | tkerber | # Zk = V^d[k] Zbloch V[k1]
|
| 388 | 1 | tkerber | for d in range(self.Ndir): |
| 389 | 1 | tkerber | for k in range(self.Nk): |
| 390 | 1 | tkerber | k1 = self.kklst_dk[d, k]
|
| 391 | 1 | tkerber | self.Z_dkww[d, k] = np.dot(dag(self.V_knw[k]), np.dot( |
| 392 | 1 | tkerber | self.Z_dknn[d, k], self.V_knw[k1])) |
| 393 | 1 | tkerber | |
| 394 | 1 | tkerber | # Update the new Z matrix
|
| 395 | 1 | tkerber | self.Z_dww = self.Z_dkww.sum(axis=1) / self.Nk |
| 396 | 1 | tkerber | |
| 397 | 1 | tkerber | def get_centers(self, scaled=False): |
| 398 | 1 | tkerber | """Calculate the Wannier centers
|
| 399 | 1 | tkerber |
|
| 400 | 1 | tkerber | ::
|
| 401 | 1 | tkerber |
|
| 402 | 1 | tkerber | pos = L / 2pi * phase(diag(Z))
|
| 403 | 1 | tkerber | """
|
| 404 | 1 | tkerber | coord_wc = np.angle(self.Z_dww[:3].diagonal(0, 1, 2)).T / (2 * pi) % 1 |
| 405 | 1 | tkerber | if not scaled: |
| 406 | 1 | tkerber | coord_wc = np.dot(coord_wc, self.largeunitcell_cc)
|
| 407 | 1 | tkerber | return coord_wc
|
| 408 | 1 | tkerber | |
| 409 | 1 | tkerber | def get_radii(self): |
| 410 | 1 | tkerber | """Calculate the spread of the Wannier functions.
|
| 411 | 1 | tkerber |
|
| 412 | 1 | tkerber | ::
|
| 413 | 1 | tkerber |
|
| 414 | 1 | tkerber | -- / L \ 2 2
|
| 415 | 1 | tkerber | radius**2 = - > | --- | ln |Z|
|
| 416 | 1 | tkerber | --d \ 2pi /
|
| 417 | 1 | tkerber | """
|
| 418 | 1 | tkerber | r2 = -np.dot(self.largeunitcell_cc.diagonal()**2 / (2 * pi)**2, |
| 419 | 1 | tkerber | np.log(abs(self.Z_dww[:3].diagonal(0, 1, 2))**2)) |
| 420 | 1 | tkerber | return np.sqrt(r2)
|
| 421 | 1 | tkerber | |
| 422 | 1 | tkerber | def get_spectral_weight(self, w): |
| 423 | 1 | tkerber | return abs(self.V_knw[:, :, w])**2 / self.Nk |
| 424 | 1 | tkerber | |
| 425 | 1 | tkerber | def get_pdos(self, w, energies, width): |
| 426 | 1 | tkerber | """Projected density of states (PDOS).
|
| 427 | 1 | tkerber |
|
| 428 | 1 | tkerber | Returns the (PDOS) for Wannier function ``w``. The calculation
|
| 429 | 1 | tkerber | is performed over the energy grid specified in energies. The
|
| 430 | 1 | tkerber | PDOS is produced as a sum of Gaussians centered at the points
|
| 431 | 1 | tkerber | of the energy grid and with the specified width.
|
| 432 | 1 | tkerber | """
|
| 433 | 1 | tkerber | spec_kn = self.get_spectral_weight(w)
|
| 434 | 1 | tkerber | dos = np.zeros(len(energies))
|
| 435 | 1 | tkerber | for k, spec_n in enumerate(spec_kn): |
| 436 | 1 | tkerber | eig_n = self.calc.get_eigenvalues(k=kpt, s=self.spin) |
| 437 | 1 | tkerber | for weight, eig in zip(spec_n, eig): |
| 438 | 1 | tkerber | # Add gaussian centered at the eigenvalue
|
| 439 | 1 | tkerber | x = ((energies - center) / width)**2
|
| 440 | 1 | tkerber | dos += weight * np.exp(-x.clip(0., 40.)) / (sqrt(pi) * width) |
| 441 | 1 | tkerber | return dos
|
| 442 | 1 | tkerber | |
| 443 | 1 | tkerber | def max_spread(self, directions=[0, 1, 2]): |
| 444 | 1 | tkerber | """Returns the index of the most delocalized Wannier function
|
| 445 | 1 | tkerber | together with the value of the spread functional"""
|
| 446 | 1 | tkerber | d = np.zeros(self.nwannier)
|
| 447 | 1 | tkerber | for dir in directions: |
| 448 | 1 | tkerber | d[dir] = np.abs(self.Z_dww[dir].diagonal())**2 *self.weight_d[dir] |
| 449 | 1 | tkerber | index = np.argsort(d)[0]
|
| 450 | 1 | tkerber | print 'Index:', index |
| 451 | 1 | tkerber | print 'Spread:', d[index] |
| 452 | 1 | tkerber | |
| 453 | 1 | tkerber | def translate(self, w, R): |
| 454 | 1 | tkerber | """Translate the w'th Wannier function
|
| 455 | 1 | tkerber |
|
| 456 | 1 | tkerber | The distance vector R = [n1, n2, n3], is in units of the basis
|
| 457 | 1 | tkerber | vectors of the small cell.
|
| 458 | 1 | tkerber | """
|
| 459 | 1 | tkerber | for kpt_c, U_ww in zip(self.kpt_kc, self.U_kww): |
| 460 | 1 | tkerber | U_ww[:, w] *= np.exp(2.j * pi * np.dot(np.array(R), kpt_c))
|
| 461 | 1 | tkerber | self.update()
|
| 462 | 1 | tkerber | |
| 463 | 1 | tkerber | def translate_to_cell(self, w, cell): |
| 464 | 1 | tkerber | """Translate the w'th Wannier function to specified cell"""
|
| 465 | 1 | tkerber | scaled_c = np.angle(self.Z_dww[:3, w, w]) * self.kptgrid / (2 * pi) |
| 466 | 1 | tkerber | trans = np.array(cell) - np.floor(scaled_c) |
| 467 | 1 | tkerber | self.translate(w, trans)
|
| 468 | 1 | tkerber | |
| 469 | 1 | tkerber | def translate_all_to_cell(self, cell=[0, 0, 0]): |
| 470 | 1 | tkerber | """Translate all Wannier functions to specified cell.
|
| 471 | 1 | tkerber |
|
| 472 | 1 | tkerber | Move all Wannier orbitals to a specific unit cell. There
|
| 473 | 1 | tkerber | exists an arbitrariness in the positions of the Wannier
|
| 474 | 1 | tkerber | orbitals relative to the unit cell. This method can move all
|
| 475 | 1 | tkerber | orbitals to the unit cell specified by ``cell``. For a
|
| 476 | 1 | tkerber | `\Gamma`-point calculation, this has no effect. For a
|
| 477 | 1 | tkerber | **k**-point calculation the periodicity of the orbitals are
|
| 478 | 1 | tkerber | given by the large unit cell defined by repeating the original
|
| 479 | 1 | tkerber | unitcell by the number of **k**-points in each direction. In
|
| 480 | 1 | tkerber | this case it is usefull to move the orbitals away from the
|
| 481 | 1 | tkerber | boundaries of the large cell before plotting them. For a bulk
|
| 482 | 1 | tkerber | calculation with, say 10x10x10 **k** points, one could move
|
| 483 | 1 | tkerber | the orbitals to the cell [2,2,2]. In this way the pbc
|
| 484 | 1 | tkerber | boundary conditions will not be noticed.
|
| 485 | 1 | tkerber | """
|
| 486 | 1 | tkerber | scaled_wc = np.angle(self.Z_dww[:3].diagonal(0, 1, 2)).T * \ |
| 487 | 1 | tkerber | self.kptgrid / (2 * pi) |
| 488 | 1 | tkerber | trans_wc = np.array(cell)[None] - np.floor(scaled_wc)
|
| 489 | 1 | tkerber | for kpt_c, U_ww in zip(self.kpt_kc, self.U_kww): |
| 490 | 1 | tkerber | U_ww *= np.exp(2.j * pi * np.dot(trans_wc, kpt_c))
|
| 491 | 1 | tkerber | self.update()
|
| 492 | 1 | tkerber | |
| 493 | 1 | tkerber | def distances(self, R): |
| 494 | 1 | tkerber | Nw = self.nwannier
|
| 495 | 1 | tkerber | cen = self.get_centers()
|
| 496 | 1 | tkerber | r1 = cen.repeat(Nw, axis=0).reshape(Nw, Nw, 3) |
| 497 | 1 | tkerber | r2 = cen.copy() |
| 498 | 1 | tkerber | for i in range(3): |
| 499 | 1 | tkerber | r2 += self.unitcell_cc[i] * R[i]
|
| 500 | 1 | tkerber | |
| 501 | 1 | tkerber | r2 = np.swapaxes(r2.repeat(Nw, axis=0).reshape(Nw, Nw, 3), 0, 1) |
| 502 | 1 | tkerber | return np.sqrt(np.sum((r1 - r2)**2, axis=-1)) |
| 503 | 1 | tkerber | |
| 504 | 1 | tkerber | def get_hopping(self, R): |
| 505 | 1 | tkerber | """Returns the matrix H(R)_nm=<0,n|H|R,m>.
|
| 506 | 1 | tkerber |
|
| 507 | 1 | tkerber | ::
|
| 508 | 1 | tkerber |
|
| 509 | 1 | tkerber | 1 _ -ik.R
|
| 510 | 1 | tkerber | H(R) = <0,n|H|R,m> = --- >_ e H(k)
|
| 511 | 1 | tkerber | Nk k
|
| 512 | 1 | tkerber |
|
| 513 | 1 | tkerber | where R is the cell-distance (in units of the basis vectors of
|
| 514 | 1 | tkerber | the small cell) and n,m are indices of the Wannier functions.
|
| 515 | 1 | tkerber | """
|
| 516 | 1 | tkerber | H_ww = np.zeros([self.nwannier, self.nwannier], complex) |
| 517 | 1 | tkerber | for k, kpt_c in enumerate(self.kpt_kc): |
| 518 | 1 | tkerber | phase = np.exp(-2.j * pi * np.dot(np.array(R), kpt_c))
|
| 519 | 1 | tkerber | H_ww += self.get_hamiltonian(k) * phase
|
| 520 | 1 | tkerber | return H_ww / self.Nk |
| 521 | 1 | tkerber | |
| 522 | 1 | tkerber | def get_hamiltonian(self, k=0): |
| 523 | 1 | tkerber | """Get Hamiltonian at existing k-vector of index k
|
| 524 | 1 | tkerber |
|
| 525 | 1 | tkerber | ::
|
| 526 | 1 | tkerber |
|
| 527 | 1 | tkerber | dag
|
| 528 | 1 | tkerber | H(k) = V diag(eps ) V
|
| 529 | 1 | tkerber | k k k
|
| 530 | 1 | tkerber | """
|
| 531 | 1 | tkerber | eps_n = self.calc.get_eigenvalues(kpt=k, spin=self.spin) |
| 532 | 1 | tkerber | return np.dot(dag(self.V_knw[k]) * eps_n, self.V_knw[k]) |
| 533 | 1 | tkerber | |
| 534 | 1 | tkerber | def get_hamiltonian_kpoint(self, kpt_c): |
| 535 | 1 | tkerber | """Get Hamiltonian at some new arbitrary k-vector
|
| 536 | 1 | tkerber |
|
| 537 | 1 | tkerber | ::
|
| 538 | 1 | tkerber |
|
| 539 | 1 | tkerber | _ ik.R
|
| 540 | 1 | tkerber | H(k) = >_ e H(R)
|
| 541 | 1 | tkerber | R
|
| 542 | 1 | tkerber |
|
| 543 | 1 | tkerber | Warning: This method moves all Wannier functions to cell (0, 0, 0)
|
| 544 | 1 | tkerber | """
|
| 545 | 1 | tkerber | if self.verbose: |
| 546 | 1 | tkerber | print 'Translating all Wannier functions to cell (0, 0, 0)' |
| 547 | 1 | tkerber | self.translate_all_to_cell()
|
| 548 | 1 | tkerber | max = (self.kptgrid - 1) / 2 |
| 549 | 1 | tkerber | max += max > 0 |
| 550 | 1 | tkerber | N1, N2, N3 = max
|
| 551 | 1 | tkerber | Hk = np.zeros([self.nwannier, self.nwannier], complex) |
| 552 | 1 | tkerber | for n1 in xrange(-N1, N1 + 1): |
| 553 | 1 | tkerber | for n2 in xrange(-N2, N2 + 1): |
| 554 | 1 | tkerber | for n3 in xrange(-N3, N3 + 1): |
| 555 | 1 | tkerber | R = np.array([n1, n2, n3], float)
|
| 556 | 1 | tkerber | hop_ww = self.get_hopping(R)
|
| 557 | 1 | tkerber | phase = np.exp(+2.j * pi * np.dot(R, kpt_c))
|
| 558 | 1 | tkerber | Hk += hop_ww * phase |
| 559 | 1 | tkerber | return Hk
|
| 560 | 1 | tkerber | |
| 561 | 1 | tkerber | def get_function(self, index, repeat=None): |
| 562 | 1 | tkerber | """Get Wannier function on grid.
|
| 563 | 1 | tkerber |
|
| 564 | 1 | tkerber | Returns an array with the funcion values of the indicated Wannier
|
| 565 | 1 | tkerber | function on a grid with the size of the *repeated* unit cell.
|
| 566 | 1 | tkerber |
|
| 567 | 1 | tkerber | For a calculation using **k**-points the relevant unit cell for
|
| 568 | 1 | tkerber | eg. visualization of the Wannier orbitals is not the original unit
|
| 569 | 1 | tkerber | cell, but rather a larger unit cell defined by repeating the
|
| 570 | 1 | tkerber | original unit cell by the number of **k**-points in each direction.
|
| 571 | 1 | tkerber | Note that for a `\Gamma`-point calculation the large unit cell
|
| 572 | 1 | tkerber | coinsides with the original unit cell.
|
| 573 | 1 | tkerber | The large unitcell also defines the periodicity of the Wannier
|
| 574 | 1 | tkerber | orbitals.
|
| 575 | 1 | tkerber |
|
| 576 | 1 | tkerber | ``index`` can be either a single WF or a coordinate vector in terms
|
| 577 | 1 | tkerber | of the WFs.
|
| 578 | 1 | tkerber | """
|
| 579 | 1 | tkerber | |
| 580 | 1 | tkerber | # Default size of plotting cell is the one corresponding to k-points.
|
| 581 | 1 | tkerber | if repeat is None: |
| 582 | 1 | tkerber | repeat = self.kptgrid
|
| 583 | 1 | tkerber | N1, N2, N3 = repeat |
| 584 | 1 | tkerber | |
| 585 | 1 | tkerber | dim = self.calc.get_number_of_grid_points()
|
| 586 | 1 | tkerber | largedim = dim * [N1, N2, N3] |
| 587 | 1 | tkerber | |
| 588 | 1 | tkerber | wanniergrid = np.zeros(largedim, dtype=complex)
|
| 589 | 1 | tkerber | for k, kpt_c in enumerate(self.kpt_kc): |
| 590 | 1 | tkerber | # The coordinate vector of wannier functions
|
| 591 | 1 | tkerber | if type(index) == int: |
| 592 | 1 | tkerber | vec_n = self.V_knw[k, :, index]
|
| 593 | 1 | tkerber | else:
|
| 594 | 1 | tkerber | vec_n = np.dot(self.V_knw[k], index)
|
| 595 | 1 | tkerber | |
| 596 | 1 | tkerber | wan_G = np.zeros(dim, complex)
|
| 597 | 1 | tkerber | for n, coeff in enumerate(vec_n): |
| 598 | 1 | tkerber | wan_G += coeff * self.calc.get_pseudo_wave_function(
|
| 599 | 1 | tkerber | n, k, self.spin, pad=True) |
| 600 | 1 | tkerber | |
| 601 | 1 | tkerber | # Distribute the small wavefunction over large cell:
|
| 602 | 1 | tkerber | for n1 in xrange(N1): |
| 603 | 1 | tkerber | for n2 in xrange(N2): |
| 604 | 1 | tkerber | for n3 in xrange(N3): # sign? |
| 605 | 1 | tkerber | e = np.exp(-2.j * pi * np.dot([n1, n2, n3], kpt_c))
|
| 606 | 1 | tkerber | wanniergrid[n1 * dim[0]:(n1 + 1) * dim[0], |
| 607 | 1 | tkerber | n2 * dim[1]:(n2 + 1) * dim[1], |
| 608 | 1 | tkerber | n3 * dim[2]:(n3 + 1) * dim[2]] += e * wan_G |
| 609 | 1 | tkerber | |
| 610 | 1 | tkerber | # Normalization
|
| 611 | 1 | tkerber | wanniergrid /= np.sqrt(self.Nk)
|
| 612 | 1 | tkerber | return wanniergrid
|
| 613 | 1 | tkerber | |
| 614 | 1 | tkerber | def write_cube(self, index, fname, repeat=None, real=True): |
| 615 | 1 | tkerber | """Dump specified Wannier function to a cube file"""
|
| 616 | 1 | tkerber | from ase.io.cube import write_cube |
| 617 | 1 | tkerber | |
| 618 | 1 | tkerber | # Default size of plotting cell is the one corresponding to k-points.
|
| 619 | 1 | tkerber | if repeat is None: |
| 620 | 1 | tkerber | repeat = self.kptgrid
|
| 621 | 1 | tkerber | atoms = self.calc.get_atoms() * repeat
|
| 622 | 1 | tkerber | func = self.get_function(index, repeat)
|
| 623 | 1 | tkerber | |
| 624 | 1 | tkerber | # Handle separation of complex wave into real parts
|
| 625 | 1 | tkerber | if real:
|
| 626 | 1 | tkerber | if self.Nk == 1: |
| 627 | 1 | tkerber | func *= np.exp(-1.j * np.angle(func.max()))
|
| 628 | 1 | tkerber | if 0: assert max(abs(func.imag).flat) < 1e-4 |
| 629 | 1 | tkerber | func = func.real |
| 630 | 1 | tkerber | else:
|
| 631 | 1 | tkerber | func = abs(func)
|
| 632 | 1 | tkerber | else:
|
| 633 | 1 | tkerber | phase_fname = fname.split('.')
|
| 634 | 1 | tkerber | phase_fname.insert(1, 'phase') |
| 635 | 1 | tkerber | phase_fname = '.'.join(phase_fname)
|
| 636 | 1 | tkerber | write_cube(phase_fname, atoms, data=np.angle(func)) |
| 637 | 1 | tkerber | func = abs(func)
|
| 638 | 1 | tkerber | |
| 639 | 1 | tkerber | write_cube(fname, atoms, data=func) |
| 640 | 1 | tkerber | |
| 641 | 1 | tkerber | def localize(self, step=0.25, tolerance=1e-08, |
| 642 | 1 | tkerber | updaterot=True, updatecoeff=True): |
| 643 | 1 | tkerber | """Optimize rotation to give maximal localization"""
|
| 644 | 1 | tkerber | md_min(self, step, tolerance, verbose=self.verbose, |
| 645 | 1 | tkerber | updaterot=updaterot, updatecoeff=updatecoeff) |
| 646 | 1 | tkerber | |
| 647 | 1 | tkerber | def get_functional_value(self): |
| 648 | 1 | tkerber | """Calculate the value of the spread functional.
|
| 649 | 1 | tkerber |
|
| 650 | 1 | tkerber | ::
|
| 651 | 1 | tkerber |
|
| 652 | 1 | tkerber | Tr[|ZI|^2]=sum(I)sum(n) w_i|Z_(i)_nn|^2,
|
| 653 | 1 | tkerber |
|
| 654 | 1 | tkerber | where w_i are weights."""
|
| 655 | 1 | tkerber | a_d = np.sum(np.abs(self.Z_dww.diagonal(0, 1, 2))**2, axis=1) |
| 656 | 1 | tkerber | return np.dot(a_d, self.weight_d).real |
| 657 | 1 | tkerber | |
| 658 | 1 | tkerber | def get_gradients(self): |
| 659 | 1 | tkerber | # Determine gradient of the spread functional.
|
| 660 | 1 | tkerber | #
|
| 661 | 1 | tkerber | # The gradient for a rotation A_kij is::
|
| 662 | 1 | tkerber | #
|
| 663 | 1 | tkerber | # dU = dRho/dA_{k,i,j} = sum(I) sum(k')
|
| 664 | 1 | tkerber | # + Z_jj Z_kk',ij^* - Z_ii Z_k'k,ij^*
|
| 665 | 1 | tkerber | # - Z_ii^* Z_kk',ji + Z_jj^* Z_k'k,ji
|
| 666 | 1 | tkerber | #
|
| 667 | 1 | tkerber | # The gradient for a change of coefficients is::
|
| 668 | 1 | tkerber | #
|
| 669 | 1 | tkerber | # dRho/da^*_{k,i,j} = sum(I) [[(Z_0)_{k} V_{k'} diag(Z^*) +
|
| 670 | 1 | tkerber | # (Z_0_{k''})^d V_{k''} diag(Z)] *
|
| 671 | 1 | tkerber | # U_k^d]_{N+i,N+j}
|
| 672 | 1 | tkerber | #
|
| 673 | 1 | tkerber | # where diag(Z) is a square,diagonal matrix with Z_nn in the diagonal,
|
| 674 | 1 | tkerber | # k' = k + dk and k = k'' + dk.
|
| 675 | 1 | tkerber | #
|
| 676 | 1 | tkerber | # The extra degrees of freedom chould be kept orthonormal to the fixed
|
| 677 | 1 | tkerber | # space, thus we introduce lagrange multipliers, and minimize instead::
|
| 678 | 1 | tkerber | #
|
| 679 | 1 | tkerber | # Rho_L=Rho- sum_{k,n,m} lambda_{k,nm} <c_{kn}|c_{km}>
|
| 680 | 1 | tkerber | #
|
| 681 | 1 | tkerber | # for this reason the coefficient gradients should be multiplied
|
| 682 | 1 | tkerber | # by (1 - c c^d).
|
| 683 | 1 | tkerber | |
| 684 | 1 | tkerber | Nb = self.nbands
|
| 685 | 1 | tkerber | Nw = self.nwannier
|
| 686 | 1 | tkerber | |
| 687 | 1 | tkerber | dU = [] |
| 688 | 1 | tkerber | dC = [] |
| 689 | 1 | tkerber | for k in xrange(self.Nk): |
| 690 | 1 | tkerber | M = self.fixedstates_k[k]
|
| 691 | 1 | tkerber | L = self.edf_k[k]
|
| 692 | 1 | tkerber | U_ww = self.U_kww[k]
|
| 693 | 1 | tkerber | C_ul = self.C_kul[k]
|
| 694 | 1 | tkerber | Utemp_ww = np.zeros((Nw, Nw), complex)
|
| 695 | 1 | tkerber | Ctemp_nw = np.zeros((Nb, Nw), complex)
|
| 696 | 1 | tkerber | |
| 697 | 1 | tkerber | for d, weight in enumerate(self.weight_d): |
| 698 | 1 | tkerber | if abs(weight) < 1.0e-6: |
| 699 | 1 | tkerber | continue
|
| 700 | 1 | tkerber | |
| 701 | 1 | tkerber | Z_knn = self.Z_dknn[d]
|
| 702 | 1 | tkerber | diagZ_w = self.Z_dww[d].diagonal()
|
| 703 | 1 | tkerber | Zii_ww = np.repeat(diagZ_w, Nw).reshape(Nw, Nw) |
| 704 | 1 | tkerber | k1 = self.kklst_dk[d, k]
|
| 705 | 1 | tkerber | k2 = self.invkklst_dk[d, k]
|
| 706 | 1 | tkerber | V_knw = self.V_knw
|
| 707 | 1 | tkerber | Z_kww = self.Z_dkww[d]
|
| 708 | 1 | tkerber | |
| 709 | 1 | tkerber | if L > 0: |
| 710 | 1 | tkerber | Ctemp_nw += weight * np.dot( |
| 711 | 1 | tkerber | np.dot(Z_knn[k], V_knw[k1]) * diagZ_w.conj() + |
| 712 | 1 | tkerber | np.dot(dag(Z_knn[k2]), V_knw[k2]) * diagZ_w, |
| 713 | 1 | tkerber | dag(U_ww)) |
| 714 | 1 | tkerber | |
| 715 | 1 | tkerber | temp = Zii_ww.T * Z_kww[k].conj() - Zii_ww * Z_kww[k2].conj() |
| 716 | 1 | tkerber | Utemp_ww += weight * (temp - dag(temp)) |
| 717 | 1 | tkerber | dU.append(Utemp_ww.ravel()) |
| 718 | 1 | tkerber | if L > 0: |
| 719 | 1 | tkerber | # Ctemp now has same dimension as V, the gradient is in the
|
| 720 | 1 | tkerber | # lower-right (Nb-M) x L block
|
| 721 | 1 | tkerber | Ctemp_ul = Ctemp_nw[M:, M:] |
| 722 | 1 | tkerber | G_ul = Ctemp_ul - np.dot(np.dot(C_ul, dag(C_ul)), Ctemp_ul) |
| 723 | 1 | tkerber | dC.append(G_ul.ravel()) |
| 724 | 1 | tkerber | |
| 725 | 1 | tkerber | return np.concatenate(dU + dC)
|
| 726 | 1 | tkerber | |
| 727 | 1 | tkerber | def step(self, dX, updaterot=True, updatecoeff=True): |
| 728 | 1 | tkerber | # dX is (A, dC) where U->Uexp(-A) and C->C+dC
|
| 729 | 1 | tkerber | Nw = self.nwannier
|
| 730 | 1 | tkerber | Nk = self.Nk
|
| 731 | 1 | tkerber | M_k = self.fixedstates_k
|
| 732 | 1 | tkerber | L_k = self.edf_k
|
| 733 | 1 | tkerber | if updaterot:
|
| 734 | 1 | tkerber | A_kww = dX[:Nk * Nw**2].reshape(Nk, Nw, Nw)
|
| 735 | 1 | tkerber | for U, A in zip(self.U_kww, A_kww): |
| 736 | 1 | tkerber | H = -1.j * A.conj()
|
| 737 | 1 | tkerber | epsilon, Z = np.linalg.eigh(H) |
| 738 | 1 | tkerber | # Z contains the eigenvectors as COLUMNS.
|
| 739 | 1 | tkerber | # Since H = iA, dU = exp(-A) = exp(iH) = ZDZ^d
|
| 740 | 1 | tkerber | dU = np.dot(Z * np.exp(1.j * epsilon), dag(Z))
|
| 741 | 1 | tkerber | U[:] = np.dot(U, dU) |
| 742 | 1 | tkerber | |
| 743 | 1 | tkerber | if updatecoeff:
|
| 744 | 1 | tkerber | start = 0
|
| 745 | 1 | tkerber | for C, unocc, L in zip(self.C_kul, self.nbands - M_k, L_k): |
| 746 | 1 | tkerber | if L == 0 or unocc == 0: |
| 747 | 1 | tkerber | continue
|
| 748 | 1 | tkerber | Ncoeff = L * unocc |
| 749 | 1 | tkerber | deltaC = dX[Nk * Nw**2 + start: Nk * Nw**2 + start + Ncoeff] |
| 750 | 1 | tkerber | C += deltaC.reshape(unocc, L) |
| 751 | 1 | tkerber | gram_schmidt(C) |
| 752 | 1 | tkerber | start += Ncoeff |
| 753 | 1 | tkerber | |
| 754 | 1 | tkerber | self.update() |