4
4
5
5
The implementation is based on a SVD of the `hopping` matrix,
6
6
which is the dimensionless scaling of the hopping of the components. [weh2021]_
7
+ However, we use the unitary eigendecomposition instead of the SVD.
7
8
8
9
Physical quantities
9
10
-------------------
75
76
76
77
"""
77
78
# pylint: disable=too-many-locals
79
+ from __future__ import annotations
80
+
78
81
import logging
79
82
80
83
from typing import Callable , NamedTuple
81
84
from functools import partial
82
85
83
86
import numpy as np
84
87
85
- from scipy import optimize
86
88
from numpy import newaxis
89
+ from scipy import optimize
87
90
88
- from gftool .matrix import decompose_mat
91
+ from gftool .matrix import decompose_her , decompose_mat , UDecomposition
89
92
90
93
LOGGER = logging .getLogger (__name__ )
91
94
@@ -101,7 +104,7 @@ class SVD(NamedTuple):
101
104
s : np .ndarray
102
105
vh : np .ndarray
103
106
104
- def truncate (self , rcond = None ) -> " SVD" :
107
+ def truncate (self , rcond = None ) -> SVD :
105
108
"""Return the truncated singular values decomposition.
106
109
107
110
Singular values smaller than `rcond` times the largest singular values
@@ -145,6 +148,55 @@ def partition(self, return_sqrts=False):
145
148
return us , svh
146
149
147
150
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
+
148
200
def gf_loc_z (z , self_beb_z , hopping , hilbert_trafo : Callable [[complex ], complex ],
149
201
diag = True , rcond = None ):
150
202
"""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]
180
232
solve_root
181
233
182
234
"""
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 ])
187
239
kind = 'diag' if diag else 'full'
188
240
189
241
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 )
191
243
# [..., newaxis]*eye add matrix axis
192
244
z_m_self = z [..., newaxis , newaxis ]* eye - self_beb_z
193
245
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 )
195
247
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 ]
199
251
dec .rv = svh_inv @ np .asfortranarray (dec .rv )
200
252
dec .rv_inv = np .asfortranarray (dec .rv_inv ) @ us_inv
201
253
return dec .reconstruct (hilbert_trafo (diag_inv ), kind = kind )
202
254
203
255
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
205
257
correction = dec .reconstruct ((diag_inv * hilbert_trafo (diag_inv ) - 1 ) * diag_inv , kind = kind )
206
258
return (diagonal (z_m_self_inv ) if diag else z_m_self_inv ) + correction
207
259
208
260
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 ,
210
262
hilbert_trafo : Callable [[complex ], complex ]):
211
263
"""Root equation r(Σ)=0 for BEB.
212
264
@@ -220,7 +272,7 @@ def self_root_eq(self_beb_z, z, e_onsite, concentration, hopping_svd: SVD,
220
272
On-site energy of the components.
221
273
concentration : (..., N_cmpt) float array_like
222
274
Concentration of the different components.
223
- hopping_svd : SVD
275
+ hopping_dec : SVD
224
276
Compact SVD decomposition of the (N_cmpt, N_cmpt) hopping matrix in the
225
277
components.
226
278
hilbert_trafo : Callable[[complex], complex]
@@ -240,14 +292,14 @@ def self_root_eq(self_beb_z, z, e_onsite, concentration, hopping_svd: SVD,
240
292
eye = np .eye (e_onsite .shape [- 1 ]) # [..., newaxis]*eye adds matrix axis
241
293
z_m_self = z [..., newaxis , newaxis ]* eye - self_beb_z
242
294
# split symmetrically
243
- us , svh = hopping_svd .partition ()
295
+ us , suh = hopping_dec .partition ()
244
296
# matrix-products are faster if larger arrays are in Fortran order
245
297
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 )
247
299
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
249
301
diag_inv = 1. / dec .eig
250
- if not hopping_svd .is_trunacted :
302
+ if not hopping_dec .is_trunacted :
251
303
gf_loc_inv = dec .reconstruct (1. / hilbert_trafo (diag_inv ), kind = 'full' )
252
304
else :
253
305
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
362
414
>>> plt.show()
363
415
364
416
"""
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 ])
369
421
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 )
371
423
if self_beb_z0 is None :
372
424
self_beb_z0 = np .zeros (hopping .shape , dtype = complex )
373
425
# 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
390
442
root_kwds ['callback' ] = lambda x , f : LOGGER .debug ('Residue: %s' , np .linalg .norm (f ))
391
443
392
444
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 )
394
446
395
447
if LOGGER .isEnabledFor (logging .INFO ):
396
448
# 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
398
450
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 )
400
452
max_cond = np .max (np .linalg .cond (dec .rv ))
401
453
LOGGER .info ("Maximal coordination number for diagonalization: %s" , max_cond )
402
454
0 commit comments