Skip to content

Commit 0475c11

Browse files
committed
BEB: replace SVD by eigendecomposition
1 parent db04344 commit 0475c11

File tree

1 file changed

+79
-27
lines changed

1 file changed

+79
-27
lines changed

gftool/beb.py

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
The implementation is based on a SVD of the `hopping` matrix,
66
which is the dimensionless scaling of the hopping of the components. [weh2021]_
7+
However, we use the unitary eigendecomposition instead of the SVD.
78
89
Physical quantities
910
-------------------
@@ -75,17 +76,19 @@
7576
7677
"""
7778
# pylint: disable=too-many-locals
79+
from __future__ import annotations
80+
7881
import logging
7982

8083
from typing import Callable, NamedTuple
8184
from functools import partial
8285

8386
import numpy as np
8487

85-
from scipy import optimize
8688
from numpy import newaxis
89+
from scipy import optimize
8790

88-
from gftool.matrix import decompose_mat
91+
from gftool.matrix import decompose_her, decompose_mat, UDecomposition
8992

9093
LOGGER = logging.getLogger(__name__)
9194

@@ -101,7 +104,7 @@ class SVD(NamedTuple):
101104
s: np.ndarray
102105
vh: np.ndarray
103106

104-
def truncate(self, rcond=None) -> "SVD":
107+
def truncate(self, rcond=None) -> SVD:
105108
"""Return the truncated singular values decomposition.
106109
107110
Singular values smaller than `rcond` times the largest singular values
@@ -145,6 +148,55 @@ def partition(self, return_sqrts=False):
145148
return us, svh
146149

147150

151+
class SpecDec(UDecomposition): # pylint: disable=too-many-ancestors
152+
"""SVD like spectral decomposition.
153+
154+
Works only for N×N matrices unlike the `UDecomposition` base class.
155+
"""
156+
157+
def truncate(self, rcond=None) -> SpecDec:
158+
"""Return the truncated spectral decomposition.
159+
160+
Singular values smaller than `rcond` times the largest singular values
161+
are discarded.
162+
163+
Parameters
164+
----------
165+
rcond : float, rcond
166+
Cut-off ratio for small singular values.
167+
168+
Returns
169+
-------
170+
truncated_svd : SpecDec
171+
The truncates the spectral decomposition discarding small singular values.
172+
173+
"""
174+
if rcond is None:
175+
rcond = np.finfo(self.eig.dtype).eps * max(self.u.shape[-2:])
176+
max_eig = np.max(abs(self.eig), axis=-1)
177+
significant = abs(self.eig) > max_eig*rcond
178+
return self.__class__(rv=self.rv[..., :, significant], eig=self.eig[..., significant],
179+
rv_inv=self.rv_inv[..., significant, :])
180+
181+
@property
182+
def is_trunacted(self) -> bool:
183+
"""Check if SVD of square matrix is truncated/compact or full."""
184+
ushape, uhshape = self.u.shape, self.uh.shape
185+
return not ushape[-2] == ushape[-1] == uhshape[-2]
186+
187+
def partition(self, return_sqrts=False):
188+
"""Symmetrically partition the spectral decomposition as `u * eig**0.5, eig**0.5 * uh`.
189+
190+
If `return_sqrts` then `us, np.sqrt(s), suh` is returned,
191+
else only `us, suh` is returned (default: False).
192+
"""
193+
sqrt_eig = np.emath.sqrt(self.eig)
194+
us, suh = self.u * sqrt_eig[..., newaxis, :], sqrt_eig[..., :, newaxis] * self.uh
195+
if return_sqrts:
196+
return us, sqrt_eig, suh
197+
return us, suh
198+
199+
148200
def gf_loc_z(z, self_beb_z, hopping, hilbert_trafo: Callable[[complex], complex],
149201
diag=True, rcond=None):
150202
"""Calculate average local Green's function matrix in components.
@@ -180,33 +232,33 @@ def gf_loc_z(z, self_beb_z, hopping, hilbert_trafo: Callable[[complex], complex]
180232
solve_root
181233
182234
"""
183-
hopping_svd = SVD(*np.linalg.svd(hopping, hermitian=True))
184-
LOGGER.info('hopping singular values %s', hopping_svd.s)
185-
hopping_svd = hopping_svd.truncate(rcond)
186-
LOGGER.info('Keeping %s (out of %s)', hopping_svd.s.shape[-1], hopping_svd.vh.shape[-1])
235+
hopping_dec = SpecDec(*decompose_her(hopping))
236+
LOGGER.info('hopping singular values %s', hopping_dec.s)
237+
hopping_dec = hopping_dec.truncate(rcond)
238+
LOGGER.info('Keeping %s (out of %s)', hopping_dec.s.shape[-1], hopping_dec.uh.shape[-1])
187239
kind = 'diag' if diag else 'full'
188240

