88from .misc import dump_object , _assert_equal_properties
99from .fits import least_squares
1010from .roots import find_root
11+ from . import linalg
1112
1213
1314class Corr :
@@ -298,7 +299,7 @@ def matrix_symmetric(self):
298299 transposed = [None if _check_for_none (self , G ) else G .T for G in self .content ]
299300 return 0.5 * (Corr (transposed ) + self )
300301
301- def GEVP (self , t0 , ts = None , sort = "Eigenvalue" , ** kwargs ):
302+ def GEVP (self , t0 , ts = None , sort = "Eigenvalue" , vector_obs = False , ** kwargs ):
302303 r'''Solve the generalized eigenvalue problem on the correlator matrix and returns the corresponding eigenvectors.
303304
304305 The eigenvectors are sorted according to the descending eigenvalues, the zeroth eigenvector(s) correspond to the
@@ -317,14 +318,21 @@ def GEVP(self, t0, ts=None, sort="Eigenvalue", **kwargs):
317318 If sort="Eigenvector" it gives a reference point for the sorting method.
318319 sort : string
319320 If this argument is set, a list of self.T vectors per state is returned. If it is set to None, only one vector is returned.
320- - "Eigenvalue": The eigenvector is chosen according to which eigenvalue it belongs individually on every timeslice.
321+ - "Eigenvalue": The eigenvector is chosen according to which eigenvalue it belongs individually on every timeslice. (default)
321322 - "Eigenvector": Use the method described in arXiv:2004.10472 to find the set of v(t) belonging to the state.
322323 The reference state is identified by its eigenvalue at $t=t_s$.
324+ - None: The GEVP is solved only at ts, no sorting is necessary
325+ vector_obs : bool
326+ If True, uncertainties are propagated in the eigenvector computation (default False).
323327
324328 Other Parameters
325329 ----------------
326330 state : int
327331 Returns only the vector(s) for a specified state. The lowest state is zero.
332+ method : str
333+ Method used to solve the GEVP.
334+ - "eigh": Use scipy.linalg.eigh to solve the GEVP. (default for vector_obs=False)
335+ - "cholesky": Use manually implemented solution via the Cholesky decomposition. Automatically chosen if vector_obs==True.
328336 '''
329337
330338 if self .N == 1 :
@@ -342,25 +350,43 @@ def GEVP(self, t0, ts=None, sort="Eigenvalue", **kwargs):
342350 else :
343351 symmetric_corr = self .matrix_symmetric ()
344352
345- G0 = np .vectorize (lambda x : x .value )(symmetric_corr [t0 ])
346- np .linalg .cholesky (G0 ) # Check if matrix G0 is positive-semidefinite.
353+ def _get_mat_at_t (t , vector_obs = vector_obs ):
354+ if vector_obs :
355+ return symmetric_corr [t ]
356+ else :
357+ return np .vectorize (lambda x : x .value )(symmetric_corr [t ])
358+ G0 = _get_mat_at_t (t0 )
359+
360+ method = kwargs .get ('method' , 'eigh' )
361+ if vector_obs :
362+ chol = linalg .cholesky (G0 )
363+ chol_inv = linalg .inv (chol )
364+ method = 'cholesky'
365+ else :
366+ chol = np .linalg .cholesky (_get_mat_at_t (t0 , vector_obs = False )) # Check if matrix G0 is positive-semidefinite.
367+ if method == 'cholesky' :
368+ chol_inv = np .linalg .inv (chol )
369+ else :
370+ chol_inv = None
347371
348372 if sort is None :
349373 if (ts is None ):
350374 raise Exception ("ts is required if sort=None." )
351375 if (self .content [t0 ] is None ) or (self .content [ts ] is None ):
352376 raise Exception ("Corr not defined at t0/ts." )
353- Gt = np .vectorize (lambda x : x .value )(symmetric_corr [ts ])
354- reordered_vecs = _GEVP_solver (Gt , G0 )
377+ Gt = _get_mat_at_t (ts )
378+ reordered_vecs = _GEVP_solver (Gt , G0 , method = method , chol_inv = chol_inv )
379+ if kwargs .get ('auto_gamma' , False ) and vector_obs :
380+ [[o .gm () for o in ev if isinstance (o , Obs )] for ev in reordered_vecs ]
355381
356382 elif sort in ["Eigenvalue" , "Eigenvector" ]:
357383 if sort == "Eigenvalue" and ts is not None :
358384 warnings .warn ("ts has no effect when sorting by eigenvalue is chosen." , RuntimeWarning )
359385 all_vecs = [None ] * (t0 + 1 )
360386 for t in range (t0 + 1 , self .T ):
361387 try :
362- Gt = np . vectorize ( lambda x : x . value )( symmetric_corr [ t ] )
363- all_vecs .append (_GEVP_solver (Gt , G0 ))
388+ Gt = _get_mat_at_t ( t )
389+ all_vecs .append (_GEVP_solver (Gt , G0 , method = method , chol_inv = chol_inv ))
364390 except Exception :
365391 all_vecs .append (None )
366392 if sort == "Eigenvector" :
@@ -369,15 +395,17 @@ def GEVP(self, t0, ts=None, sort="Eigenvalue", **kwargs):
369395 all_vecs = _sort_vectors (all_vecs , ts )
370396
371397 reordered_vecs = [[v [s ] if v is not None else None for v in all_vecs ] for s in range (self .N )]
398+ if kwargs .get ('auto_gamma' , False ) and vector_obs :
399+ [[[o .gm () for o in evn ] for evn in ev if evn is not None ] for ev in reordered_vecs ]
372400 else :
373- raise Exception ("Unkown value for 'sort'." )
401+ raise Exception ("Unknown value for 'sort'. Choose 'Eigenvalue', 'Eigenvector' or None ." )
374402
375403 if "state" in kwargs :
376404 return reordered_vecs [kwargs .get ("state" )]
377405 else :
378406 return reordered_vecs
379407
380- def Eigenvalue (self , t0 , ts = None , state = 0 , sort = "Eigenvalue" ):
408+ def Eigenvalue (self , t0 , ts = None , state = 0 , sort = "Eigenvalue" , ** kwargs ):
381409 """Determines the eigenvalue of the GEVP by solving and projecting the correlator
382410
383411 Parameters
@@ -387,7 +415,7 @@ def Eigenvalue(self, t0, ts=None, state=0, sort="Eigenvalue"):
387415
388416 All other parameters are identical to the ones of Corr.GEVP.
389417 """
390- vec = self .GEVP (t0 , ts = ts , sort = sort )[state ]
418+ vec = self .GEVP (t0 , ts = ts , sort = sort , ** kwargs )[state ]
391419 return self .projected (vec )
392420
393421 def Hankel (self , N , periodic = False ):
@@ -1386,8 +1414,13 @@ def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
13861414 return Corr (newcontent )
13871415
13881416
1389- def _sort_vectors (vec_set , ts ):
1417+ def _sort_vectors (vec_set_in , ts ):
13901418 """Helper function used to find a set of Eigenvectors consistent over all timeslices"""
1419+
1420+ if isinstance (vec_set_in [ts ][0 ][0 ], Obs ):
1421+ vec_set = [anp .vectorize (lambda x : float (x ))(vi ) if vi is not None else vi for vi in vec_set_in ]
1422+ else :
1423+ vec_set = vec_set_in
13911424 reference_sorting = np .array (vec_set [ts ])
13921425 N = reference_sorting .shape [0 ]
13931426 sorted_vec_set = []
@@ -1406,9 +1439,9 @@ def _sort_vectors(vec_set, ts):
14061439 if current_score > best_score :
14071440 best_score = current_score
14081441 best_perm = perm
1409- sorted_vec_set .append ([vec_set [t ][k ] for k in best_perm ])
1442+ sorted_vec_set .append ([vec_set_in [t ][k ] for k in best_perm ])
14101443 else :
1411- sorted_vec_set .append (vec_set [t ])
1444+ sorted_vec_set .append (vec_set_in [t ])
14121445
14131446 return sorted_vec_set
14141447
@@ -1418,10 +1451,63 @@ def _check_for_none(corr, entry):
14181451 return len (list (filter (None , np .asarray (entry ).flatten ()))) < corr .N ** 2
14191452
14201453
1421- def _GEVP_solver (Gt , G0 ):
1422- """Helper function for solving the GEVP and sorting the eigenvectors.
1454+ def _GEVP_solver (Gt , G0 , method = 'eigh' , chol_inv = None ):
1455+ r"""Helper function for solving the GEVP and sorting the eigenvectors.
1456+
1457+ Solves $G(t)v_i=\lambda_i G(t_0)v_i$ and returns the eigenvectors v_i
14231458
14241459 The helper function assumes that both provided matrices are symmetric and
14251460 only processes the lower triangular part of both matrices. In case the matrices
1426- are not symmetric the upper triangular parts are effectively discarded."""
1427- return scipy .linalg .eigh (Gt , G0 , lower = True )[1 ].T [::- 1 ]
1461+ are not symmetric the upper triangular parts are effectively discarded.
1462+
1463+ Parameters
1464+ ----------
1465+ Gt : array
1466+ The correlator at time t for the left hand side of the GEVP
1467+ G0 : array
1468+ The correlator at time t0 for the right hand side of the GEVP
1469+ Method used to solve the GEVP.
1470+ - "eigh": Use scipy.linalg.eigh to solve the GEVP.
1471+ - "cholesky": Use manually implemented solution via the Cholesky decomposition.
1472+ chol_inv : array, optional
1473+ Inverse of the Cholesky decomposition of G0. May be provided to
1474+ speed up the computation in the case of method=='cholesky'
1475+
1476+ """
1477+ if isinstance (G0 [0 ][0 ], Obs ):
1478+ vector_obs = True
1479+ else :
1480+ vector_obs = False
1481+
1482+ if method == 'cholesky' :
1483+ if vector_obs :
1484+ cholesky = linalg .cholesky
1485+ inv = linalg .inv
1486+ eigv = linalg .eigv
1487+ matmul = linalg .matmul
1488+ else :
1489+ cholesky = np .linalg .cholesky
1490+ inv = np .linalg .inv
1491+
1492+ def eigv (x , ** kwargs ):
1493+ return np .linalg .eigh (x )[1 ]
1494+
1495+ def matmul (* operands ):
1496+ return np .linalg .multi_dot (operands )
1497+ N = Gt .shape [0 ]
1498+ output = [[] for j in range (N )]
1499+ if chol_inv is None :
1500+ chol = cholesky (G0 ) # This will automatically report if the matrix is not pos-def
1501+ chol_inv = inv (chol )
1502+
1503+ try :
1504+ new_matrix = matmul (chol_inv , Gt , chol_inv .T )
1505+ ev = eigv (new_matrix )
1506+ ev = matmul (chol_inv .T , ev )
1507+ output = np .flip (ev , axis = 1 ).T
1508+ except (np .linalg .LinAlgError , TypeError , ValueError ): # The above code can fail because of linalg-errors or because the entry of the corr is None
1509+ for s in range (N ):
1510+ output [s ] = None
1511+ return output
1512+ elif method == 'eigh' :
1513+ return scipy .linalg .eigh (Gt , G0 , lower = True )[1 ].T [::- 1 ]
0 commit comments