From 478e1236e7d942abb45f94dc27eea613e46da539 Mon Sep 17 00:00:00 2001 From: Antonio Manesco <am@antoniomanesco.org> Date: Wed, 15 Nov 2023 15:35:14 +0100 Subject: [PATCH] some progress, not there yet --- codes/hf.py | 166 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 100 insertions(+), 66 deletions(-) diff --git a/codes/hf.py b/codes/hf.py index e26fa58..0f4210b 100644 --- a/codes/hf.py +++ b/codes/hf.py @@ -2,10 +2,9 @@ from scipy.ndimage import convolve import numpy as np import codes.utils as utils from functools import partial -from scipy.optimize import anderson +from scipy.optimize import anderson, minimize - -def mean_field_F(vals, vecs, E_F): +def density_matrix(vals, vecs, E_F): """ Returns the mean field F_ij(k) = <psi_i(k)|psi_j(k)> for all k-points and eigenvectors below the Fermi level. @@ -37,19 +36,19 @@ def mean_field_F(vals, vecs, E_F): occ_vecs_flat = np.transpose(occ_vecs_flat, axes=[0, 2, 1]) # inner products between eigenvectors - F_ij = np.einsum("kie,kje->kij", occ_vecs_flat, occ_vecs_flat.conj()) + rho_ij = np.einsum("kie,kje->kij", occ_vecs_flat, occ_vecs_flat.conj()) reshape_order = [nk for i in range(dim)] reshape_order.extend([norbs, norbs]) - F = F_ij.reshape(*reshape_order) + rho = rho_ij.reshape(*reshape_order) else: unocc_vals = vals > E_F occ_vecs = vecs occ_vecs[:, unocc_vals] = 0 # Outter products between eigenvectors - F = occ_vecs @ occ_vecs.T.conj() + rho = occ_vecs @ occ_vecs.T.conj() - return F + return rho def convolution(M1, M2): @@ -82,7 +81,7 @@ def convolution(M1, M2): return V_output -def compute_mf(vals, vecs, filling, H_int): +def compute_mf(rho, H_int): """ Compute mean-field correction at self-consistent loop. @@ -102,25 +101,69 @@ def compute_mf(vals, vecs, filling, H_int): mf : nd-array Meanf-field correction with same format as `H_int`. """ - dim = len(vals.shape) - 1 + + nk = rho.shape[0] + dim = len(rho.shape) - 2 + + if dim > 0: + H0_int = H_int[*([0]*dim)] + local_density = np.diag(np.average(rho, axis=tuple([i for i in range(dim)]))) + exchange_mf = convolution(rho, H_int) * nk ** (-dim) + direct_mf = np.diag(np.einsum("i,ij->j", local_density, H0_int)) + else: + local_density = np.diag(rho) + exchange_mf = rho * H_int + direct_mf = np.diag(np.einsum("i,ij->j", local_density, H_int)) + return direct_mf - exchange_mf + +def total_energy(h, rho): + return np.sum(np.trace(h @ rho, axis1=-1, axis2=-2)) + +class Model: + + def __init__(self, tb_model, int_model=None, Vk=None, guess=None): + self.tb_model = tb_model + self.hk = utils.model2hk(tb_model=tb_model) + if int_model is not None: + self.int_model = int_model + self.Vk = utils.model2hk(tb_model=int_model) + else: + self.Vk = Vk + self.dim = len([*tb_model.keys()][0]) + self.ndof = len([*tb_model.values()][0]) + self.guess = guess + + + def random_guess(self, vectors): + self.guess = utils.generate_guess( + vectors=vectors, + ndof=self.ndof, + scale=1 + ) - nk = vals.shape[0] + def kgrid_evaluation(self, nk): + self.hamiltonians_0, self.ks = utils.kgrid_hamiltonian( + nk=nk, + hk=self.hk, + dim=self.dim, + return_ks=True + ) + self.H_int = utils.kgrid_hamiltonian(nk=nk, hk=self.Vk, dim=self.dim) + self.mf_k = utils.kgrid_hamiltonian( + nk=nk, + hk=utils.model2hk(self.guess), + dim=self.dim, + ) - E_F = utils.get_fermi_energy(vals, filling) - F = mean_field_F(vals=vals, vecs=vecs, E_F=E_F) + def flatten_mf(self): + flat = self.mf_k.flatten() + return flat[:len(flat)//2] + 1j * flat[len(flat)//2:] - if dim > 0: - H0_int = H_int[*[0 for i in range(dim)]] - rho = np.diag(np.average(F, axis=tuple([i for i in range(dim)]))) - exchange_mf = convolution(F, H_int) * nk ** (-dim) - direct_mf = np.diag(np.einsum("i,ij->j", rho, H0_int)) - else: - rho = np.diag(F) - exchange_mf = F * H_int - direct_mf = np.diag(np.einsum("i,ij->j", rho, H_int)) - return direct_mf - exchange_mf - E_F * np.eye(H0_int.shape[-1]) + def reshape_mf(self, mf_flat): + mf_flat = np.concatenate((np.real(mf_flat), np.imag(mf_flat))) + return mf_flat.reshape(*self.hamiltonians_0.shape) -def update_mf(mf, H_int, filling, hamiltonians_0): +def updated_matrices(mf_k, model): """ Self-consistent loop. @@ -141,30 +184,32 @@ def update_mf(mf, H_int, filling, hamiltonians_0): Updated mean-field solution. """ # Generate the Hamiltonian - hamiltonians = hamiltonians_0 + mf + hamiltonians = model.hamiltonians_0 + mf_k vals, vecs = np.linalg.eigh(hamiltonians) vecs = np.linalg.qr(vecs)[0] - - return compute_mf(vals=vals, vecs=vecs, filling=filling, H_int=H_int) - -def default_cost(mf, H_int, filling, hamiltonians_0): - mf_new = update_mf(mf, H_int, filling, hamiltonians_0) - diff = mf_new - mf - return diff - + E_F = utils.get_fermi_energy(vals, model.filling) + rho = density_matrix(vals=vals, vecs=vecs, E_F=E_F) + return rho, compute_mf(rho=rho, H_int=model.H_int) - E_F * np.eye(model.hamiltonians_0.shape[-1]) + +def default_cost(mf, model): + model.rho, model.mf_k = updated_matrices(mf_k=mf, model=model) + model.energy = total_energy(h=model.hamiltonians_0 + model.mf_k, rho=model.rho) + h = model.hamiltonians_0 + model.mf_k + commutator = h@model.rho - model.rho@h + n_herm = (mf - np.moveaxis(mf, -1, -2).conj())/2 + delta_mf = model.mf_k - mf + quantities = np.array([commutator, delta_mf, n_herm]) + idx_max = np.argmax(np.linalg.norm(quantities.reshape(3, -1), axis=-1)) + return quantities[idx_max] def find_groundstate_ham( - hk, - Vk, - cutoff, - dim, + model, + cutoff_Vk, filling, nk=10, cost_function=default_cost, - guess=None, optimizer=anderson, optimizer_kwargs={}, - return_model=False ): """ Self-consistent loop to find groundstate Hamiltonian. @@ -187,33 +232,22 @@ def find_groundstate_ham( scf_model : dict Tight-binding model of Hartree-Fock solution. """ - hamiltonians_0, ks = utils.kgrid_hamiltonian( - nk=nk, - hk=hk, - dim=dim, - return_ks=True - ) - H_int = utils.kgrid_hamiltonian(nk=nk, hk=Vk, dim=dim) - vectors = utils.generate_vectors(cutoff, dim) - if guess is None: - guess = utils.generate_guess( - vectors=vectors, - ndof=hamiltonians_0.shape[-1], - scale=np.max(np.abs(H_int)) - ) - guess_k = utils.kgrid_hamiltonian( - nk=nk, - hk=utils.model2hk(guess), - dim=dim, - ) - cost_function_wrapped = partial( + model.nk=nk + model.filling=filling + vectors = utils.generate_vectors(cutoff_Vk, model.dim) + model.vectors=[*vectors, *model.tb_model.keys()] + if model.guess is None: + model.random_guess(model.vectors) + model.kgrid_evaluation(nk=nk) + fun = partial( cost_function, - hamiltonians_0=hamiltonians_0, - H_int=H_int, - filling=filling, + model=model ) - if return_model: - mf_k = optimizer(cost_function_wrapped, guess_k, **optimizer_kwargs) - return mf_k, utils.hk2tb_model(hamiltonians_0 + mf_k, vectors, ks) - else: - return optimizer(cost_function_wrapped, guess_k, **optimizer_kwargs) + mf_k = optimizer( + fun, + model.mf_k, + **optimizer_kwargs + ) + assert np.allclose((mf_k - np.moveaxis(mf_k, -1, -2).conj())/2, 0, atol=1e-5) + mf_k = (mf_k + np.moveaxis(mf_k, -1, -2).conj())/2 + return utils.hk2tb_model(model.hamiltonians_0 + mf_k, model.vectors, model.ks) -- GitLab