Source code for pydlr.pydlr

""" Discrete Lehman Representation (DLR) implementation
using Numpy and Scipy."""
"""
Copyright 2021 Hugo U.R. Strand

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing
permissions and limitations under the License."""

 
import numpy as np
import numpy.polynomial.legendre as leg

from scipy.linalg import eigh as scipy_eigh 
from scipy.linalg import lu_solve, lu_factor


from .kernel import kernel, KernelInterpolativeDecoposition


[docs]class dlr(object): """ Discrete Lehmann Representation (DLR) class. Provides the DRL basis, transforms, and algorithms. Parameters ---------- lamb : float DLR scale parameter :math:`\\Lambda`. eps : float Set accuracy of the DLR representation. xi : sign, optional, Statistical sign :math:`\\xi = \\pm 1` for bosons and fermions respectively. max_rank : int, optional Maximum rank of the DLR kernel decomposition. Default 500. nmax : int, optional Maxumum index of the Matsubara frequency grid. Default int(lamb). verbose : bool, optional Default `False`. python_impl : bool, optional Switch between the python and fortran library driver. Default `True`. """ def __init__(self, lamb, eps=1e-15, xi=-1, max_rank=500, nmax=None, verbose=False, python_impl=True): self.xi = xi self.lamb = lamb self.eps = eps if not python_impl: from .kernel_fortran import KernelInterpolativeDecopositionFortran KID = KernelInterpolativeDecopositionFortran else: KID = KernelInterpolativeDecoposition kid = KID(lamb, eps=eps, xi=xi, max_rank=max_rank, nmax=nmax, verbose=verbose) members = [ 'rank', 'dlrit', 'dlrrf', 'dlrmf', 'dlrit2cf', 'it2cfpiv', 'dlrmf2cf', 'mf2cfpiv', 'T_lx', 'T_qx', ] for member in members: setattr(self, member, getattr(kid, member)) del kid # -- Split real-frequency nodes (assuming sorted) self.pm_idx = np.argwhere(self.dlrrf > 0)[0,0] self.dlrrf_p = self.dlrrf[self.pm_idx:] self.dlrrf_m = self.dlrrf[:self.pm_idx] self.kernel_nominator_p = 1 / (1 + np.exp(-self.dlrrf_p)) self.kernel_nominator_m = 1 / (1 + np.exp(+self.dlrrf_m)) # -- Auxilliary variables tau_l = self.get_tau(1.) w_x = self.dlrrf I = np.eye(len(w_x)) self.W_xx = 1. / (I + w_x[:, None] - w_x[None, :]) - I self.k1_x = -np.squeeze(kernel(np.ones(1), w_x)) self.TtT_xx = self.dlr_from_tau(tau_l[:, None] * self.T_lx) self.__bosonic_corr_freq = lambda omega: np.tanh(0.5 * omega) self.bosonic_corr_x = self.__bosonic_corr_freq(self.dlrrf) self.W_bc_xx = self.W_xx * self.bosonic_corr_x[:, None] self.TtT_bc_xx = self.TtT_xx * self.bosonic_corr_x[None, :] def __len__(self): """The length of the DLR expansion Returns ------- rank : int Number of DLR expansion coefficients. """ return self.rank def __xi_arg(self, xi): """Internal helper function to filter xi arguments.""" if xi is None: xi = self.xi assert( np.abs(xi) == 1 and type(xi) == int ) return xi
[docs] def get_dlr_frequencies(self): """Get real frequency DLR grid :math:`\\omega_x \\in [-\\Lambda, \\Lambda]` Returns ------- w_x : (n), ndarray Scaled real frequency points :math:`\\omega_x` for the DLR representation. """ return self.dlrrf
# -- Imaginary time
[docs] def get_tau_over_beta(self): """Get grid in scaled imaginary time :math:`\\tau/\\beta \\in [0, 1]` Returns ------- ttau_l : (n), ndarray Scaled imaginary time points :math:`\\tilde{\\tau}_l = \\tau_l / \\beta` for the DLR representation, :math:`\\tilde{\\tau}_l \\in [0, 1]`. """ ttau_l = self.dlrit return ttau_l
[docs] def get_tau(self, beta): """Get :math:`\\tau`-grid in imaginary time :math:`\\tau \\in [0, \\beta]`. Parameters ---------- beta : float Inverse temperature :math:`\\beta` Returns ------- tau_l : (n), ndarray Imaginary time points :math:`\\tau_l` for the DLR representation, :math:`\\tau_l \\in [0, \\beta]`. """ tau_l = self.get_tau_over_beta() * beta return tau_l
[docs] def dlr_from_tau(self, G_laa): """Transform the rank-3 array_like Green's function `G_laa` from imaginary time to DLR space. Parameters ---------- G_laa : (n,m,m), array_like Green's function in imaginary time with :math:`m \\times m` orbital indices. Returns ------- G_xaa : (n,m,m), ndarray Green's function i DLR coefficient space with :math:`m \\times m` orbital indices. """ G_xaa = lu_solve((self.dlrit2cf, self.it2cfpiv), G_laa) return G_xaa
[docs] def tau_from_dlr(self, G_xaa): """Transform the rank-3 array_like Green's function `G_xaa` from DLR space to imaginary time. Parameters ---------- G_xaa : (n,m,m), array_like Green's function in DLR coefficient space with :math:`m \\times m` orbital indices. Returns ------- G_laa : (n,m,m), ndarray Green's function in imaginary time with :math:`m \\times m` orbital indices. """ G_laa = np.tensordot(self.T_lx, G_xaa, axes=(1, 0)) return G_laa
[docs] def tau_from_legendre(self, G_naa): """Transform the rank-3 array_like Green's function `G_naa` from Legendre coefficient space to imaginary time. Parameters ---------- G_naa : (n,m,m), array_like Green's function in Legendre coefficient space with :math:`m \\times m` orbital indices. Returns ------- G_laa : (n,m,m), ndarray Green's function in imaginary time with :math:`m \\times m` orbital indices. """ x = 2 * self.get_tau_over_beta() - 1 G_laa = np.rollaxis(leg.legval(x, G_naa), -1) return G_laa
[docs] def eval_dlr_tau(self, G_xaa, tau_k, beta): """Evaluate the DLR coefficient Green's function `G_xaa` at arbibrary points in imaginary time. Parameters ---------- G_xaa : (n,m,m), array_like Green's function in DLR coefficient space with :math:`m \\times m` orbital indices. tau_k : (k), array_like Imaginary time points :math:`\\tau_k` where to evaluate the Green's function. beta : float Inverse temperature :math:`\\beta` Returns ------- G_kaa : (k,m,m), ndarray Green's function at the imaginary time points :math:`\\tau_k` with :math:`m \\times m` orbital indices. """ tau_k = tau_k[:, None] / beta w_p = self.dlrrf_p[None, :] K_kp = np.exp(-tau_k*w_p) * self.kernel_nominator_p[None, :] G_kaa = np.einsum('kp,p...->k...', K_kp, G_xaa[self.pm_idx:]) w_m = self.dlrrf_m[None, :] K_km = np.exp((1 - tau_k)*w_m) * self.kernel_nominator_m[None, :] G_kaa += np.einsum('km,m...->k...', K_km, G_xaa[:self.pm_idx]) return G_kaa
[docs] def lstsq_dlr_from_tau(self, tau_i, G_iaa, beta): """Return DLR coefficients by least squares fit to values on arbitrary imaginary time grid. Parameters ---------- tau_i : (i), array_like Imaginary time points :math:`\\tau_i` where the Green's function is sampled. G_iaa : (i,m,m), array_like Green's function in imaginary time space :math:`G(\\tau_i)` with :math:`m \\times m` orbital indices. beta : float Inverse temperature :math:`\\beta` Returns ------- G_xaa : (k,m,m), ndarray Green's function in DLR coefficient space with :math:`m \\times m` orbital indices. """ shape_iaa = G_iaa.shape assert(len(shape_iaa) == 3) shape_iA = (shape_iaa[0], shape_iaa[1]*shape_iaa[2]) shape_xaa = (len(self), shape_iaa[1], shape_iaa[2]) K_ix = kernel(tau_i/beta, self.dlrrf) G_xaa = np.linalg.lstsq( K_ix, G_iaa.reshape(shape_iA), rcond=None)[0].reshape(shape_xaa) return G_xaa
# -- Matsubara Frequency
[docs] def get_matsubara_frequencies(self, beta): """Get Matsubara frequency grid. Parameters ---------- beta : float Inverse temperature :math:`\\beta` Returns ------- w_q : (n), ndarray Matsubara frequency points :math:`i\\omega_q` for the DLR representation. """ zeta = (1 - self.xi)/2 w_q = 1.j * np.pi/beta * (2*self.dlrmf + zeta) return w_q
[docs] def dlr_from_matsubara(self, G_qaa, beta, xi=None): """Transform the rank-3 array_like Green's function `G_qaa` from Matsbuara frequency to DLR space. Parameters ---------- G_qaa : (n,m,m), array_like Green's function in Matsubara frequency with :math:`m \\times m` orbital indices. beta : float Inverse temperature :math:`\\beta` xi : int, optional Statistics sign, :math:`\\xi = +1` for bosons and :math:`\\xi = -1` for fermions. Returns ------- G_xaa : (n,m,m), ndarray Green's function i DLR coefficient space with :math:`m \\times m` orbital indices. """ xi = self.__xi_arg(xi) G_xaa = lu_solve((self.dlrmf2cf, self.mf2cfpiv), G_qaa / beta) if xi == 1: G_xaa /= self.bosonic_corr_x[:, None, None] return G_xaa
[docs] def matsubara_from_dlr(self, G_xaa, beta, xi=None): """Transform the rank-3 array_like Green's function `G_xaa` from DLR space to Matsbuara frequency. Parameters ---------- G_xaa : (n,m,m), array_like Green's function i DLR coefficient space with :math:`m \\times m` orbital indices. beta : float Inverse temperature :math:`\\beta` xi : int, optional Statistics sign, :math:`\\xi = +1` for bosons and :math:`\\xi = -1` for fermions. Returns ------- G_qaa : (n,m,m), ndarray Green's function in Matsubara frequency with :math:`m \\times m` orbital indices. """ xi = self.__xi_arg(xi) if xi == 1: G_qaa = beta * np.tensordot( self.T_qx * self.bosonic_corr_x[None, :], G_xaa, axes=(1, 0)) else: G_qaa = beta * np.tensordot(self.T_qx, G_xaa, axes=(1, 0)) if len(G_qaa.shape) == 3: G_qaa = np.transpose(G_qaa, axes=(0, 2, 1)) return G_qaa
[docs] def eval_dlr_freq(self, G_xaa, z, beta, xi=None): """Evaluate the DLR coefficient Green's function `G_xaa` at arbibrary points `z` in frequency space. Parameters ---------- G_xaa : (n,m,m), array_like Green's function in DLR coefficient space with :math:`m \\times m` orbital indices. z : (k), array_like Frequency points :math:`z_k` where to evaluate the Green's function. beta : float Inverse temperature :math:`\\beta` xi : int, optional Statistics sign, :math:`\\xi = +1` for bosons and :math:`\\xi = -1` for fermions. Returns ------- G_zaa : (k,m,m), ndarray Green's function at the frequency points :math:`z_k` with :math:`m \\times m` orbital indices. """ xi = self.__xi_arg(xi) w_x = self.dlrrf / beta kernel_zx = 1./(z[:, None] + w_x[None, :]) if xi == 1: kernel_zx *= self.bosonic_corr_x[None, :] G_zaa = np.einsum('x...,zx->z...', G_xaa, kernel_zx) if len(G_zaa.shape) == 3: G_zaa = np.transpose(G_zaa, axes=(0, 2, 1)) G_zaa = G_zaa.conj() return G_zaa
[docs] def lstsq_dlr_from_matsubara(self, w_q, G_qaa, beta): """Return DLR coefficients by least squares fit to values on arbitrary Matsubara frequency grid. Parameters ---------- w_q : (q), array_like Imaginary frequency points :math:`i\\omega_q` where the Green's function is sampled. G_qaa : (q,m,m), array_like Green's function in imaginary frequency space :math:`G(i\\omega_q)` with :math:`m \\times m` orbital indices. beta : float Inverse temperature :math:`\\beta` Returns ------- G_xaa : (k,m,m), ndarray Green's function in DLR coefficient space with :math:`m \\times m` orbital indices. """ shape_qaa = G_qaa.shape assert(len(shape_qaa) == 3) shape_qA = (shape_qaa[0], shape_qaa[1]*shape_qaa[2]) shape_xaa = (len(self), shape_qaa[1], shape_qaa[2]) K_qx = -1./(w_q[:, None] - self.dlrrf[None, :]/beta) G_xaa = np.linalg.lstsq( K_qx, G_qaa.reshape(shape_qA), rcond=None)[0].reshape(shape_xaa) return G_xaa
# -- Mathematical operations
[docs] def quadratic_hamiltonian(self, G_xaa, beta, xi=None): """ Get quadratic Hamiltonian contribution of physical Green's function. Parameters ---------- G_xaa : (k,m,m), ndarray Green's function in DLR coefficient space with :math:`m \\times m` orbital indices. beta : float Inverse temperature :math:`\\beta` Returns ------- H_aa : (m,m), ndarray Quadratic Hamiltonian contribution to the Green's function. """ xi = self.__xi_arg(xi) w_x = self.dlrrf K0_x = kernel(np.array([0.]), w_x) K1_x = kernel(np.array([1.]), w_x) D_x = (-K0_x + xi * K1_x) * w_x / beta H_aa = np.tensordot(D_x, G_xaa, axes=([-1, 0]))[0] return H_aa
[docs] def convolution(self, A_xaa, B_xaa, beta, xi=None): """ DLR convolution with :math:`\mathcal{O}(N^2)` scaling. Author: Hugo U.R. Strand (2021) Imaginary time convolution .. math:: C = A \\ast B reformulated in DLR coefficient space. The notation :math:`C = A \\ast B` is short hand for: .. math:: C_{ij}(\\tau) = \\sum_k \\int_{0}^\\beta d\\bar{\\tau} A_{ik}(\\tau - \\bar{\\tau}) B_{kj}(\\bar{\\tau}) Parameters ---------- A_xaa : (n,m,m) Green's function :math:`A` in DLR coefficient space with :math:`m \\times m` orbital indices. B_xaa : (n,m,m) Green's function :math:`B` in DLR coefficient space with :math:`m \\times m` orbital indices. beta : float Inverse temperature :math:`\\beta` xi : int, optional Statistics sign, :math:`\\xi = +1` for bosons and :math:`\\xi = -1` for fermions. Returns ------- C_xaa : (n,m,m), ndarray Green's function :math:`C` in DLR coefficient space with :math:`m \\times m` orbital indices, given by the convolution :math:`C = A \\ast B`. """ xi = self.__xi_arg(xi) W_xx = self.W_xx if xi == -1 else self.W_bc_xx TtT_xx = self.TtT_xx if xi == -1 else self.TtT_bc_xx n, na, _ = A_xaa.shape WA_xaa = np.matmul(W_xx.T, A_xaa.reshape((n, na*na))).reshape((n, na, na)) C_xaa = np.matmul(WA_xaa, B_xaa) del WA_xaa WB_xaa = np.matmul(W_xx.T, B_xaa.reshape((n, na*na))).reshape((n, na, na)) C_xaa += np.matmul(A_xaa, WB_xaa) del WB_xaa AB_xaa = np.matmul(A_xaa, B_xaa) C_xaa += -xi * self.k1_x[:, None, None] * AB_xaa C_xaa += np.matmul(TtT_xx, AB_xaa.reshape((n, na*na))).reshape((n, na, na)) del AB_xaa C_xaa *= beta return C_xaa
[docs] def convolution_matrix(self, A_xaa, beta, xi=None): """ DLR convolution matrix with :math:`\mathcal{O}(N^2)` scaling. Author: Hugo U.R. Strand (2021) The imaginary time convolution matrix :math:`M` is given by .. math:: M = [A \\ast] i.e. the combination of the Green's function :math:`A` and the convolution operator :math:`\\ast`. Author: Hugo U.R. Strand (2021) Parameters ---------- A_xaa : (n,m,m) Green's function :math:`A` in DLR coefficient space with :math:`m \\times m` orbital indices. beta : float Inverse temperature :math:`\\beta` xi : int, optional Statistics sign, :math:`\\xi = +1` for bosons and :math:`\\xi = -1` for fermions. Returns ------- M_xaxa : (n,m,n,m), ndarray Convolution matrix :math:`[A \\ast]` as a rank-4 tensor in DLR and orbital space. """ xi = self.__xi_arg(xi) W_xx = self.W_xx if xi == -1 else self.W_bc_xx TtT_xx = self.TtT_xx if xi == -1 else self.TtT_bc_xx n, na, _ = A_xaa.shape Q_xaa = np.einsum('yx,yab->xab', W_xx, A_xaa) Q_xaa += -xi * self.k1_x[:,None,None] * A_xaa M_xxaa = np.einsum( 'xy,yab->yxab', W_xx, A_xaa) M_xxaa += np.einsum('xy,yab->xyab', TtT_xx, A_xaa) M_xxaa += np.einsum('xy,yab->xyab', np.eye(n), Q_xaa) M_xxaa *= beta M_xaxa = np.moveaxis(M_xxaa, 2, 1) return M_xaxa
# -- Free Green's function solvers
[docs] def free_greens_function_dlr(self, H_aa, beta, S_aa=None, xi=None): """ Return the free Green's function in DLR coefficent space. The free Green's function is the solution to the Dyson differential equation .. math:: (-\\partial_\\tau - H_{ij}) G_{jk}(\\tau) = \\delta(\\tau) Parameters ---------- H_aa : (m,m), array_like Single-particle Hamiltonian matrix in :math:`m \\times m` orbital space. beta : float Inverse temperature :math:`\\beta` S_aa : (m,m), array_like, optional Overlap matrix for the generalized case of non-orthogonal basis functions, where the Dyson equation takes the form :math:`(-S_{ij} \\partial_\\tau - H_{ij}) G_{jk}(\\tau) = \\delta(\\tau)`. Default is `S_aa = None`. xi : int, optional Statistics sign, :math:`\\xi = +1` for bosons and :math:`\\xi = -1` for fermions. Returns ------- G_xaa : (n,m,m), ndarray Free Green's function :math:`G` in DLR coefficient space with :math:`m \\times m` orbital indices. Notes ----- The fastest algorithm for calculation of free Green's functions is the `free_greens_function_tau` method. """ xi = self.__xi_arg(xi) na = H_aa.shape[0] I_aa = np.eye(na) if S_aa is None: S_aa = I_aa w_x = self.dlrrf n = len(w_x) D_lx = self.T_lx * w_x[None, :] / beta D_AA = np.kron(D_lx, S_aa) - np.kron(self.T_lx, H_aa) bc_x = kernel(np.array([0.]), w_x) - xi * kernel(np.array([1.]), w_x) D_AA[(n-1)*na:, :] = np.kron(bc_x, S_aa) b_Aa = np.zeros((n*na, na)) b_Aa[(n-1)*na:, :] = -I_aa g_xaa = np.linalg.solve(D_AA, b_Aa).reshape((n, na, na)) return g_xaa
[docs] def free_greens_function_tau(self, H_aa, beta, S_aa=None, xi=None): """ Return the free Green's function in imaginary time. The free Green's function is the solution to the Dyson differential equation .. math:: (-\\partial_\\tau - H_{ij}) G_{jk}(\\tau) = \\delta(\\tau) Parameters ---------- H_aa : (m,m), array_like Single-particle Hamiltonian matrix in :math:`m \\times m` orbital space. beta : float Inverse temperature :math:`\\beta` S_aa : (m,m), array_like, optional Overlap matrix for the generalized case of non-orthogonal basis functions, where the Dyson equation takes the form :math:`(-S_{ij} \\partial_\\tau - H_{ij}) G_{jk}(\\tau) = \\delta(\\tau)`. Default is `S_aa = None`. xi : int, optional Statistics sign, :math:`\\xi = +1` for bosons and :math:`\\xi = -1` for fermions. Returns ------- G_laa : (n,m,m), ndarray Free Green's function :math:`G` in imaginary time with :math:`m \\times m` orbital indices. """ xi = self.__xi_arg(xi) w_x = self.dlrrf if S_aa is None: E, U = np.linalg.eigh(H_aa) else: E, U = scipy_eigh(H_aa, S_aa) tau_l = self.get_tau(1.) g_lE = -kernel(tau_l, E*beta) if xi == 1: g_lE /= self.__bosonic_corr_freq(E*beta)[None, :] g_laa = np.einsum('lE,aE,Eb->lab', g_lE, U, U.T.conj()) return g_laa
[docs] def free_greens_function_matsubara(self, H_aa, beta, S_aa=None): """ Return the free Green's function in Matsubara frequency. The free Green's function is the solution to the Dyson equation .. math:: G_{ij}(i \\omega_n ) = \\left[ i\\omega_n - H_{ij} \\right]^{-1} Parameters ---------- H_aa : (m,m), array_like Single-particle Hamiltonian matrix in :math:`m \\times m` orbital space. beta : float Inverse temperature :math:`\\beta` S_aa : (m,m), array_like, optional Overlap matrix for the generalized case of non-orthogonal basis functions, where the Dyson equation takes the form :math:`= G_{jk}(i\\omega_n) = (-S_{ij} i\\omega_n - H_{ij})^{-1}`. Default is `S_aa = None`. Returns ------- G_qaa : (n,m,m), ndarray Free Green's function :math:`G` in Matsubara frequency with :math:`m \\times m` orbital indices. Notes ----- The fastest algorithm for calculation of free Green's functions is the `free_greens_function_tau` method. """ if S_aa is None: S_aa = np.eye(H_aa.shape[0]) w_q = self.get_matsubara_frequencies(beta) g_qaa = np.linalg.inv(w_q[:, None, None] * S_aa[None, ...] - H_aa[None, ...]) return g_qaa
# -- Dyson equation solvers
[docs] def dyson_matsubara(self, H_aa, Sigma_qaa, beta, S_aa=None): """ Solve the Dyson equation in Matsubara frequency. The Dyson equation gives the Green's function as .. math:: G_{ij}(i \\omega_n ) = \\left[ i\\omega_n - H_{ij} - \\Sigma(i\\omega_n) \\right]^{-1} Parameters ---------- H_aa : (m,m), array_like Single-particle Hamiltonian matrix in :math:`m \\times m` orbital space. Sigma_qaa : (n,m,m), ndarray Self-energy :math:`\\Sigma` in Matsubara frequency with :math:`m \\times m` orbital indices. beta : float Inverse temperature :math:`\\beta` S_aa : (m,m), array_like, optional Overlap matrix for the generalized case of non-orthogonal basis functions. Default is `S_aa = None`. Returns ------- G_qaa : (n,m,m), ndarray Green's function :math:`G` in Matsubara frequency with :math:`m \\times m` orbital indices. Notes ----- The Matsubara frequency Dyson solver is the fastest Dyson solver, albeit not as accurate as the DLR solver `dyson_dlr`. """ if S_aa is None: S_aa = np.eye(H_aa.shape[0]) w_q = self.get_matsubara_frequencies(beta) G_qaa = np.linalg.inv(w_q[:, None, None] * S_aa[None, ...] - H_aa[None, ...] - Sigma_qaa) return G_qaa
[docs] def dyson_dlr(self, H_aa, Sigma_xaa, beta, S_aa=None, iterative=False, lomem=False, verbose=False, tol=1e-12): """ Solve the Dyson equation in DLR coefficient space. The Dyson equation gives the imaginary time Green's function :math:`G_{ij}(\\tau)` as .. math:: (-\\partial_\\tau - H_{ij} - \\Sigma_{ij} \\ast \\, ) G_{jk}(\\tau) = \\delta(\\tau) Using the free Green's function :math:`g` this can be rewritten as the integral equation .. math:: (1 - g \\ast \\Sigma \\ast \\, ) G = g which here is solved directly in DLR coefficient space. Parameters ---------- H_aa : (m,m), array_like Single-particle Hamiltonian matrix in :math:`m \\times m` orbital space. Sigma_xaa : (n,m,m), ndarray Self-energy :math:`\\Sigma` in DLR coefficient space with :math:`m \\times m` orbital indices. beta : float Inverse temperature :math:`\\beta` S_aa : (m,m), array_like, optional Overlap matrix for the generalized case of non-orthogonal basis functions. Default is `S_aa = None`. iterative : bool, optional Default `False` lomem : bool, optional Default `False` tol : float, optional Tolerance for iterative GMRES solver. Returns ------- G_xaa : (n,m,m), ndarray Green's function :math:`G` in DLR coefficient space with :math:`m \\times m` orbital indices. Notes ----- This DLR space Dyson solver is the most accurate algorithm for solving the Dyson equation. By default it uses Lapack's direct solver on matrix problem in combined DLR and orbital space. This is fast and accurate for small problems. For larger problems the :math:`\\mathcal{O}(N^2M^2)` memory foot-print limits the performance. Hence, for large problems the solver should be run with `iterative=True` and `lomem=True` and will then use GMRES to solve the linear system using an implicit matrix formulation. This formulation gives a memory foot print that is of the same order as storing a single Green's function. However, the requested GMRES tolerance `tol` has to be carefully tuned. """ na = H_aa.shape[0] n = Sigma_xaa.shape[0] I_aa = np.eye(na) g0_iaa = self.free_greens_function_tau(H_aa, beta, S_aa=S_aa) g0_xaa = self.dlr_from_tau(g0_iaa) g0Sigma_xaa = self.convolution(g0_xaa, Sigma_xaa, beta) if iterative: from scipy.sparse.linalg import LinearOperator from scipy.sparse.linalg import gmres as scipy_gmres if lomem: def Amatvec(x_Aa): x_xaa = x_Aa.reshape((n, na, na)) y_xaa = x_xaa.copy() y_xaa -= self.convolution(g0Sigma_xaa, x_xaa, beta) return self.tau_from_dlr(y_xaa).reshape((n*na, na)) else: C_AA = self.convolution_matrix(g0Sigma_xaa, beta).reshape((n*na, n*na)) def Amatvec(x_Aa): y_Aa = x_Aa - C_AA @ x_Aa y_xaa = y_Aa.reshape((n, na, na)) return self.tau_from_dlr(y_xaa).reshape((n*na, na)) I_aa = np.eye(na) g0Sigma_qaa = self.matsubara_from_dlr(g0Sigma_xaa, beta) M_qaa = np.linalg.inv(I_aa[None, ...] - g0Sigma_qaa) def Mmatvec(x_Aa): x_iaa = x_Aa.reshape((n, na, na)) x_qaa = self.matsubara_from_dlr(self.dlr_from_tau(x_iaa), beta) y_qaa = M_qaa @ x_qaa y_xaa = self.dlr_from_matsubara(y_qaa, beta) return y_xaa.reshape((n*na, na)) D_AA = LinearOperator(matvec=Amatvec, shape=(n * na, n * na), dtype=complex) M_AA = LinearOperator(matvec=Mmatvec, shape=(n * na, n * na), dtype=complex) b_Aa = g0_iaa.reshape((n*na, na)) x0_Aa = M_AA.matvec(b_Aa) g_Aa, info = solver_wrapper(scipy_gmres, verbose=verbose)(D_AA, b_Aa, x0=x0_Aa, M=M_AA, atol=tol) g_xaa = g_Aa.reshape((n, na, na)) if verbose: diff = np.max(np.abs(self.tau_from_dlr(x0_Aa.reshape((n, na, na))) - self.tau_from_dlr(g_Aa.reshape((n,na,na))))) print(f'Corr on precond: {diff:2.2E}') else: if lomem: raise NotImplementedError #D_AA = np.kron(self.T_lx, I_aa) - self.tau_from_dlr(self.convolution_matrix(g0Sigma_xaa, beta)).reshape((n*na, n*na)) #b_Aa = g0_iaa.reshape((n*na, na)) D_AA = np.kron(np.eye(n), I_aa) - self.convolution_matrix(g0Sigma_xaa, beta).reshape((n*na, n*na)) b_Aa = g0_xaa.reshape((n*na, na)) g_xaa = np.linalg.solve(D_AA, b_Aa).reshape((n, na, na)) return g_xaa
[docs] def dyson_dlr_integrodiff(self, H_aa, Sigma_xaa, beta, S_aa=None, xi=None): """ Solve the Dyson equation in DLR coefficient space. The Dyson equation gives the imaginary time Green's function :math:`G_{ij}(\\tau)` as .. math:: (-\\partial_\\tau - H_{ij} - \\Sigma_{ij} \\ast \\, ) G_{jk}(\\tau) = \\delta(\\tau) which here is solved directly in DLR coefficient space. Parameters ---------- H_aa : (m,m), array_like Single-particle Hamiltonian matrix in :math:`m \\times m` orbital space. Sigma_xaa : (n,m,m), ndarray Self-energy :math:`\\Sigma` in DLR coefficient space with :math:`m \\times m` orbital indices. beta : float Inverse temperature :math:`\\beta` S_aa : (m,m), array_like, optional Overlap matrix for the generalized case of non-orthogonal basis functions. Default is `S_aa = None`. Returns ------- G_xaa : (n,m,m), ndarray Green's function :math:`G` in DLR coefficient space with :math:`m \\times m` orbital indices. Notes ----- This DLR space Dyson solver should not be used in production, use `dyson_dlr` instead. The differential formulation gives a worse condition number, as compared to the integral equation formulation. The method is kept here for educational purposes. """ xi = self.__xi_arg(xi) na = H_aa.shape[0] I_aa = np.eye(na) if S_aa is None: S_aa = I_aa w_x = self.dlrrf n = len(w_x) D_lx = self.T_lx * w_x[None, :] / beta D_AA = -self.tau_from_dlr(self.convolution_matrix(Sigma_xaa, beta)).reshape((n*na, n*na)) D_AA += np.kron(D_lx, S_aa) - np.kron(self.T_lx, H_aa) bc_x = kernel(np.array([0.]), w_x) - xi * kernel(np.array([1.]), w_x) D_AA[(n-1)*na:, :] = np.kron(bc_x, S_aa) b_Aa = np.zeros((n*na, na)) b_Aa[(n-1)*na:, :] = -I_aa g_xaa = np.linalg.solve(D_AA, b_Aa).reshape((n, na, na)) return g_xaa
[docs]class solver_wrapper: """ Wrapper for scipy.sparse.linalg iterative linearsystems solvers """ def __init__(self, solver, verbose=False): self.solver = solver self.verbose = verbose self.callback_kwargs = dict(callback=self.callback) # -- Hack to suppress missing paramter warning 'callback_type' # -- in new Scipy GMRES self.old_scipy = True from inspect import signature if 'callback_type' in signature(solver).parameters: self.callback_kwargs['callback_type'] = 'legacy' self.old_scipy = False
[docs] def kwargs_filter(self, d): if self.old_scipy: d['tol'] = d.pop('atol') return d
[docs] def callback(self, x): self.iter += 1
def __call__(self, A, b, **kwargs): self.iter = 1 ret = self.solver(A, b, **self.callback_kwargs, **self.kwargs_filter(kwargs)) if self.verbose: print('GMRES N iter:', self.iter) return ret