From d447933f84b1ec0320e8c6116396a94d8bb2d09b Mon Sep 17 00:00:00 2001
From: Antonio Manesco <am@antoniomanesco.org>
Date: Tue, 19 Dec 2023 18:44:11 +0100
Subject: [PATCH] update solver

---
 codes/hf.py | 169 +++++++++++++++++++++++++++++++---------------------
 1 file changed, 102 insertions(+), 67 deletions(-)

diff --git a/codes/hf.py b/codes/hf.py
index 0f4210b..008fcd4 100644
--- a/codes/hf.py
+++ b/codes/hf.py
@@ -20,8 +20,8 @@ def density_matrix(vals, vecs, E_F):
 
     Returns
     -------
-    F : array_like
-        mean field F[kx, ky, ..., i, j] where i,j are cell indices.
+    rho : array_like
+        Density matrix rho=rho[kx, ky, ..., i, j] where i,j are cell indices.
     """
     norbs = vals.shape[-1]
     dim = len(vals.shape) - 1
@@ -87,14 +87,10 @@ def compute_mf(rho, H_int):
 
     Parameters:
     -----------
-    vals : nd-array
-        Eigenvalues of current loop vals[k_x, ..., k_n, j].
-    vecs : nd_array
-        Eigenvectors of current loop vals[k_x, ..., k_n, i, j].
+    rho : nd_array
+        Density matrix.
     H_int : nd-array
         Interaction matrix.
-    filling: int
-        Number of electrons per cell.
 
     Returns:
     --------
@@ -117,51 +113,22 @@ def compute_mf(rho, 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
-        )
-
-    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,
-        )
+    """
+    Compute total energy.
 
-    def flatten_mf(self):
-        flat = self.mf_k.flatten()
-        return flat[:len(flat)//2] + 1j * flat[len(flat)//2:]
+    Paramters:
+    ----------
+    h : nd-array
+        Hamiltonian.
+    rho : nd-array
+        Density matrix.
 
-    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)
+    Returns:
+    --------
+    total_energy : float
+        System total energy computed as tr[h@rho].
+    """
+    return np.sum(np.trace(h @ rho, axis1=-1, axis2=-2))
 
 def updated_matrices(mf_k, model):
     """
@@ -192,24 +159,102 @@ def updated_matrices(mf_k, model):
     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):
+    """
+    Default cost function.
+
+    Parameters:
+    -----------
+    mf : nd-array
+        Mean-field correction evaluated on a k-point grid.
+    model : Model
+        Physical model.
+
+    Returns:
+    --------
+    cost : nd-array
+        Array with same shape as input. Is chosen to be the largest value between:
+            * Difference between intial and final mean-field correction.
+            * Non-hermitian part of the mean-field correction.
+            * The commutator between the mean-field Hamiltonian and density matrix.
+    """
+    mf_dict = {}
+    for i, key in enumerate(model.guess.keys()):
+        mf_dict[key] = mf[i]
+    mf = utils.kgrid_hamiltonian(
+        nk=model.nk,
+        hk=utils.model2hk(mf_dict),
+        dim=model.dim,
+        hermitian=False
+    )
     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
+    commutator = (h@model.rho - model.rho@h)
     n_herm = (mf - np.moveaxis(mf, -1, -2).conj())/2
     delta_mf = model.mf_k - mf
+    n_herm = [*utils.hk2tb_model(n_herm, model.vectors, model.ks).values()]
+    commutator = [*utils.hk2tb_model(commutator, model.vectors, model.ks).values()]
+    delta_mf = [*utils.hk2tb_model(delta_mf, model.vectors, model.ks).values()]
     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 kspace_solver(model, nk, optimizer, optimizer_kwargs):
+    """
+    Default cost function.
+
+    Parameters:
+    -----------
+    mf : nd-array
+        Mean-field correction evaluated on a k-point grid.
+    model : Model
+        Physical model.
+
+    Returns:
+    --------
+    cost : nd-array
+        Array with same shape as input. Is chosen to be the largest value between:
+            * Difference between intial and final mean-field correction.
+            * Non-hermitian part of the mean-field correction.
+            * The commutator between the mean-field Hamiltonian and density matrix.
+    """
+    model.kgrid_evaluation(nk=nk)
+    def cost_function(mf):
+        mf = utils.flat_to_matrix(utils.real_to_complex(mf), model.mf_k.shape)
+        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)
+        delta_mf = model.mf_k - mf
+        return utils.matrix_to_flat(utils.complex_to_real(delta_mf))
+
+    mf = optimizer(
+        cost_function,
+        utils.matrix_to_flat(utils.complex_to_real(model.mf_k)),
+        **optimizer_kwargs
+    )
+    mf = utils.flat_to_matrix(utils.real_to_complex(mf), model.mf_k.shape)
+    h = model.hamiltonians_0 + mf
+    commutator = (h@model.rho - model.rho@h)
+    while np.invert(np.isclose(commutator, 0, atol=1e-12)).all():
+        model.random_guess(model.vectors)
+        model.kgrid_evaluation(nk=nk)
+        mf = optimizer(
+            cost_function,
+            utils.matrix_to_flat(utils.complex_to_real(model.mf_k)),
+            **optimizer_kwargs
+        )
+        mf = utils.flat_to_matrix(utils.real_to_complex(mf), model.mf_k.shape)
+        h = model.hamiltonians_0 + mf
+        commutator = (h@model.rho - model.rho@h)
+
+
 def find_groundstate_ham(
     model,
     cutoff_Vk,
     filling,
     nk=10,
-    cost_function=default_cost,
+    solver=kspace_solver,
     optimizer=anderson,
-    optimizer_kwargs={},
+    optimizer_kwargs={'f_tol' : 1e-6},
 ):
     """
     Self-consistent loop to find groundstate Hamiltonian.
@@ -238,16 +283,6 @@ def find_groundstate_ham(
     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,
-        model=model
-    )
-    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)
+    solver(model, nk, optimizer, optimizer_kwargs)
+    assert np.allclose((model.mf_k - np.moveaxis(model.mf_k, -1, -2).conj())/2, 0, atol=1e-15)
+    return utils.hk2tb_model(model.hamiltonians_0 + model.mf_k, model.vectors, model.ks)
-- 
GitLab