189241
eye = np.eye(*hopping.shape)
190-
us, sqrt_s, svh = hopping_svd.partition(return_sqrts=True)
242+
us, sqrt_s, suh = hopping_dec.partition(return_sqrts=True)
191243
# [..., newaxis]*eye add matrix axis
192244
z_m_self = z[..., newaxis, newaxis]*eye - self_beb_z
193245
z_m_self_inv = np.asfortranarray(np.linalg.inv(z_m_self))
194-
dec = decompose_mat(svh @ z_m_self_inv @ us)
246+
dec = decompose_mat(suh @ z_m_self_inv @ us)
195247
diag_inv = 1. / dec.eig
196-
if not hopping_svd.is_trunacted:
197-
svh_inv = transpose(hopping_svd.vh).conj() / sqrt_s[..., newaxis, :]
198-
us_inv = transpose(hopping_svd.u).conj() / sqrt_s[..., :, newaxis]
248+
if not hopping_dec.is_trunacted:
249+
svh_inv = transpose(hopping_dec.uh).conj() / sqrt_s[..., newaxis, :]
250+
us_inv = transpose(hopping_dec.u).conj() / sqrt_s[..., :, newaxis]
199251
dec.rv = svh_inv @ np.asfortranarray(dec.rv)
200252
dec.rv_inv = np.asfortranarray(dec.rv_inv) @ us_inv
201253
return dec.reconstruct(hilbert_trafo(diag_inv), kind=kind)
202254

203255
dec.rv = z_m_self_inv @ us @ np.asfortranarray(dec.rv)
204-
dec.rv_inv = np.asfortranarray(dec.rv_inv) @ svh @ z_m_self_inv
256+
dec.rv_inv = np.asfortranarray(dec.rv_inv) @ suh @ z_m_self_inv
205257
correction = dec.reconstruct((diag_inv*hilbert_trafo(diag_inv) - 1) * diag_inv, kind=kind)
206258
return (diagonal(z_m_self_inv) if diag else z_m_self_inv) + correction
207259

208260

209-
def self_root_eq(self_beb_z, z, e_onsite, concentration, hopping_svd: SVD,
261+
def self_root_eq(self_beb_z, z, e_onsite, concentration, hopping_dec: SpecDec,
210262
hilbert_trafo: Callable[[complex], complex]):
211263
"""Root equation r(Σ)=0 for BEB.
212264
@@ -220,7 +272,7 @@ def self_root_eq(self_beb_z, z, e_onsite, concentration, hopping_svd: SVD,
220272
On-site energy of the components.
221273
concentration : (..., N_cmpt) float array_like
222274
Concentration of the different components.
223-
hopping_svd : SVD
275+
hopping_dec : SVD
224276
Compact SVD decomposition of the (N_cmpt, N_cmpt) hopping matrix in the
225277
components.
226278
hilbert_trafo : Callable[[complex], complex]
@@ -240,14 +292,14 @@ def self_root_eq(self_beb_z, z, e_onsite, concentration, hopping_svd: SVD,
240292
eye = np.eye(e_onsite.shape[-1]) # [..., newaxis]*eye adds matrix axis
241293
z_m_self = z[..., newaxis, newaxis]*eye - self_beb_z
242294
# split symmetrically
243-
us, svh = hopping_svd.partition()
295+
us, suh = hopping_dec.partition()
244296
# matrix-products are faster if larger arrays are in Fortran order
245297
z_m_self_inv = np.asfortranarray(np.linalg.inv(z_m_self))
246-
dec = decompose_mat(svh @ z_m_self_inv @ us)
298+
dec = decompose_mat(suh @ z_m_self_inv @ us)
247299
dec.rv = us @ np.asfortranarray(dec.rv)
248-
dec.rv_inv = np.asfortranarray(dec.rv_inv) @ svh
300+
dec.rv_inv = np.asfortranarray(dec.rv_inv) @ suh
249301
diag_inv = 1. / dec.eig
250-
if not hopping_svd.is_trunacted:
302+
if not hopping_dec.is_trunacted:
251303
gf_loc_inv = dec.reconstruct(1./hilbert_trafo(diag_inv), kind='full')
252304
else:
253305
gf_loc_inv = z_m_self + dec.reconstruct(1./hilbert_trafo(diag_inv) - diag_inv, kind='full')
@@ -362,12 +414,12 @@ def solve_root(z, e_onsite, concentration, hopping, hilbert_trafo: Callable[[com
362414
>>> plt.show()
363415
364416
"""
365-
hopping_svd = SVD(*np.linalg.svd(hopping, hermitian=True))
366-
LOGGER.info('hopping singular values %s', hopping_svd.s)
367-
hopping_svd = hopping_svd.truncate(rcond)
368-
LOGGER.info('Keeping %s (out of %s)', hopping_svd.s.shape[-1], hopping_svd.vh.shape[-1])
417+
hopping_dec = SpecDec(*decompose_her(hopping))
418+
LOGGER.info('hopping singular values %s', hopping_dec.s)
419+
hopping_dec = hopping_dec.truncate(rcond)
420+
LOGGER.info('Keeping %s (out of %s)', hopping_dec.s.shape[-1], hopping_dec.uh.shape[-1])
369421
self_root_part = partial(self_root_eq, z=z, e_onsite=e_onsite, concentration=concentration,
370-
hopping_svd=hopping_svd, hilbert_trafo=hilbert_trafo)
422+
hopping_dec=hopping_dec, hilbert_trafo=hilbert_trafo)
371423
if self_beb_z0 is None:
372424
self_beb_z0 = np.zeros(hopping.shape, dtype=complex)
373425
# experience shows that a single fixed_point is a good starting point
@@ -390,13 +442,13 @@ def solve_root(z, e_onsite, concentration, hopping, hilbert_trafo: Callable[[com
390442
root_kwds['callback'] = lambda x, f: LOGGER.debug('Residue: %s', np.linalg.norm(f))
391443

392444
sol = optimize.root(root_eq, x0=self_beb_z0, **root_kwds)
393-
LOGGER.debug("BEB self-energy root found after %s iterations.", sol.nit)
445+
LOGGER.info("BEB self-energy root found after %s iterations.", sol.nit)
394446

395447
if LOGGER.isEnabledFor(logging.INFO):
396448
# check condition number in matrix diagonalization to make sure it is well defined
397-
us, svh = hopping_svd.partition() # pylint: disable=unbalanced-tuple-unpacking
449+
us, suh = hopping_dec.partition() # pylint: disable=unbalanced-tuple-unpacking
398450
z_m_self = z[..., newaxis, newaxis]*np.eye(*hopping.shape) - sol.x
399-
dec = decompose_mat(svh @ np.linalg.inv(z_m_self) @ us)
451+
dec = decompose_mat(suh @ np.linalg.inv(z_m_self) @ us)
400452
max_cond = np.max(np.linalg.cond(dec.rv))
401453
LOGGER.info("Maximal coordination number for diagonalization: %s", max_cond)
402454

0 commit comments

Comments
 (0)