pyerrors.correlators
1import warnings 2from itertools import permutations 3import numpy as np 4import autograd.numpy as anp 5import matplotlib.pyplot as plt 6import scipy.linalg 7from .obs import Obs, reweight, correlate, CObs 8from .misc import dump_object, _assert_equal_properties 9from .fits import least_squares 10from .roots import find_root 11from . import linalg 12 13 14class Corr: 15 r"""The class for a correlator (time dependent sequence of pe.Obs). 16 17 Everything, this class does, can be achieved using lists or arrays of Obs. 18 But it is simply more convenient to have a dedicated object for correlators. 19 One often wants to add or multiply correlators of the same length at every timeslice and it is inconvenient 20 to iterate over all timeslices for every operation. This is especially true, when dealing with matrices. 21 22 The correlator can have two types of content: An Obs at every timeslice OR a matrix at every timeslice. 23 Other dependency (eg. spatial) are not supported. 24 25 The Corr class can also deal with missing measurements or paddings for fixed boundary conditions. 26 The missing entries are represented via the `None` object. 27 28 Initialization 29 -------------- 30 A simple correlator can be initialized with a list or a one-dimensional array of `Obs` or `Cobs` 31 ```python 32 corr11 = pe.Corr([obs1, obs2]) 33 corr11 = pe.Corr(np.array([obs1, obs2])) 34 ``` 35 A matrix-valued correlator can either be initialized via a two-dimensional array of `Corr` objects 36 ```python 37 matrix_corr = pe.Corr(np.array([[corr11, corr12], [corr21, corr22]])) 38 ``` 39 or alternatively via a three-dimensional array of `Obs` or `CObs` of shape (T, N, N) where T is 40 the temporal extent of the correlator and N is the dimension of the matrix. 41 """ 42 43 __slots__ = ["content", "N", "T", "tag", "prange"] 44 45 def __init__(self, data_input, padding=[0, 0], prange=None): 46 """ Initialize a Corr object. 47 48 Parameters 49 ---------- 50 data_input : list or array 51 list of Obs or list of arrays of Obs or array of Corrs (see class docstring for details). 52 padding : list, optional 53 List with two entries where the first labels the padding 54 at the front of the correlator and the second the padding 55 at the back. 56 prange : list, optional 57 List containing the first and last timeslice of the plateau 58 region identified for this correlator. 59 """ 60 61 if isinstance(data_input, np.ndarray): 62 if data_input.ndim == 1: 63 data_input = list(data_input) 64 elif data_input.ndim == 2: 65 if not data_input.shape[0] == data_input.shape[1]: 66 raise ValueError("Array needs to be square.") 67 if not all([isinstance(item, Corr) for item in data_input.flatten()]): 68 raise ValueError("If the input is an array, its elements must be of type pe.Corr.") 69 if not all([item.N == 1 for item in data_input.flatten()]): 70 raise ValueError("Can only construct matrix correlator from single valued correlators.") 71 if not len(set([item.T for item in data_input.flatten()])) == 1: 72 raise ValueError("All input Correlators must be defined over the same timeslices.") 73 74 T = data_input[0, 0].T 75 N = data_input.shape[0] 76 input_as_list = [] 77 for t in range(T): 78 if any([(item.content[t] is None) for item in data_input.flatten()]): 79 if not all([(item.content[t] is None) for item in data_input.flatten()]): 80 warnings.warn("Input ill-defined at different timeslices. Conversion leads to data loss.!", RuntimeWarning) 81 input_as_list.append(None) 82 else: 83 array_at_timeslace = np.empty([N, N], dtype="object") 84 for i in range(N): 85 for j in range(N): 86 array_at_timeslace[i, j] = data_input[i, j][t] 87 input_as_list.append(array_at_timeslace) 88 data_input = input_as_list 89 elif data_input.ndim == 3: 90 if not data_input.shape[1] == data_input.shape[2]: 91 raise ValueError("Array needs to be square.") 92 data_input = list(data_input) 93 else: 94 raise ValueError("Arrays with ndim>3 not supported.") 95 96 if isinstance(data_input, list): 97 98 if all([isinstance(item, (Obs, CObs)) or item is None for item in data_input]): 99 _assert_equal_properties([o for o in data_input if o is not None]) 100 self.content = [np.asarray([item]) if item is not None else None for item in data_input] 101 self.N = 1 102 elif all([isinstance(item, np.ndarray) or item is None for item in data_input]) and any([isinstance(item, np.ndarray) for item in data_input]): 103 self.content = data_input 104 noNull = [a for a in self.content if not (a is None)] # To check if the matrices are correct for all undefined elements 105 self.N = noNull[0].shape[0] 106 if self.N > 1 and noNull[0].shape[0] != noNull[0].shape[1]: 107 raise ValueError("Smearing matrices are not NxN.") 108 if (not all([item.shape == noNull[0].shape for item in noNull])): 109 raise ValueError("Items in data_input are not of identical shape." + str(noNull)) 110 else: 111 raise TypeError("'data_input' contains item of wrong type.") 112 else: 113 raise TypeError("Data input was not given as list or correct array.") 114 115 self.tag = None 116 117 # An undefined timeslice is represented by the None object 118 self.content = [None] * padding[0] + self.content + [None] * padding[1] 119 self.T = len(self.content) 120 self.prange = prange 121 122 def __getitem__(self, idx): 123 """Return the content of timeslice idx""" 124 if self.content[idx] is None: 125 return None 126 elif len(self.content[idx]) == 1: 127 return self.content[idx][0] 128 else: 129 return self.content[idx] 130 131 @property 132 def reweighted(self): 133 bool_array = np.array([list(map(lambda x: x.reweighted, o)) for o in [x for x in self.content if x is not None]]) 134 if np.all(bool_array == 1): 135 return True 136 elif np.all(bool_array == 0): 137 return False 138 else: 139 raise Exception("Reweighting status of correlator corrupted.") 140 141 def gamma_method(self, **kwargs): 142 """Apply the gamma method to the content of the Corr.""" 143 for item in self.content: 144 if not (item is None): 145 if self.N == 1: 146 item[0].gamma_method(**kwargs) 147 else: 148 for i in range(self.N): 149 for j in range(self.N): 150 item[i, j].gamma_method(**kwargs) 151 152 gm = gamma_method 153 154 def projected(self, vector_l=None, vector_r=None, normalize=False): 155 """We need to project the Correlator with a Vector to get a single value at each timeslice. 156 157 The method can use one or two vectors. 158 If two are specified it returns v1@G@v2 (the order might be very important.) 159 By default it will return the lowest source, which usually means unsmeared-unsmeared (0,0), but it does not have to 160 """ 161 if self.N == 1: 162 raise Exception("Trying to project a Corr, that already has N=1.") 163 164 if vector_l is None: 165 vector_l, vector_r = np.asarray([1.] + (self.N - 1) * [0.]), np.asarray([1.] + (self.N - 1) * [0.]) 166 elif (vector_r is None): 167 vector_r = vector_l 168 if isinstance(vector_l, list) and not isinstance(vector_r, list): 169 if len(vector_l) != self.T: 170 raise Exception("Length of vector list must be equal to T") 171 vector_r = [vector_r] * self.T 172 if isinstance(vector_r, list) and not isinstance(vector_l, list): 173 if len(vector_r) != self.T: 174 raise Exception("Length of vector list must be equal to T") 175 vector_l = [vector_l] * self.T 176 177 if not isinstance(vector_l, list): 178 if not vector_l.shape == vector_r.shape == (self.N,): 179 raise Exception("Vectors are of wrong shape!") 180 if normalize: 181 vector_l, vector_r = vector_l / np.sqrt((vector_l @ vector_l)), vector_r / np.sqrt(vector_r @ vector_r) 182 newcontent = [None if _check_for_none(self, item) else np.asarray([vector_l.T @ item @ vector_r]) for item in self.content] 183 184 else: 185 # There are no checks here yet. There are so many possible scenarios, where this can go wrong. 186 if normalize: 187 for t in range(self.T): 188 vector_l[t], vector_r[t] = vector_l[t] / np.sqrt((vector_l[t] @ vector_l[t])), vector_r[t] / np.sqrt(vector_r[t] @ vector_r[t]) 189 190 newcontent = [None if (_check_for_none(self, self.content[t]) or vector_l[t] is None or vector_r[t] is None) else np.asarray([vector_l[t].T @ self.content[t] @ vector_r[t]]) for t in range(self.T)] 191 return Corr(newcontent) 192 193 def item(self, i, j): 194 """Picks the element [i,j] from every matrix and returns a correlator containing one Obs per timeslice. 195 196 Parameters 197 ---------- 198 i : int 199 First index to be picked. 200 j : int 201 Second index to be picked. 202 """ 203 if self.N == 1: 204 raise Exception("Trying to pick item from projected Corr") 205 newcontent = [None if (item is None) else item[i, j] for item in self.content] 206 return Corr(newcontent) 207 208 def plottable(self): 209 """Outputs the correlator in a plotable format. 210 211 Outputs three lists containing the timeslice index, the value on each 212 timeslice and the error on each timeslice. 213 """ 214 if self.N != 1: 215 raise Exception("Can only make Corr[N=1] plottable") 216 x_list = [x for x in range(self.T) if not self.content[x] is None] 217 y_list = [y[0].value for y in self.content if y is not None] 218 y_err_list = [y[0].dvalue for y in self.content if y is not None] 219 220 return x_list, y_list, y_err_list 221 222 def symmetric(self): 223 """ Symmetrize the correlator around x0=0.""" 224 if self.N != 1: 225 raise Exception('symmetric cannot be safely applied to multi-dimensional correlators.') 226 if self.T % 2 != 0: 227 raise Exception("Can not symmetrize odd T") 228 229 if self.content[0] is not None: 230 if np.argmax(np.abs([o[0].value if o is not None else 0 for o in self.content])) != 0: 231 warnings.warn("Correlator does not seem to be symmetric around x0=0.", RuntimeWarning) 232 233 newcontent = [self.content[0]] 234 for t in range(1, self.T): 235 if (self.content[t] is None) or (self.content[self.T - t] is None): 236 newcontent.append(None) 237 else: 238 newcontent.append(0.5 * (self.content[t] + self.content[self.T - t])) 239 if (all([x is None for x in newcontent])): 240 raise Exception("Corr could not be symmetrized: No redundant values") 241 return Corr(newcontent, prange=self.prange) 242 243 def anti_symmetric(self): 244 """Anti-symmetrize the correlator around x0=0.""" 245 if self.N != 1: 246 raise TypeError('anti_symmetric cannot be safely applied to multi-dimensional correlators.') 247 if self.T % 2 != 0: 248 raise Exception("Can not symmetrize odd T") 249 250 test = 1 * self 251 test.gamma_method() 252 if not all([o.is_zero_within_error(3) for o in test.content[0]]): 253 warnings.warn("Correlator does not seem to be anti-symmetric around x0=0.", RuntimeWarning) 254 255 newcontent = [self.content[0]] 256 for t in range(1, self.T): 257 if (self.content[t] is None) or (self.content[self.T - t] is None): 258 newcontent.append(None) 259 else: 260 newcontent.append(0.5 * (self.content[t] - self.content[self.T - t])) 261 if (all([x is None for x in newcontent])): 262 raise Exception("Corr could not be symmetrized: No redundant values") 263 return Corr(newcontent, prange=self.prange) 264 265 def is_matrix_symmetric(self): 266 """Checks whether a correlator matrices is symmetric on every timeslice.""" 267 if self.N == 1: 268 raise TypeError("Only works for correlator matrices.") 269 for t in range(self.T): 270 if self[t] is None: 271 continue 272 for i in range(self.N): 273 for j in range(i + 1, self.N): 274 if self[t][i, j] is self[t][j, i]: 275 continue 276 if hash(self[t][i, j]) != hash(self[t][j, i]): 277 return False 278 return True 279 280 def trace(self): 281 """Calculates the per-timeslice trace of a correlator matrix.""" 282 if self.N == 1: 283 raise ValueError("Only works for correlator matrices.") 284 newcontent = [] 285 for t in range(self.T): 286 if _check_for_none(self, self.content[t]): 287 newcontent.append(None) 288 else: 289 newcontent.append(np.trace(self.content[t])) 290 return Corr(newcontent) 291 292 def matrix_symmetric(self): 293 """Symmetrizes the correlator matrices on every timeslice.""" 294 if self.N == 1: 295 raise Exception("Trying to symmetrize a correlator matrix, that already has N=1.") 296 if self.is_matrix_symmetric(): 297 return 1.0 * self 298 else: 299 transposed = [None if _check_for_none(self, G) else G.T for G in self.content] 300 return 0.5 * (Corr(transposed) + self) 301 302 def GEVP(self, t0, ts=None, sort="Eigenvalue", vector_obs=False, **kwargs): 303 r'''Solve the generalized eigenvalue problem on the correlator matrix and returns the corresponding eigenvectors. 304 305 The eigenvectors are sorted according to the descending eigenvalues, the zeroth eigenvector(s) correspond to the 306 largest eigenvalue(s). The eigenvector(s) for the individual states can be accessed via slicing 307 ```python 308 C.GEVP(t0=2)[0] # Ground state vector(s) 309 C.GEVP(t0=2)[:3] # Vectors for the lowest three states 310 ``` 311 312 Parameters 313 ---------- 314 t0 : int 315 The time t0 for the right hand side of the GEVP according to $G(t)v_i=\lambda_i G(t_0)v_i$ 316 ts : int 317 fixed time $G(t_s)v_i=\lambda_i G(t_0)v_i$ if sort=None. 318 If sort="Eigenvector" it gives a reference point for the sorting method. 319 sort : string 320 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. 321 - "Eigenvalue": The eigenvector is chosen according to which eigenvalue it belongs individually on every timeslice. (default) 322 - "Eigenvector": Use the method described in arXiv:2004.10472 to find the set of v(t) belonging to the state. 323 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). 327 328 Other Parameters 329 ---------------- 330 state : int 331 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. 336 ''' 337 338 if self.N == 1: 339 raise Exception("GEVP methods only works on correlator matrices and not single correlators.") 340 if ts is not None: 341 if (ts <= t0): 342 raise Exception("ts has to be larger than t0.") 343 344 if "sorted_list" in kwargs: 345 warnings.warn("Argument 'sorted_list' is deprecated, use 'sort' instead.", DeprecationWarning) 346 sort = kwargs.get("sorted_list") 347 348 if self.is_matrix_symmetric(): 349 symmetric_corr = self 350 else: 351 symmetric_corr = self.matrix_symmetric() 352 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 371 372 if sort is None: 373 if (ts is None): 374 raise Exception("ts is required if sort=None.") 375 if (self.content[t0] is None) or (self.content[ts] is None): 376 raise Exception("Corr not defined at t0/ts.") 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] 381 382 elif sort in ["Eigenvalue", "Eigenvector"]: 383 if sort == "Eigenvalue" and ts is not None: 384 warnings.warn("ts has no effect when sorting by eigenvalue is chosen.", RuntimeWarning) 385 all_vecs = [None] * (t0 + 1) 386 for t in range(t0 + 1, self.T): 387 try: 388 Gt = _get_mat_at_t(t) 389 all_vecs.append(_GEVP_solver(Gt, G0, method=method, chol_inv=chol_inv)) 390 except Exception: 391 all_vecs.append(None) 392 if sort == "Eigenvector": 393 if ts is None: 394 raise Exception("ts is required for the Eigenvector sorting method.") 395 all_vecs = _sort_vectors(all_vecs, ts) 396 397 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] 400 else: 401 raise Exception("Unknown value for 'sort'. Choose 'Eigenvalue', 'Eigenvector' or None.") 402 403 if "state" in kwargs: 404 return reordered_vecs[kwargs.get("state")] 405 else: 406 return reordered_vecs 407 408 def Eigenvalue(self, t0, ts=None, state=0, sort="Eigenvalue", **kwargs): 409 """Determines the eigenvalue of the GEVP by solving and projecting the correlator 410 411 Parameters 412 ---------- 413 state : int 414 The state one is interested in ordered by energy. The lowest state is zero. 415 416 All other parameters are identical to the ones of Corr.GEVP. 417 """ 418 vec = self.GEVP(t0, ts=ts, sort=sort, **kwargs)[state] 419 return self.projected(vec) 420 421 def Hankel(self, N, periodic=False): 422 """Constructs an NxN Hankel matrix 423 424 C(t) c(t+1) ... c(t+n-1) 425 C(t+1) c(t+2) ... c(t+n) 426 ................. 427 C(t+(n-1)) c(t+n) ... c(t+2(n-1)) 428 429 Parameters 430 ---------- 431 N : int 432 Dimension of the Hankel matrix 433 periodic : bool, optional 434 determines whether the matrix is extended periodically 435 """ 436 437 if self.N != 1: 438 raise Exception("Multi-operator Prony not implemented!") 439 440 array = np.empty([N, N], dtype="object") 441 new_content = [] 442 for t in range(self.T): 443 new_content.append(array.copy()) 444 445 def wrap(i): 446 while i >= self.T: 447 i -= self.T 448 return i 449 450 for t in range(self.T): 451 for i in range(N): 452 for j in range(N): 453 if periodic: 454 new_content[t][i, j] = self.content[wrap(t + i + j)][0] 455 elif (t + i + j) >= self.T: 456 new_content[t] = None 457 else: 458 new_content[t][i, j] = self.content[t + i + j][0] 459 460 return Corr(new_content) 461 462 def roll(self, dt): 463 """Periodically shift the correlator by dt timeslices 464 465 Parameters 466 ---------- 467 dt : int 468 number of timeslices 469 """ 470 return Corr(list(np.roll(np.array(self.content, dtype=object), dt, axis=0))) 471 472 def reverse(self): 473 """Reverse the time ordering of the Corr""" 474 return Corr(self.content[:: -1]) 475 476 def thin(self, spacing=2, offset=0): 477 """Thin out a correlator to suppress correlations 478 479 Parameters 480 ---------- 481 spacing : int 482 Keep only every 'spacing'th entry of the correlator 483 offset : int 484 Offset the equal spacing 485 """ 486 new_content = [] 487 for t in range(self.T): 488 if (offset + t) % spacing != 0: 489 new_content.append(None) 490 else: 491 new_content.append(self.content[t]) 492 return Corr(new_content) 493 494 def correlate(self, partner): 495 """Correlate the correlator with another correlator or Obs 496 497 Parameters 498 ---------- 499 partner : Obs or Corr 500 partner to correlate the correlator with. 501 Can either be an Obs which is correlated with all entries of the 502 correlator or a Corr of same length. 503 """ 504 if self.N != 1: 505 raise Exception("Only one-dimensional correlators can be safely correlated.") 506 new_content = [] 507 for x0, t_slice in enumerate(self.content): 508 if _check_for_none(self, t_slice): 509 new_content.append(None) 510 else: 511 if isinstance(partner, Corr): 512 if _check_for_none(partner, partner.content[x0]): 513 new_content.append(None) 514 else: 515 new_content.append(np.array([correlate(o, partner.content[x0][0]) for o in t_slice])) 516 elif isinstance(partner, Obs): # Should this include CObs? 517 new_content.append(np.array([correlate(o, partner) for o in t_slice])) 518 else: 519 raise Exception("Can only correlate with an Obs or a Corr.") 520 521 return Corr(new_content) 522 523 def reweight(self, weight, **kwargs): 524 """Reweight the correlator. 525 526 Parameters 527 ---------- 528 weight : Obs 529 Reweighting factor. An Observable that has to be defined on a superset of the 530 configurations in obs[i].idl for all i. 531 all_configs : bool 532 if True, the reweighted observables are normalized by the average of 533 the reweighting factor on all configurations in weight.idl and not 534 on the configurations in obs[i].idl. 535 """ 536 if self.N != 1: 537 raise Exception("Reweighting only implemented for one-dimensional correlators.") 538 new_content = [] 539 for t_slice in self.content: 540 if _check_for_none(self, t_slice): 541 new_content.append(None) 542 else: 543 new_content.append(np.array(reweight(weight, t_slice, **kwargs))) 544 return Corr(new_content) 545 546 def T_symmetry(self, partner, parity=+1): 547 """Return the time symmetry average of the correlator and its partner 548 549 Parameters 550 ---------- 551 partner : Corr 552 Time symmetry partner of the Corr 553 parity : int 554 Parity quantum number of the correlator, can be +1 or -1 555 """ 556 if self.N != 1: 557 raise Exception("T_symmetry only implemented for one-dimensional correlators.") 558 if not isinstance(partner, Corr): 559 raise Exception("T partner has to be a Corr object.") 560 if parity not in [+1, -1]: 561 raise Exception("Parity has to be +1 or -1.") 562 T_partner = parity * partner.reverse() 563 564 t_slices = [] 565 test = (self - T_partner) 566 test.gamma_method() 567 for x0, t_slice in enumerate(test.content): 568 if t_slice is not None: 569 if not t_slice[0].is_zero_within_error(5): 570 t_slices.append(x0) 571 if t_slices: 572 warnings.warn("T symmetry partners do not agree within 5 sigma on time slices " + str(t_slices) + ".", RuntimeWarning) 573 574 return (self + T_partner) / 2 575 576 def deriv(self, variant="symmetric"): 577 """Return the first derivative of the correlator with respect to x0. 578 579 Parameters 580 ---------- 581 variant : str 582 decides which definition of the finite differences derivative is used. 583 Available choice: symmetric, forward, backward, improved, log, default: symmetric 584 """ 585 if self.N != 1: 586 raise Exception("deriv only implemented for one-dimensional correlators.") 587 if variant == "symmetric": 588 newcontent = [] 589 for t in range(1, self.T - 1): 590 if (self.content[t - 1] is None) or (self.content[t + 1] is None): 591 newcontent.append(None) 592 else: 593 newcontent.append(0.5 * (self.content[t + 1] - self.content[t - 1])) 594 if (all([x is None for x in newcontent])): 595 raise Exception('Derivative is undefined at all timeslices') 596 return Corr(newcontent, padding=[1, 1]) 597 elif variant == "forward": 598 newcontent = [] 599 for t in range(self.T - 1): 600 if (self.content[t] is None) or (self.content[t + 1] is None): 601 newcontent.append(None) 602 else: 603 newcontent.append(self.content[t + 1] - self.content[t]) 604 if (all([x is None for x in newcontent])): 605 raise Exception("Derivative is undefined at all timeslices") 606 return Corr(newcontent, padding=[0, 1]) 607 elif variant == "backward": 608 newcontent = [] 609 for t in range(1, self.T): 610 if (self.content[t - 1] is None) or (self.content[t] is None): 611 newcontent.append(None) 612 else: 613 newcontent.append(self.content[t] - self.content[t - 1]) 614 if (all([x is None for x in newcontent])): 615 raise Exception("Derivative is undefined at all timeslices") 616 return Corr(newcontent, padding=[1, 0]) 617 elif variant == "improved": 618 newcontent = [] 619 for t in range(2, self.T - 2): 620 if (self.content[t - 2] is None) or (self.content[t - 1] is None) or (self.content[t + 1] is None) or (self.content[t + 2] is None): 621 newcontent.append(None) 622 else: 623 newcontent.append((1 / 12) * (self.content[t - 2] - 8 * self.content[t - 1] + 8 * self.content[t + 1] - self.content[t + 2])) 624 if (all([x is None for x in newcontent])): 625 raise Exception('Derivative is undefined at all timeslices') 626 return Corr(newcontent, padding=[2, 2]) 627 elif variant == 'log': 628 newcontent = [] 629 for t in range(self.T): 630 if (self.content[t] is None) or (self.content[t] <= 0): 631 newcontent.append(None) 632 else: 633 newcontent.append(np.log(self.content[t])) 634 if (all([x is None for x in newcontent])): 635 raise Exception("Log is undefined at all timeslices") 636 logcorr = Corr(newcontent) 637 return self * logcorr.deriv('symmetric') 638 else: 639 raise Exception("Unknown variant.") 640 641 def second_deriv(self, variant="symmetric"): 642 r"""Return the second derivative of the correlator with respect to x0. 643 644 Parameters 645 ---------- 646 variant : str 647 decides which definition of the finite differences derivative is used. 648 Available choice: 649 - symmetric (default) 650 $$\tilde{\partial}^2_0 f(x_0) = f(x_0+1)-2f(x_0)+f(x_0-1)$$ 651 - big_symmetric 652 $$\partial^2_0 f(x_0) = \frac{f(x_0+2)-2f(x_0)+f(x_0-2)}{4}$$ 653 - improved 654 $$\partial^2_0 f(x_0) = \frac{-f(x_0+2) + 16 * f(x_0+1) - 30 * f(x_0) + 16 * f(x_0-1) - f(x_0-2)}{12}$$ 655 - log 656 $$f(x) = \tilde{\partial}^2_0 log(f(x_0))+(\tilde{\partial}_0 log(f(x_0)))^2$$ 657 """ 658 if self.N != 1: 659 raise Exception("second_deriv only implemented for one-dimensional correlators.") 660 if variant == "symmetric": 661 newcontent = [] 662 for t in range(1, self.T - 1): 663 if (self.content[t - 1] is None) or (self.content[t + 1] is None): 664 newcontent.append(None) 665 else: 666 newcontent.append((self.content[t + 1] - 2 * self.content[t] + self.content[t - 1])) 667 if (all([x is None for x in newcontent])): 668 raise Exception("Derivative is undefined at all timeslices") 669 return Corr(newcontent, padding=[1, 1]) 670 elif variant == "big_symmetric": 671 newcontent = [] 672 for t in range(2, self.T - 2): 673 if (self.content[t - 2] is None) or (self.content[t + 2] is None): 674 newcontent.append(None) 675 else: 676 newcontent.append((self.content[t + 2] - 2 * self.content[t] + self.content[t - 2]) / 4) 677 if (all([x is None for x in newcontent])): 678 raise Exception("Derivative is undefined at all timeslices") 679 return Corr(newcontent, padding=[2, 2]) 680 elif variant == "improved": 681 newcontent = [] 682 for t in range(2, self.T - 2): 683 if (self.content[t - 2] is None) or (self.content[t - 1] is None) or (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 2] is None): 684 newcontent.append(None) 685 else: 686 newcontent.append((1 / 12) * (-self.content[t + 2] + 16 * self.content[t + 1] - 30 * self.content[t] + 16 * self.content[t - 1] - self.content[t - 2])) 687 if (all([x is None for x in newcontent])): 688 raise Exception("Derivative is undefined at all timeslices") 689 return Corr(newcontent, padding=[2, 2]) 690 elif variant == 'log': 691 newcontent = [] 692 for t in range(self.T): 693 if (self.content[t] is None) or (self.content[t] <= 0): 694 newcontent.append(None) 695 else: 696 newcontent.append(np.log(self.content[t])) 697 if (all([x is None for x in newcontent])): 698 raise Exception("Log is undefined at all timeslices") 699 logcorr = Corr(newcontent) 700 return self * (logcorr.second_deriv('symmetric') + (logcorr.deriv('symmetric'))**2) 701 else: 702 raise Exception("Unknown variant.") 703 704 def m_eff(self, variant='log', guess=1.0): 705 """Returns the effective mass of the correlator as correlator object 706 707 Parameters 708 ---------- 709 variant : str 710 log : uses the standard effective mass log(C(t) / C(t+1)) 711 cosh, periodic : Use periodicity of the correlator by solving C(t) / C(t+1) = cosh(m * (t - T/2)) / cosh(m * (t + 1 - T/2)) for m. 712 sinh : Use anti-periodicity of the correlator by solving C(t) / C(t+1) = sinh(m * (t - T/2)) / sinh(m * (t + 1 - T/2)) for m. 713 See, e.g., arXiv:1205.5380 714 arccosh : Uses the explicit form of the symmetrized correlator (not recommended) 715 logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2 716 guess : float 717 guess for the root finder, only relevant for the root variant 718 """ 719 if self.N != 1: 720 raise Exception('Correlator must be projected before getting m_eff') 721 if variant == 'log': 722 newcontent = [] 723 for t in range(self.T - 1): 724 if ((self.content[t] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 725 newcontent.append(None) 726 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 727 newcontent.append(None) 728 else: 729 newcontent.append(self.content[t] / self.content[t + 1]) 730 if (all([x is None for x in newcontent])): 731 raise Exception('m_eff is undefined at all timeslices') 732 733 return np.log(Corr(newcontent, padding=[0, 1])) 734 735 elif variant == 'logsym': 736 newcontent = [] 737 for t in range(1, self.T - 1): 738 if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 739 newcontent.append(None) 740 elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0: 741 newcontent.append(None) 742 else: 743 newcontent.append(self.content[t - 1] / self.content[t + 1]) 744 if (all([x is None for x in newcontent])): 745 raise Exception('m_eff is undefined at all timeslices') 746 747 return np.log(Corr(newcontent, padding=[1, 1])) / 2 748 749 elif variant in ['periodic', 'cosh', 'sinh']: 750 if variant in ['periodic', 'cosh']: 751 func = anp.cosh 752 else: 753 func = anp.sinh 754 755 def root_function(x, d): 756 return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d 757 758 newcontent = [] 759 for t in range(self.T - 1): 760 if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0): 761 newcontent.append(None) 762 # Fill the two timeslices in the middle of the lattice with their predecessors 763 elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]: 764 newcontent.append(newcontent[-1]) 765 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 766 newcontent.append(None) 767 else: 768 newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess))) 769 if (all([x is None for x in newcontent])): 770 raise Exception('m_eff is undefined at all timeslices') 771 772 return Corr(newcontent, padding=[0, 1]) 773 774 elif variant == 'arccosh': 775 newcontent = [] 776 for t in range(1, self.T - 1): 777 if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t - 1] is None) or (self.content[t][0].value == 0): 778 newcontent.append(None) 779 else: 780 newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t])) 781 if (all([x is None for x in newcontent])): 782 raise Exception("m_eff is undefined at all timeslices") 783 return np.arccosh(Corr(newcontent, padding=[1, 1])) 784 785 else: 786 raise Exception('Unknown variant.') 787 788 def fit(self, function, fitrange=None, silent=False, **kwargs): 789 r'''Fits function to the data 790 791 Parameters 792 ---------- 793 function : obj 794 function to fit to the data. See fits.least_squares for details. 795 fitrange : list 796 Two element list containing the timeslices on which the fit is supposed to start and stop. 797 Caution: This range is inclusive as opposed to standard python indexing. 798 `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6. 799 If not specified, self.prange or all timeslices are used. 800 silent : bool 801 Decides whether output is printed to the standard output. 802 ''' 803 if self.N != 1: 804 raise Exception("Correlator must be projected before fitting") 805 806 if fitrange is None: 807 if self.prange: 808 fitrange = self.prange 809 else: 810 fitrange = [0, self.T - 1] 811 else: 812 if not isinstance(fitrange, list): 813 raise Exception("fitrange has to be a list with two elements") 814 if len(fitrange) != 2: 815 raise Exception("fitrange has to have exactly two elements [fit_start, fit_stop]") 816 817 xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]) 818 ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]) 819 result = least_squares(xs, ys, function, silent=silent, **kwargs) 820 return result 821 822 def plateau(self, plateau_range=None, method="fit", auto_gamma=False): 823 """ Extract a plateau value from a Corr object 824 825 Parameters 826 ---------- 827 plateau_range : list 828 list with two entries, indicating the first and the last timeslice 829 of the plateau region. 830 method : str 831 method to extract the plateau. 832 'fit' fits a constant to the plateau region 833 'avg', 'average' or 'mean' just average over the given timeslices. 834 auto_gamma : bool 835 apply gamma_method with default parameters to the Corr. Defaults to None 836 """ 837 if not plateau_range: 838 if self.prange: 839 plateau_range = self.prange 840 else: 841 raise Exception("no plateau range provided") 842 if self.N != 1: 843 raise Exception("Correlator must be projected before getting a plateau.") 844 if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])): 845 raise Exception("plateau is undefined at all timeslices in plateaurange.") 846 if auto_gamma: 847 self.gamma_method() 848 if method == "fit": 849 def const_func(a, t): 850 return a[0] 851 return self.fit(const_func, plateau_range)[0] 852 elif method in ["avg", "average", "mean"]: 853 returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None]) 854 return returnvalue 855 856 else: 857 raise Exception("Unsupported plateau method: " + method) 858 859 def set_prange(self, prange): 860 """Sets the attribute prange of the Corr object.""" 861 if not len(prange) == 2: 862 raise Exception("prange must be a list or array with two values") 863 if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))): 864 raise Exception("Start and end point must be integers") 865 if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] < prange[1]): 866 raise Exception("Start and end point must define a range in the interval 0,T") 867 868 self.prange = prange 869 return 870 871 def show(self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, fit_key=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None): 872 """Plots the correlator using the tag of the correlator as label if available. 873 874 Parameters 875 ---------- 876 x_range : list 877 list of two values, determining the range of the x-axis e.g. [4, 8]. 878 comp : Corr or list of Corr 879 Correlator or list of correlators which are plotted for comparison. 880 The tags of these correlators are used as labels if available. 881 logscale : bool 882 Sets y-axis to logscale. 883 plateau : Obs 884 Plateau value to be visualized in the figure. 885 fit_res : Fit_result 886 Fit_result object to be visualized. 887 fit_key : str 888 Key for the fit function in Fit_result.fit_function (for combined fits). 889 ylabel : str 890 Label for the y-axis. 891 save : str 892 path to file in which the figure should be saved. 893 auto_gamma : bool 894 Apply the gamma method with standard parameters to all correlators and plateau values before plotting. 895 hide_sigma : float 896 Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors. 897 references : list 898 List of floating point values that are displayed as horizontal lines for reference. 899 title : string 900 Optional title of the figure. 901 """ 902 if self.N != 1: 903 raise Exception("Correlator must be projected before plotting") 904 905 if auto_gamma: 906 self.gamma_method() 907 908 if x_range is None: 909 x_range = [0, self.T - 1] 910 911 fig = plt.figure() 912 ax1 = fig.add_subplot(111) 913 914 x, y, y_err = self.plottable() 915 if hide_sigma: 916 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 917 else: 918 hide_from = None 919 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag) 920 if logscale: 921 ax1.set_yscale('log') 922 else: 923 if y_range is None: 924 try: 925 y_min = min([(x[0].value - x[0].dvalue) for x in self.content[x_range[0]: x_range[1] + 1] if (x is not None) and x[0].dvalue < 2 * np.abs(x[0].value)]) 926 y_max = max([(x[0].value + x[0].dvalue) for x in self.content[x_range[0]: x_range[1] + 1] if (x is not None) and x[0].dvalue < 2 * np.abs(x[0].value)]) 927 ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)]) 928 except Exception: 929 pass 930 else: 931 ax1.set_ylim(y_range) 932 if comp: 933 if isinstance(comp, (Corr, list)): 934 for corr in comp if isinstance(comp, list) else [comp]: 935 if auto_gamma: 936 corr.gamma_method() 937 x, y, y_err = corr.plottable() 938 if hide_sigma: 939 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 940 else: 941 hide_from = None 942 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor']) 943 else: 944 raise Exception("'comp' must be a correlator or a list of correlators.") 945 946 if plateau: 947 if isinstance(plateau, Obs): 948 if auto_gamma: 949 plateau.gamma_method() 950 ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau)) 951 ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-') 952 else: 953 raise Exception("'plateau' must be an Obs") 954 955 if references: 956 if isinstance(references, list): 957 for ref in references: 958 ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--') 959 else: 960 raise Exception("'references' must be a list of floating pint values.") 961 962 if self.prange: 963 ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',', color="black", zorder=0) 964 ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',', color="black", zorder=0) 965 966 if fit_res: 967 x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05) 968 if isinstance(fit_res.fit_function, dict): 969 if fit_key: 970 ax1.plot(x_samples, fit_res.fit_function[fit_key]([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2) 971 else: 972 raise ValueError("Please provide a 'fit_key' for visualizing combined fits.") 973 else: 974 ax1.plot(x_samples, fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2) 975 976 ax1.set_xlabel(r'$x_0 / a$') 977 if ylabel: 978 ax1.set_ylabel(ylabel) 979 ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5]) 980 981 handles, labels = ax1.get_legend_handles_labels() 982 if labels: 983 ax1.legend() 984 985 if title: 986 plt.title(title) 987 988 plt.draw() 989 990 if save: 991 if isinstance(save, str): 992 fig.savefig(save, bbox_inches='tight') 993 else: 994 raise Exception("'save' has to be a string.") 995 996 def spaghetti_plot(self, logscale=True): 997 """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations. 998 999 Parameters 1000 ---------- 1001 logscale : bool 1002 Determines whether the scale of the y-axis is logarithmic or standard. 1003 """ 1004 if self.N != 1: 1005 raise Exception("Correlator needs to be projected first.") 1006 1007 mc_names = list(set([item for sublist in [sum(map(o[0].e_content.get, o[0].mc_names), []) for o in self.content if o is not None] for item in sublist])) 1008 x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content) if o is not None] 1009 1010 for name in mc_names: 1011 data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T 1012 1013 fig = plt.figure() 1014 ax = fig.add_subplot(111) 1015 for dat in data: 1016 ax.plot(x0_vals, dat, ls='-', marker='') 1017 1018 if logscale is True: 1019 ax.set_yscale('log') 1020 1021 ax.set_xlabel(r'$x_0 / a$') 1022 plt.title(name) 1023 plt.draw() 1024 1025 def dump(self, filename, datatype="json.gz", **kwargs): 1026 """Dumps the Corr into a file of chosen type 1027 Parameters 1028 ---------- 1029 filename : str 1030 Name of the file to be saved. 1031 datatype : str 1032 Format of the exported file. Supported formats include 1033 "json.gz" and "pickle" 1034 path : str 1035 specifies a custom path for the file (default '.') 1036 """ 1037 if datatype == "json.gz": 1038 from .input.json import dump_to_json 1039 if 'path' in kwargs: 1040 file_name = kwargs.get('path') + '/' + filename 1041 else: 1042 file_name = filename 1043 dump_to_json(self, file_name) 1044 elif datatype == "pickle": 1045 dump_object(self, filename, **kwargs) 1046 else: 1047 raise Exception("Unknown datatype " + str(datatype)) 1048 1049 def print(self, print_range=None): 1050 print(self.__repr__(print_range)) 1051 1052 def __repr__(self, print_range=None): 1053 if print_range is None: 1054 print_range = [0, None] 1055 1056 content_string = "" 1057 content_string += "Corr T=" + str(self.T) + " N=" + str(self.N) + "\n" # +" filled with"+ str(type(self.content[0][0])) there should be a good solution here 1058 1059 if self.tag is not None: 1060 content_string += "Description: " + self.tag + "\n" 1061 if self.N != 1: 1062 return content_string 1063 1064 if print_range[1]: 1065 print_range[1] += 1 1066 content_string += 'x0/a\tCorr(x0/a)\n------------------\n' 1067 for i, sub_corr in enumerate(self.content[print_range[0]:print_range[1]]): 1068 if sub_corr is None: 1069 content_string += str(i + print_range[0]) + '\n' 1070 else: 1071 content_string += str(i + print_range[0]) 1072 for element in sub_corr: 1073 content_string += f"\t{element:+2}" 1074 content_string += '\n' 1075 return content_string 1076 1077 def __str__(self): 1078 return self.__repr__() 1079 1080 # We define the basic operations, that can be performed with correlators. 1081 # While */+- get defined here, they only work for Corr*Obs and not Obs*Corr. 1082 # This is because Obs*Corr checks Obs.__mul__ first and does not catch an exception. 1083 # One could try and tell Obs to check if the y in __mul__ is a Corr and 1084 1085 __array_priority__ = 10000 1086 1087 def __eq__(self, y): 1088 if isinstance(y, Corr): 1089 comp = np.asarray(y.content, dtype=object) 1090 else: 1091 comp = np.asarray(y) 1092 return np.asarray(self.content, dtype=object) == comp 1093 1094 def __add__(self, y): 1095 if isinstance(y, Corr): 1096 if ((self.N != y.N) or (self.T != y.T)): 1097 raise Exception("Addition of Corrs with different shape") 1098 newcontent = [] 1099 for t in range(self.T): 1100 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1101 newcontent.append(None) 1102 else: 1103 newcontent.append(self.content[t] + y.content[t]) 1104 return Corr(newcontent) 1105 1106 elif isinstance(y, (Obs, int, float, CObs, complex)): 1107 newcontent = [] 1108 for t in range(self.T): 1109 if _check_for_none(self, self.content[t]): 1110 newcontent.append(None) 1111 else: 1112 newcontent.append(self.content[t] + y) 1113 return Corr(newcontent, prange=self.prange) 1114 elif isinstance(y, np.ndarray): 1115 if y.shape == (self.T,): 1116 return Corr(list((np.array(self.content).T + y).T)) 1117 else: 1118 raise ValueError("operands could not be broadcast together") 1119 else: 1120 raise TypeError("Corr + wrong type") 1121 1122 def __mul__(self, y): 1123 if isinstance(y, Corr): 1124 if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): 1125 raise Exception("Multiplication of Corr object requires N=N or N=1 and T=T") 1126 newcontent = [] 1127 for t in range(self.T): 1128 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1129 newcontent.append(None) 1130 else: 1131 newcontent.append(self.content[t] * y.content[t]) 1132 return Corr(newcontent) 1133 1134 elif isinstance(y, (Obs, int, float, CObs, complex)): 1135 newcontent = [] 1136 for t in range(self.T): 1137 if _check_for_none(self, self.content[t]): 1138 newcontent.append(None) 1139 else: 1140 newcontent.append(self.content[t] * y) 1141 return Corr(newcontent, prange=self.prange) 1142 elif isinstance(y, np.ndarray): 1143 if y.shape == (self.T,): 1144 return Corr(list((np.array(self.content).T * y).T)) 1145 else: 1146 raise ValueError("operands could not be broadcast together") 1147 else: 1148 raise TypeError("Corr * wrong type") 1149 1150 def __matmul__(self, y): 1151 if isinstance(y, np.ndarray): 1152 if y.ndim != 2 or y.shape[0] != y.shape[1]: 1153 raise ValueError("Can only multiply correlators by square matrices.") 1154 if not self.N == y.shape[0]: 1155 raise ValueError("matmul: mismatch of matrix dimensions") 1156 newcontent = [] 1157 for t in range(self.T): 1158 if _check_for_none(self, self.content[t]): 1159 newcontent.append(None) 1160 else: 1161 newcontent.append(self.content[t] @ y) 1162 return Corr(newcontent) 1163 elif isinstance(y, Corr): 1164 if not self.N == y.N: 1165 raise ValueError("matmul: mismatch of matrix dimensions") 1166 newcontent = [] 1167 for t in range(self.T): 1168 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1169 newcontent.append(None) 1170 else: 1171 newcontent.append(self.content[t] @ y.content[t]) 1172 return Corr(newcontent) 1173 1174 else: 1175 return NotImplemented 1176 1177 def __rmatmul__(self, y): 1178 if isinstance(y, np.ndarray): 1179 if y.ndim != 2 or y.shape[0] != y.shape[1]: 1180 raise ValueError("Can only multiply correlators by square matrices.") 1181 if not self.N == y.shape[0]: 1182 raise ValueError("matmul: mismatch of matrix dimensions") 1183 newcontent = [] 1184 for t in range(self.T): 1185 if _check_for_none(self, self.content[t]): 1186 newcontent.append(None) 1187 else: 1188 newcontent.append(y @ self.content[t]) 1189 return Corr(newcontent) 1190 else: 1191 return NotImplemented 1192 1193 def __truediv__(self, y): 1194 if isinstance(y, Corr): 1195 if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): 1196 raise Exception("Multiplication of Corr object requires N=N or N=1 and T=T") 1197 newcontent = [] 1198 for t in range(self.T): 1199 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1200 newcontent.append(None) 1201 else: 1202 newcontent.append(self.content[t] / y.content[t]) 1203 for t in range(self.T): 1204 if _check_for_none(self, newcontent[t]): 1205 continue 1206 if np.isnan(np.sum(newcontent[t]).value): 1207 newcontent[t] = None 1208 1209 if all([item is None for item in newcontent]): 1210 raise Exception("Division returns completely undefined correlator") 1211 return Corr(newcontent) 1212 1213 elif isinstance(y, (Obs, CObs)): 1214 if isinstance(y, Obs): 1215 if y.value == 0: 1216 raise Exception('Division by zero will return undefined correlator') 1217 if isinstance(y, CObs): 1218 if y.is_zero(): 1219 raise Exception('Division by zero will return undefined correlator') 1220 1221 newcontent = [] 1222 for t in range(self.T): 1223 if _check_for_none(self, self.content[t]): 1224 newcontent.append(None) 1225 else: 1226 newcontent.append(self.content[t] / y) 1227 return Corr(newcontent, prange=self.prange) 1228 1229 elif isinstance(y, (int, float)): 1230 if y == 0: 1231 raise Exception('Division by zero will return undefined correlator') 1232 newcontent = [] 1233 for t in range(self.T): 1234 if _check_for_none(self, self.content[t]): 1235 newcontent.append(None) 1236 else: 1237 newcontent.append(self.content[t] / y) 1238 return Corr(newcontent, prange=self.prange) 1239 elif isinstance(y, np.ndarray): 1240 if y.shape == (self.T,): 1241 return Corr(list((np.array(self.content).T / y).T)) 1242 else: 1243 raise ValueError("operands could not be broadcast together") 1244 else: 1245 raise TypeError('Corr / wrong type') 1246 1247 def __neg__(self): 1248 newcontent = [None if _check_for_none(self, item) else -1. * item for item in self.content] 1249 return Corr(newcontent, prange=self.prange) 1250 1251 def __sub__(self, y): 1252 return self + (-y) 1253 1254 def __pow__(self, y): 1255 if isinstance(y, (Obs, int, float, CObs)): 1256 newcontent = [None if _check_for_none(self, item) else item**y for item in self.content] 1257 return Corr(newcontent, prange=self.prange) 1258 else: 1259 raise TypeError('Type of exponent not supported') 1260 1261 def __abs__(self): 1262 newcontent = [None if _check_for_none(self, item) else np.abs(item) for item in self.content] 1263 return Corr(newcontent, prange=self.prange) 1264 1265 # The numpy functions: 1266 def sqrt(self): 1267 return self ** 0.5 1268 1269 def log(self): 1270 newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content] 1271 return Corr(newcontent, prange=self.prange) 1272 1273 def exp(self): 1274 newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content] 1275 return Corr(newcontent, prange=self.prange) 1276 1277 def _apply_func_to_corr(self, func): 1278 newcontent = [None if _check_for_none(self, item) else func(item) for item in self.content] 1279 for t in range(self.T): 1280 if _check_for_none(self, newcontent[t]): 1281 continue 1282 tmp_sum = np.sum(newcontent[t]) 1283 if hasattr(tmp_sum, "value"): 1284 if np.isnan(tmp_sum.value): 1285 newcontent[t] = None 1286 if all([item is None for item in newcontent]): 1287 raise Exception('Operation returns undefined correlator') 1288 return Corr(newcontent) 1289 1290 def sin(self): 1291 return self._apply_func_to_corr(np.sin) 1292 1293 def cos(self): 1294 return self._apply_func_to_corr(np.cos) 1295 1296 def tan(self): 1297 return self._apply_func_to_corr(np.tan) 1298 1299 def sinh(self): 1300 return self._apply_func_to_corr(np.sinh) 1301 1302 def cosh(self): 1303 return self._apply_func_to_corr(np.cosh) 1304 1305 def tanh(self): 1306 return self._apply_func_to_corr(np.tanh) 1307 1308 def arcsin(self): 1309 return self._apply_func_to_corr(np.arcsin) 1310 1311 def arccos(self): 1312 return self._apply_func_to_corr(np.arccos) 1313 1314 def arctan(self): 1315 return self._apply_func_to_corr(np.arctan) 1316 1317 def arcsinh(self): 1318 return self._apply_func_to_corr(np.arcsinh) 1319 1320 def arccosh(self): 1321 return self._apply_func_to_corr(np.arccosh) 1322 1323 def arctanh(self): 1324 return self._apply_func_to_corr(np.arctanh) 1325 1326 # Right hand side operations (require tweak in main module to work) 1327 def __radd__(self, y): 1328 return self + y 1329 1330 def __rsub__(self, y): 1331 return -self + y 1332 1333 def __rmul__(self, y): 1334 return self * y 1335 1336 def __rtruediv__(self, y): 1337 return (self / y) ** (-1) 1338 1339 @property 1340 def real(self): 1341 def return_real(obs_OR_cobs): 1342 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1343 return np.vectorize(lambda x: x.real)(obs_OR_cobs) 1344 else: 1345 return obs_OR_cobs 1346 1347 return self._apply_func_to_corr(return_real) 1348 1349 @property 1350 def imag(self): 1351 def return_imag(obs_OR_cobs): 1352 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1353 return np.vectorize(lambda x: x.imag)(obs_OR_cobs) 1354 else: 1355 return obs_OR_cobs * 0 # So it stays the right type 1356 1357 return self._apply_func_to_corr(return_imag) 1358 1359 def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None): 1360 r''' Project large correlation matrix to lowest states 1361 1362 This method can be used to reduce the size of an (N x N) correlation matrix 1363 to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise 1364 is still small. 1365 1366 Parameters 1367 ---------- 1368 Ntrunc: int 1369 Rank of the target matrix. 1370 tproj: int 1371 Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method. 1372 The default value is 3. 1373 t0proj: int 1374 Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly 1375 discouraged for O(a) improved theories, since the correctness of the procedure 1376 cannot be granted in this case. The default value is 2. 1377 basematrix : Corr 1378 Correlation matrix that is used to determine the eigenvectors of the 1379 lowest states based on a GEVP. basematrix is taken to be the Corr itself if 1380 is is not specified. 1381 1382 Notes 1383 ----- 1384 We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving 1385 the GEVP $$C(t) v_n(t, t_0) = \lambda_n(t, t_0) C(t_0) v_n(t, t_0)$$ where $t \equiv t_\mathrm{proj}$ 1386 and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the 1387 resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via 1388 $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large 1389 correlation matrix and to remove some noise that is added by irrelevant operators. 1390 This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated 1391 bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$. 1392 ''' 1393 1394 if self.N == 1: 1395 raise Exception('Method cannot be applied to one-dimensional correlators.') 1396 if basematrix is None: 1397 basematrix = self 1398 if Ntrunc >= basematrix.N: 1399 raise Exception('Cannot truncate using Ntrunc <= %d' % (basematrix.N)) 1400 if basematrix.N != self.N: 1401 raise Exception('basematrix and targetmatrix have to be of the same size.') 1402 1403 evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc] 1404 1405 tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object) 1406 rmat = [] 1407 for t in range(basematrix.T): 1408 for i in range(Ntrunc): 1409 for j in range(Ntrunc): 1410 tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j] 1411 rmat.append(np.copy(tmpmat)) 1412 1413 newcontent = [None if (self.content[t] is None) else rmat[t] for t in range(self.T)] 1414 return Corr(newcontent) 1415 1416 1417def _sort_vectors(vec_set_in, ts): 1418 """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 1424 reference_sorting = np.array(vec_set[ts]) 1425 N = reference_sorting.shape[0] 1426 sorted_vec_set = [] 1427 for t in range(len(vec_set)): 1428 if vec_set[t] is None: 1429 sorted_vec_set.append(None) 1430 elif not t == ts: 1431 perms = [list(o) for o in permutations([i for i in range(N)], N)] 1432 best_score = 0 1433 for perm in perms: 1434 current_score = 1 1435 for k in range(N): 1436 new_sorting = reference_sorting.copy() 1437 new_sorting[perm[k], :] = vec_set[t][k] 1438 current_score *= abs(np.linalg.det(new_sorting)) 1439 if current_score > best_score: 1440 best_score = current_score 1441 best_perm = perm 1442 sorted_vec_set.append([vec_set_in[t][k] for k in best_perm]) 1443 else: 1444 sorted_vec_set.append(vec_set_in[t]) 1445 1446 return sorted_vec_set 1447 1448 1449def _check_for_none(corr, entry): 1450 """Checks if entry for correlator corr is None""" 1451 return len(list(filter(None, np.asarray(entry).flatten()))) < corr.N ** 2 1452 1453 1454def _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 1458 1459 The helper function assumes that both provided matrices are symmetric and 1460 only processes the lower triangular part of both matrices. In case the matrices 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]
15class Corr: 16 r"""The class for a correlator (time dependent sequence of pe.Obs). 17 18 Everything, this class does, can be achieved using lists or arrays of Obs. 19 But it is simply more convenient to have a dedicated object for correlators. 20 One often wants to add or multiply correlators of the same length at every timeslice and it is inconvenient 21 to iterate over all timeslices for every operation. This is especially true, when dealing with matrices. 22 23 The correlator can have two types of content: An Obs at every timeslice OR a matrix at every timeslice. 24 Other dependency (eg. spatial) are not supported. 25 26 The Corr class can also deal with missing measurements or paddings for fixed boundary conditions. 27 The missing entries are represented via the `None` object. 28 29 Initialization 30 -------------- 31 A simple correlator can be initialized with a list or a one-dimensional array of `Obs` or `Cobs` 32 ```python 33 corr11 = pe.Corr([obs1, obs2]) 34 corr11 = pe.Corr(np.array([obs1, obs2])) 35 ``` 36 A matrix-valued correlator can either be initialized via a two-dimensional array of `Corr` objects 37 ```python 38 matrix_corr = pe.Corr(np.array([[corr11, corr12], [corr21, corr22]])) 39 ``` 40 or alternatively via a three-dimensional array of `Obs` or `CObs` of shape (T, N, N) where T is 41 the temporal extent of the correlator and N is the dimension of the matrix. 42 """ 43 44 __slots__ = ["content", "N", "T", "tag", "prange"] 45 46 def __init__(self, data_input, padding=[0, 0], prange=None): 47 """ Initialize a Corr object. 48 49 Parameters 50 ---------- 51 data_input : list or array 52 list of Obs or list of arrays of Obs or array of Corrs (see class docstring for details). 53 padding : list, optional 54 List with two entries where the first labels the padding 55 at the front of the correlator and the second the padding 56 at the back. 57 prange : list, optional 58 List containing the first and last timeslice of the plateau 59 region identified for this correlator. 60 """ 61 62 if isinstance(data_input, np.ndarray): 63 if data_input.ndim == 1: 64 data_input = list(data_input) 65 elif data_input.ndim == 2: 66 if not data_input.shape[0] == data_input.shape[1]: 67 raise ValueError("Array needs to be square.") 68 if not all([isinstance(item, Corr) for item in data_input.flatten()]): 69 raise ValueError("If the input is an array, its elements must be of type pe.Corr.") 70 if not all([item.N == 1 for item in data_input.flatten()]): 71 raise ValueError("Can only construct matrix correlator from single valued correlators.") 72 if not len(set([item.T for item in data_input.flatten()])) == 1: 73 raise ValueError("All input Correlators must be defined over the same timeslices.") 74 75 T = data_input[0, 0].T 76 N = data_input.shape[0] 77 input_as_list = [] 78 for t in range(T): 79 if any([(item.content[t] is None) for item in data_input.flatten()]): 80 if not all([(item.content[t] is None) for item in data_input.flatten()]): 81 warnings.warn("Input ill-defined at different timeslices. Conversion leads to data loss.!", RuntimeWarning) 82 input_as_list.append(None) 83 else: 84 array_at_timeslace = np.empty([N, N], dtype="object") 85 for i in range(N): 86 for j in range(N): 87 array_at_timeslace[i, j] = data_input[i, j][t] 88 input_as_list.append(array_at_timeslace) 89 data_input = input_as_list 90 elif data_input.ndim == 3: 91 if not data_input.shape[1] == data_input.shape[2]: 92 raise ValueError("Array needs to be square.") 93 data_input = list(data_input) 94 else: 95 raise ValueError("Arrays with ndim>3 not supported.") 96 97 if isinstance(data_input, list): 98 99 if all([isinstance(item, (Obs, CObs)) or item is None for item in data_input]): 100 _assert_equal_properties([o for o in data_input if o is not None]) 101 self.content = [np.asarray([item]) if item is not None else None for item in data_input] 102 self.N = 1 103 elif all([isinstance(item, np.ndarray) or item is None for item in data_input]) and any([isinstance(item, np.ndarray) for item in data_input]): 104 self.content = data_input 105 noNull = [a for a in self.content if not (a is None)] # To check if the matrices are correct for all undefined elements 106 self.N = noNull[0].shape[0] 107 if self.N > 1 and noNull[0].shape[0] != noNull[0].shape[1]: 108 raise ValueError("Smearing matrices are not NxN.") 109 if (not all([item.shape == noNull[0].shape for item in noNull])): 110 raise ValueError("Items in data_input are not of identical shape." + str(noNull)) 111 else: 112 raise TypeError("'data_input' contains item of wrong type.") 113 else: 114 raise TypeError("Data input was not given as list or correct array.") 115 116 self.tag = None 117 118 # An undefined timeslice is represented by the None object 119 self.content = [None] * padding[0] + self.content + [None] * padding[1] 120 self.T = len(self.content) 121 self.prange = prange 122 123 def __getitem__(self, idx): 124 """Return the content of timeslice idx""" 125 if self.content[idx] is None: 126 return None 127 elif len(self.content[idx]) == 1: 128 return self.content[idx][0] 129 else: 130 return self.content[idx] 131 132 @property 133 def reweighted(self): 134 bool_array = np.array([list(map(lambda x: x.reweighted, o)) for o in [x for x in self.content if x is not None]]) 135 if np.all(bool_array == 1): 136 return True 137 elif np.all(bool_array == 0): 138 return False 139 else: 140 raise Exception("Reweighting status of correlator corrupted.") 141 142 def gamma_method(self, **kwargs): 143 """Apply the gamma method to the content of the Corr.""" 144 for item in self.content: 145 if not (item is None): 146 if self.N == 1: 147 item[0].gamma_method(**kwargs) 148 else: 149 for i in range(self.N): 150 for j in range(self.N): 151 item[i, j].gamma_method(**kwargs) 152 153 gm = gamma_method 154 155 def projected(self, vector_l=None, vector_r=None, normalize=False): 156 """We need to project the Correlator with a Vector to get a single value at each timeslice. 157 158 The method can use one or two vectors. 159 If two are specified it returns v1@G@v2 (the order might be very important.) 160 By default it will return the lowest source, which usually means unsmeared-unsmeared (0,0), but it does not have to 161 """ 162 if self.N == 1: 163 raise Exception("Trying to project a Corr, that already has N=1.") 164 165 if vector_l is None: 166 vector_l, vector_r = np.asarray([1.] + (self.N - 1) * [0.]), np.asarray([1.] + (self.N - 1) * [0.]) 167 elif (vector_r is None): 168 vector_r = vector_l 169 if isinstance(vector_l, list) and not isinstance(vector_r, list): 170 if len(vector_l) != self.T: 171 raise Exception("Length of vector list must be equal to T") 172 vector_r = [vector_r] * self.T 173 if isinstance(vector_r, list) and not isinstance(vector_l, list): 174 if len(vector_r) != self.T: 175 raise Exception("Length of vector list must be equal to T") 176 vector_l = [vector_l] * self.T 177 178 if not isinstance(vector_l, list): 179 if not vector_l.shape == vector_r.shape == (self.N,): 180 raise Exception("Vectors are of wrong shape!") 181 if normalize: 182 vector_l, vector_r = vector_l / np.sqrt((vector_l @ vector_l)), vector_r / np.sqrt(vector_r @ vector_r) 183 newcontent = [None if _check_for_none(self, item) else np.asarray([vector_l.T @ item @ vector_r]) for item in self.content] 184 185 else: 186 # There are no checks here yet. There are so many possible scenarios, where this can go wrong. 187 if normalize: 188 for t in range(self.T): 189 vector_l[t], vector_r[t] = vector_l[t] / np.sqrt((vector_l[t] @ vector_l[t])), vector_r[t] / np.sqrt(vector_r[t] @ vector_r[t]) 190 191 newcontent = [None if (_check_for_none(self, self.content[t]) or vector_l[t] is None or vector_r[t] is None) else np.asarray([vector_l[t].T @ self.content[t] @ vector_r[t]]) for t in range(self.T)] 192 return Corr(newcontent) 193 194 def item(self, i, j): 195 """Picks the element [i,j] from every matrix and returns a correlator containing one Obs per timeslice. 196 197 Parameters 198 ---------- 199 i : int 200 First index to be picked. 201 j : int 202 Second index to be picked. 203 """ 204 if self.N == 1: 205 raise Exception("Trying to pick item from projected Corr") 206 newcontent = [None if (item is None) else item[i, j] for item in self.content] 207 return Corr(newcontent) 208 209 def plottable(self): 210 """Outputs the correlator in a plotable format. 211 212 Outputs three lists containing the timeslice index, the value on each 213 timeslice and the error on each timeslice. 214 """ 215 if self.N != 1: 216 raise Exception("Can only make Corr[N=1] plottable") 217 x_list = [x for x in range(self.T) if not self.content[x] is None] 218 y_list = [y[0].value for y in self.content if y is not None] 219 y_err_list = [y[0].dvalue for y in self.content if y is not None] 220 221 return x_list, y_list, y_err_list 222 223 def symmetric(self): 224 """ Symmetrize the correlator around x0=0.""" 225 if self.N != 1: 226 raise Exception('symmetric cannot be safely applied to multi-dimensional correlators.') 227 if self.T % 2 != 0: 228 raise Exception("Can not symmetrize odd T") 229 230 if self.content[0] is not None: 231 if np.argmax(np.abs([o[0].value if o is not None else 0 for o in self.content])) != 0: 232 warnings.warn("Correlator does not seem to be symmetric around x0=0.", RuntimeWarning) 233 234 newcontent = [self.content[0]] 235 for t in range(1, self.T): 236 if (self.content[t] is None) or (self.content[self.T - t] is None): 237 newcontent.append(None) 238 else: 239 newcontent.append(0.5 * (self.content[t] + self.content[self.T - t])) 240 if (all([x is None for x in newcontent])): 241 raise Exception("Corr could not be symmetrized: No redundant values") 242 return Corr(newcontent, prange=self.prange) 243 244 def anti_symmetric(self): 245 """Anti-symmetrize the correlator around x0=0.""" 246 if self.N != 1: 247 raise TypeError('anti_symmetric cannot be safely applied to multi-dimensional correlators.') 248 if self.T % 2 != 0: 249 raise Exception("Can not symmetrize odd T") 250 251 test = 1 * self 252 test.gamma_method() 253 if not all([o.is_zero_within_error(3) for o in test.content[0]]): 254 warnings.warn("Correlator does not seem to be anti-symmetric around x0=0.", RuntimeWarning) 255 256 newcontent = [self.content[0]] 257 for t in range(1, self.T): 258 if (self.content[t] is None) or (self.content[self.T - t] is None): 259 newcontent.append(None) 260 else: 261 newcontent.append(0.5 * (self.content[t] - self.content[self.T - t])) 262 if (all([x is None for x in newcontent])): 263 raise Exception("Corr could not be symmetrized: No redundant values") 264 return Corr(newcontent, prange=self.prange) 265 266 def is_matrix_symmetric(self): 267 """Checks whether a correlator matrices is symmetric on every timeslice.""" 268 if self.N == 1: 269 raise TypeError("Only works for correlator matrices.") 270 for t in range(self.T): 271 if self[t] is None: 272 continue 273 for i in range(self.N): 274 for j in range(i + 1, self.N): 275 if self[t][i, j] is self[t][j, i]: 276 continue 277 if hash(self[t][i, j]) != hash(self[t][j, i]): 278 return False 279 return True 280 281 def trace(self): 282 """Calculates the per-timeslice trace of a correlator matrix.""" 283 if self.N == 1: 284 raise ValueError("Only works for correlator matrices.") 285 newcontent = [] 286 for t in range(self.T): 287 if _check_for_none(self, self.content[t]): 288 newcontent.append(None) 289 else: 290 newcontent.append(np.trace(self.content[t])) 291 return Corr(newcontent) 292 293 def matrix_symmetric(self): 294 """Symmetrizes the correlator matrices on every timeslice.""" 295 if self.N == 1: 296 raise Exception("Trying to symmetrize a correlator matrix, that already has N=1.") 297 if self.is_matrix_symmetric(): 298 return 1.0 * self 299 else: 300 transposed = [None if _check_for_none(self, G) else G.T for G in self.content] 301 return 0.5 * (Corr(transposed) + self) 302 303 def GEVP(self, t0, ts=None, sort="Eigenvalue", vector_obs=False, **kwargs): 304 r'''Solve the generalized eigenvalue problem on the correlator matrix and returns the corresponding eigenvectors. 305 306 The eigenvectors are sorted according to the descending eigenvalues, the zeroth eigenvector(s) correspond to the 307 largest eigenvalue(s). The eigenvector(s) for the individual states can be accessed via slicing 308 ```python 309 C.GEVP(t0=2)[0] # Ground state vector(s) 310 C.GEVP(t0=2)[:3] # Vectors for the lowest three states 311 ``` 312 313 Parameters 314 ---------- 315 t0 : int 316 The time t0 for the right hand side of the GEVP according to $G(t)v_i=\lambda_i G(t_0)v_i$ 317 ts : int 318 fixed time $G(t_s)v_i=\lambda_i G(t_0)v_i$ if sort=None. 319 If sort="Eigenvector" it gives a reference point for the sorting method. 320 sort : string 321 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. 322 - "Eigenvalue": The eigenvector is chosen according to which eigenvalue it belongs individually on every timeslice. (default) 323 - "Eigenvector": Use the method described in arXiv:2004.10472 to find the set of v(t) belonging to the state. 324 The reference state is identified by its eigenvalue at $t=t_s$. 325 - None: The GEVP is solved only at ts, no sorting is necessary 326 vector_obs : bool 327 If True, uncertainties are propagated in the eigenvector computation (default False). 328 329 Other Parameters 330 ---------------- 331 state : int 332 Returns only the vector(s) for a specified state. The lowest state is zero. 333 method : str 334 Method used to solve the GEVP. 335 - "eigh": Use scipy.linalg.eigh to solve the GEVP. (default for vector_obs=False) 336 - "cholesky": Use manually implemented solution via the Cholesky decomposition. Automatically chosen if vector_obs==True. 337 ''' 338 339 if self.N == 1: 340 raise Exception("GEVP methods only works on correlator matrices and not single correlators.") 341 if ts is not None: 342 if (ts <= t0): 343 raise Exception("ts has to be larger than t0.") 344 345 if "sorted_list" in kwargs: 346 warnings.warn("Argument 'sorted_list' is deprecated, use 'sort' instead.", DeprecationWarning) 347 sort = kwargs.get("sorted_list") 348 349 if self.is_matrix_symmetric(): 350 symmetric_corr = self 351 else: 352 symmetric_corr = self.matrix_symmetric() 353 354 def _get_mat_at_t(t, vector_obs=vector_obs): 355 if vector_obs: 356 return symmetric_corr[t] 357 else: 358 return np.vectorize(lambda x: x.value)(symmetric_corr[t]) 359 G0 = _get_mat_at_t(t0) 360 361 method = kwargs.get('method', 'eigh') 362 if vector_obs: 363 chol = linalg.cholesky(G0) 364 chol_inv = linalg.inv(chol) 365 method = 'cholesky' 366 else: 367 chol = np.linalg.cholesky(_get_mat_at_t(t0, vector_obs=False)) # Check if matrix G0 is positive-semidefinite. 368 if method == 'cholesky': 369 chol_inv = np.linalg.inv(chol) 370 else: 371 chol_inv = None 372 373 if sort is None: 374 if (ts is None): 375 raise Exception("ts is required if sort=None.") 376 if (self.content[t0] is None) or (self.content[ts] is None): 377 raise Exception("Corr not defined at t0/ts.") 378 Gt = _get_mat_at_t(ts) 379 reordered_vecs = _GEVP_solver(Gt, G0, method=method, chol_inv=chol_inv) 380 if kwargs.get('auto_gamma', False) and vector_obs: 381 [[o.gm() for o in ev if isinstance(o, Obs)] for ev in reordered_vecs] 382 383 elif sort in ["Eigenvalue", "Eigenvector"]: 384 if sort == "Eigenvalue" and ts is not None: 385 warnings.warn("ts has no effect when sorting by eigenvalue is chosen.", RuntimeWarning) 386 all_vecs = [None] * (t0 + 1) 387 for t in range(t0 + 1, self.T): 388 try: 389 Gt = _get_mat_at_t(t) 390 all_vecs.append(_GEVP_solver(Gt, G0, method=method, chol_inv=chol_inv)) 391 except Exception: 392 all_vecs.append(None) 393 if sort == "Eigenvector": 394 if ts is None: 395 raise Exception("ts is required for the Eigenvector sorting method.") 396 all_vecs = _sort_vectors(all_vecs, ts) 397 398 reordered_vecs = [[v[s] if v is not None else None for v in all_vecs] for s in range(self.N)] 399 if kwargs.get('auto_gamma', False) and vector_obs: 400 [[[o.gm() for o in evn] for evn in ev if evn is not None] for ev in reordered_vecs] 401 else: 402 raise Exception("Unknown value for 'sort'. Choose 'Eigenvalue', 'Eigenvector' or None.") 403 404 if "state" in kwargs: 405 return reordered_vecs[kwargs.get("state")] 406 else: 407 return reordered_vecs 408 409 def Eigenvalue(self, t0, ts=None, state=0, sort="Eigenvalue", **kwargs): 410 """Determines the eigenvalue of the GEVP by solving and projecting the correlator 411 412 Parameters 413 ---------- 414 state : int 415 The state one is interested in ordered by energy. The lowest state is zero. 416 417 All other parameters are identical to the ones of Corr.GEVP. 418 """ 419 vec = self.GEVP(t0, ts=ts, sort=sort, **kwargs)[state] 420 return self.projected(vec) 421 422 def Hankel(self, N, periodic=False): 423 """Constructs an NxN Hankel matrix 424 425 C(t) c(t+1) ... c(t+n-1) 426 C(t+1) c(t+2) ... c(t+n) 427 ................. 428 C(t+(n-1)) c(t+n) ... c(t+2(n-1)) 429 430 Parameters 431 ---------- 432 N : int 433 Dimension of the Hankel matrix 434 periodic : bool, optional 435 determines whether the matrix is extended periodically 436 """ 437 438 if self.N != 1: 439 raise Exception("Multi-operator Prony not implemented!") 440 441 array = np.empty([N, N], dtype="object") 442 new_content = [] 443 for t in range(self.T): 444 new_content.append(array.copy()) 445 446 def wrap(i): 447 while i >= self.T: 448 i -= self.T 449 return i 450 451 for t in range(self.T): 452 for i in range(N): 453 for j in range(N): 454 if periodic: 455 new_content[t][i, j] = self.content[wrap(t + i + j)][0] 456 elif (t + i + j) >= self.T: 457 new_content[t] = None 458 else: 459 new_content[t][i, j] = self.content[t + i + j][0] 460 461 return Corr(new_content) 462 463 def roll(self, dt): 464 """Periodically shift the correlator by dt timeslices 465 466 Parameters 467 ---------- 468 dt : int 469 number of timeslices 470 """ 471 return Corr(list(np.roll(np.array(self.content, dtype=object), dt, axis=0))) 472 473 def reverse(self): 474 """Reverse the time ordering of the Corr""" 475 return Corr(self.content[:: -1]) 476 477 def thin(self, spacing=2, offset=0): 478 """Thin out a correlator to suppress correlations 479 480 Parameters 481 ---------- 482 spacing : int 483 Keep only every 'spacing'th entry of the correlator 484 offset : int 485 Offset the equal spacing 486 """ 487 new_content = [] 488 for t in range(self.T): 489 if (offset + t) % spacing != 0: 490 new_content.append(None) 491 else: 492 new_content.append(self.content[t]) 493 return Corr(new_content) 494 495 def correlate(self, partner): 496 """Correlate the correlator with another correlator or Obs 497 498 Parameters 499 ---------- 500 partner : Obs or Corr 501 partner to correlate the correlator with. 502 Can either be an Obs which is correlated with all entries of the 503 correlator or a Corr of same length. 504 """ 505 if self.N != 1: 506 raise Exception("Only one-dimensional correlators can be safely correlated.") 507 new_content = [] 508 for x0, t_slice in enumerate(self.content): 509 if _check_for_none(self, t_slice): 510 new_content.append(None) 511 else: 512 if isinstance(partner, Corr): 513 if _check_for_none(partner, partner.content[x0]): 514 new_content.append(None) 515 else: 516 new_content.append(np.array([correlate(o, partner.content[x0][0]) for o in t_slice])) 517 elif isinstance(partner, Obs): # Should this include CObs? 518 new_content.append(np.array([correlate(o, partner) for o in t_slice])) 519 else: 520 raise Exception("Can only correlate with an Obs or a Corr.") 521 522 return Corr(new_content) 523 524 def reweight(self, weight, **kwargs): 525 """Reweight the correlator. 526 527 Parameters 528 ---------- 529 weight : Obs 530 Reweighting factor. An Observable that has to be defined on a superset of the 531 configurations in obs[i].idl for all i. 532 all_configs : bool 533 if True, the reweighted observables are normalized by the average of 534 the reweighting factor on all configurations in weight.idl and not 535 on the configurations in obs[i].idl. 536 """ 537 if self.N != 1: 538 raise Exception("Reweighting only implemented for one-dimensional correlators.") 539 new_content = [] 540 for t_slice in self.content: 541 if _check_for_none(self, t_slice): 542 new_content.append(None) 543 else: 544 new_content.append(np.array(reweight(weight, t_slice, **kwargs))) 545 return Corr(new_content) 546 547 def T_symmetry(self, partner, parity=+1): 548 """Return the time symmetry average of the correlator and its partner 549 550 Parameters 551 ---------- 552 partner : Corr 553 Time symmetry partner of the Corr 554 parity : int 555 Parity quantum number of the correlator, can be +1 or -1 556 """ 557 if self.N != 1: 558 raise Exception("T_symmetry only implemented for one-dimensional correlators.") 559 if not isinstance(partner, Corr): 560 raise Exception("T partner has to be a Corr object.") 561 if parity not in [+1, -1]: 562 raise Exception("Parity has to be +1 or -1.") 563 T_partner = parity * partner.reverse() 564 565 t_slices = [] 566 test = (self - T_partner) 567 test.gamma_method() 568 for x0, t_slice in enumerate(test.content): 569 if t_slice is not None: 570 if not t_slice[0].is_zero_within_error(5): 571 t_slices.append(x0) 572 if t_slices: 573 warnings.warn("T symmetry partners do not agree within 5 sigma on time slices " + str(t_slices) + ".", RuntimeWarning) 574 575 return (self + T_partner) / 2 576 577 def deriv(self, variant="symmetric"): 578 """Return the first derivative of the correlator with respect to x0. 579 580 Parameters 581 ---------- 582 variant : str 583 decides which definition of the finite differences derivative is used. 584 Available choice: symmetric, forward, backward, improved, log, default: symmetric 585 """ 586 if self.N != 1: 587 raise Exception("deriv only implemented for one-dimensional correlators.") 588 if variant == "symmetric": 589 newcontent = [] 590 for t in range(1, self.T - 1): 591 if (self.content[t - 1] is None) or (self.content[t + 1] is None): 592 newcontent.append(None) 593 else: 594 newcontent.append(0.5 * (self.content[t + 1] - self.content[t - 1])) 595 if (all([x is None for x in newcontent])): 596 raise Exception('Derivative is undefined at all timeslices') 597 return Corr(newcontent, padding=[1, 1]) 598 elif variant == "forward": 599 newcontent = [] 600 for t in range(self.T - 1): 601 if (self.content[t] is None) or (self.content[t + 1] is None): 602 newcontent.append(None) 603 else: 604 newcontent.append(self.content[t + 1] - self.content[t]) 605 if (all([x is None for x in newcontent])): 606 raise Exception("Derivative is undefined at all timeslices") 607 return Corr(newcontent, padding=[0, 1]) 608 elif variant == "backward": 609 newcontent = [] 610 for t in range(1, self.T): 611 if (self.content[t - 1] is None) or (self.content[t] is None): 612 newcontent.append(None) 613 else: 614 newcontent.append(self.content[t] - self.content[t - 1]) 615 if (all([x is None for x in newcontent])): 616 raise Exception("Derivative is undefined at all timeslices") 617 return Corr(newcontent, padding=[1, 0]) 618 elif variant == "improved": 619 newcontent = [] 620 for t in range(2, self.T - 2): 621 if (self.content[t - 2] is None) or (self.content[t - 1] is None) or (self.content[t + 1] is None) or (self.content[t + 2] is None): 622 newcontent.append(None) 623 else: 624 newcontent.append((1 / 12) * (self.content[t - 2] - 8 * self.content[t - 1] + 8 * self.content[t + 1] - self.content[t + 2])) 625 if (all([x is None for x in newcontent])): 626 raise Exception('Derivative is undefined at all timeslices') 627 return Corr(newcontent, padding=[2, 2]) 628 elif variant == 'log': 629 newcontent = [] 630 for t in range(self.T): 631 if (self.content[t] is None) or (self.content[t] <= 0): 632 newcontent.append(None) 633 else: 634 newcontent.append(np.log(self.content[t])) 635 if (all([x is None for x in newcontent])): 636 raise Exception("Log is undefined at all timeslices") 637 logcorr = Corr(newcontent) 638 return self * logcorr.deriv('symmetric') 639 else: 640 raise Exception("Unknown variant.") 641 642 def second_deriv(self, variant="symmetric"): 643 r"""Return the second derivative of the correlator with respect to x0. 644 645 Parameters 646 ---------- 647 variant : str 648 decides which definition of the finite differences derivative is used. 649 Available choice: 650 - symmetric (default) 651 $$\tilde{\partial}^2_0 f(x_0) = f(x_0+1)-2f(x_0)+f(x_0-1)$$ 652 - big_symmetric 653 $$\partial^2_0 f(x_0) = \frac{f(x_0+2)-2f(x_0)+f(x_0-2)}{4}$$ 654 - improved 655 $$\partial^2_0 f(x_0) = \frac{-f(x_0+2) + 16 * f(x_0+1) - 30 * f(x_0) + 16 * f(x_0-1) - f(x_0-2)}{12}$$ 656 - log 657 $$f(x) = \tilde{\partial}^2_0 log(f(x_0))+(\tilde{\partial}_0 log(f(x_0)))^2$$ 658 """ 659 if self.N != 1: 660 raise Exception("second_deriv only implemented for one-dimensional correlators.") 661 if variant == "symmetric": 662 newcontent = [] 663 for t in range(1, self.T - 1): 664 if (self.content[t - 1] is None) or (self.content[t + 1] is None): 665 newcontent.append(None) 666 else: 667 newcontent.append((self.content[t + 1] - 2 * self.content[t] + self.content[t - 1])) 668 if (all([x is None for x in newcontent])): 669 raise Exception("Derivative is undefined at all timeslices") 670 return Corr(newcontent, padding=[1, 1]) 671 elif variant == "big_symmetric": 672 newcontent = [] 673 for t in range(2, self.T - 2): 674 if (self.content[t - 2] is None) or (self.content[t + 2] is None): 675 newcontent.append(None) 676 else: 677 newcontent.append((self.content[t + 2] - 2 * self.content[t] + self.content[t - 2]) / 4) 678 if (all([x is None for x in newcontent])): 679 raise Exception("Derivative is undefined at all timeslices") 680 return Corr(newcontent, padding=[2, 2]) 681 elif variant == "improved": 682 newcontent = [] 683 for t in range(2, self.T - 2): 684 if (self.content[t - 2] is None) or (self.content[t - 1] is None) or (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 2] is None): 685 newcontent.append(None) 686 else: 687 newcontent.append((1 / 12) * (-self.content[t + 2] + 16 * self.content[t + 1] - 30 * self.content[t] + 16 * self.content[t - 1] - self.content[t - 2])) 688 if (all([x is None for x in newcontent])): 689 raise Exception("Derivative is undefined at all timeslices") 690 return Corr(newcontent, padding=[2, 2]) 691 elif variant == 'log': 692 newcontent = [] 693 for t in range(self.T): 694 if (self.content[t] is None) or (self.content[t] <= 0): 695 newcontent.append(None) 696 else: 697 newcontent.append(np.log(self.content[t])) 698 if (all([x is None for x in newcontent])): 699 raise Exception("Log is undefined at all timeslices") 700 logcorr = Corr(newcontent) 701 return self * (logcorr.second_deriv('symmetric') + (logcorr.deriv('symmetric'))**2) 702 else: 703 raise Exception("Unknown variant.") 704 705 def m_eff(self, variant='log', guess=1.0): 706 """Returns the effective mass of the correlator as correlator object 707 708 Parameters 709 ---------- 710 variant : str 711 log : uses the standard effective mass log(C(t) / C(t+1)) 712 cosh, periodic : Use periodicity of the correlator by solving C(t) / C(t+1) = cosh(m * (t - T/2)) / cosh(m * (t + 1 - T/2)) for m. 713 sinh : Use anti-periodicity of the correlator by solving C(t) / C(t+1) = sinh(m * (t - T/2)) / sinh(m * (t + 1 - T/2)) for m. 714 See, e.g., arXiv:1205.5380 715 arccosh : Uses the explicit form of the symmetrized correlator (not recommended) 716 logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2 717 guess : float 718 guess for the root finder, only relevant for the root variant 719 """ 720 if self.N != 1: 721 raise Exception('Correlator must be projected before getting m_eff') 722 if variant == 'log': 723 newcontent = [] 724 for t in range(self.T - 1): 725 if ((self.content[t] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 726 newcontent.append(None) 727 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 728 newcontent.append(None) 729 else: 730 newcontent.append(self.content[t] / self.content[t + 1]) 731 if (all([x is None for x in newcontent])): 732 raise Exception('m_eff is undefined at all timeslices') 733 734 return np.log(Corr(newcontent, padding=[0, 1])) 735 736 elif variant == 'logsym': 737 newcontent = [] 738 for t in range(1, self.T - 1): 739 if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 740 newcontent.append(None) 741 elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0: 742 newcontent.append(None) 743 else: 744 newcontent.append(self.content[t - 1] / self.content[t + 1]) 745 if (all([x is None for x in newcontent])): 746 raise Exception('m_eff is undefined at all timeslices') 747 748 return np.log(Corr(newcontent, padding=[1, 1])) / 2 749 750 elif variant in ['periodic', 'cosh', 'sinh']: 751 if variant in ['periodic', 'cosh']: 752 func = anp.cosh 753 else: 754 func = anp.sinh 755 756 def root_function(x, d): 757 return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d 758 759 newcontent = [] 760 for t in range(self.T - 1): 761 if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0): 762 newcontent.append(None) 763 # Fill the two timeslices in the middle of the lattice with their predecessors 764 elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]: 765 newcontent.append(newcontent[-1]) 766 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 767 newcontent.append(None) 768 else: 769 newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess))) 770 if (all([x is None for x in newcontent])): 771 raise Exception('m_eff is undefined at all timeslices') 772 773 return Corr(newcontent, padding=[0, 1]) 774 775 elif variant == 'arccosh': 776 newcontent = [] 777 for t in range(1, self.T - 1): 778 if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t - 1] is None) or (self.content[t][0].value == 0): 779 newcontent.append(None) 780 else: 781 newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t])) 782 if (all([x is None for x in newcontent])): 783 raise Exception("m_eff is undefined at all timeslices") 784 return np.arccosh(Corr(newcontent, padding=[1, 1])) 785 786 else: 787 raise Exception('Unknown variant.') 788 789 def fit(self, function, fitrange=None, silent=False, **kwargs): 790 r'''Fits function to the data 791 792 Parameters 793 ---------- 794 function : obj 795 function to fit to the data. See fits.least_squares for details. 796 fitrange : list 797 Two element list containing the timeslices on which the fit is supposed to start and stop. 798 Caution: This range is inclusive as opposed to standard python indexing. 799 `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6. 800 If not specified, self.prange or all timeslices are used. 801 silent : bool 802 Decides whether output is printed to the standard output. 803 ''' 804 if self.N != 1: 805 raise Exception("Correlator must be projected before fitting") 806 807 if fitrange is None: 808 if self.prange: 809 fitrange = self.prange 810 else: 811 fitrange = [0, self.T - 1] 812 else: 813 if not isinstance(fitrange, list): 814 raise Exception("fitrange has to be a list with two elements") 815 if len(fitrange) != 2: 816 raise Exception("fitrange has to have exactly two elements [fit_start, fit_stop]") 817 818 xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]) 819 ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]) 820 result = least_squares(xs, ys, function, silent=silent, **kwargs) 821 return result 822 823 def plateau(self, plateau_range=None, method="fit", auto_gamma=False): 824 """ Extract a plateau value from a Corr object 825 826 Parameters 827 ---------- 828 plateau_range : list 829 list with two entries, indicating the first and the last timeslice 830 of the plateau region. 831 method : str 832 method to extract the plateau. 833 'fit' fits a constant to the plateau region 834 'avg', 'average' or 'mean' just average over the given timeslices. 835 auto_gamma : bool 836 apply gamma_method with default parameters to the Corr. Defaults to None 837 """ 838 if not plateau_range: 839 if self.prange: 840 plateau_range = self.prange 841 else: 842 raise Exception("no plateau range provided") 843 if self.N != 1: 844 raise Exception("Correlator must be projected before getting a plateau.") 845 if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])): 846 raise Exception("plateau is undefined at all timeslices in plateaurange.") 847 if auto_gamma: 848 self.gamma_method() 849 if method == "fit": 850 def const_func(a, t): 851 return a[0] 852 return self.fit(const_func, plateau_range)[0] 853 elif method in ["avg", "average", "mean"]: 854 returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None]) 855 return returnvalue 856 857 else: 858 raise Exception("Unsupported plateau method: " + method) 859 860 def set_prange(self, prange): 861 """Sets the attribute prange of the Corr object.""" 862 if not len(prange) == 2: 863 raise Exception("prange must be a list or array with two values") 864 if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))): 865 raise Exception("Start and end point must be integers") 866 if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] < prange[1]): 867 raise Exception("Start and end point must define a range in the interval 0,T") 868 869 self.prange = prange 870 return 871 872 def show(self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, fit_key=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None): 873 """Plots the correlator using the tag of the correlator as label if available. 874 875 Parameters 876 ---------- 877 x_range : list 878 list of two values, determining the range of the x-axis e.g. [4, 8]. 879 comp : Corr or list of Corr 880 Correlator or list of correlators which are plotted for comparison. 881 The tags of these correlators are used as labels if available. 882 logscale : bool 883 Sets y-axis to logscale. 884 plateau : Obs 885 Plateau value to be visualized in the figure. 886 fit_res : Fit_result 887 Fit_result object to be visualized. 888 fit_key : str 889 Key for the fit function in Fit_result.fit_function (for combined fits). 890 ylabel : str 891 Label for the y-axis. 892 save : str 893 path to file in which the figure should be saved. 894 auto_gamma : bool 895 Apply the gamma method with standard parameters to all correlators and plateau values before plotting. 896 hide_sigma : float 897 Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors. 898 references : list 899 List of floating point values that are displayed as horizontal lines for reference. 900 title : string 901 Optional title of the figure. 902 """ 903 if self.N != 1: 904 raise Exception("Correlator must be projected before plotting") 905 906 if auto_gamma: 907 self.gamma_method() 908 909 if x_range is None: 910 x_range = [0, self.T - 1] 911 912 fig = plt.figure() 913 ax1 = fig.add_subplot(111) 914 915 x, y, y_err = self.plottable() 916 if hide_sigma: 917 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 918 else: 919 hide_from = None 920 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag) 921 if logscale: 922 ax1.set_yscale('log') 923 else: 924 if y_range is None: 925 try: 926 y_min = min([(x[0].value - x[0].dvalue) for x in self.content[x_range[0]: x_range[1] + 1] if (x is not None) and x[0].dvalue < 2 * np.abs(x[0].value)]) 927 y_max = max([(x[0].value + x[0].dvalue) for x in self.content[x_range[0]: x_range[1] + 1] if (x is not None) and x[0].dvalue < 2 * np.abs(x[0].value)]) 928 ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)]) 929 except Exception: 930 pass 931 else: 932 ax1.set_ylim(y_range) 933 if comp: 934 if isinstance(comp, (Corr, list)): 935 for corr in comp if isinstance(comp, list) else [comp]: 936 if auto_gamma: 937 corr.gamma_method() 938 x, y, y_err = corr.plottable() 939 if hide_sigma: 940 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 941 else: 942 hide_from = None 943 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor']) 944 else: 945 raise Exception("'comp' must be a correlator or a list of correlators.") 946 947 if plateau: 948 if isinstance(plateau, Obs): 949 if auto_gamma: 950 plateau.gamma_method() 951 ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau)) 952 ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-') 953 else: 954 raise Exception("'plateau' must be an Obs") 955 956 if references: 957 if isinstance(references, list): 958 for ref in references: 959 ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--') 960 else: 961 raise Exception("'references' must be a list of floating pint values.") 962 963 if self.prange: 964 ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',', color="black", zorder=0) 965 ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',', color="black", zorder=0) 966 967 if fit_res: 968 x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05) 969 if isinstance(fit_res.fit_function, dict): 970 if fit_key: 971 ax1.plot(x_samples, fit_res.fit_function[fit_key]([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2) 972 else: 973 raise ValueError("Please provide a 'fit_key' for visualizing combined fits.") 974 else: 975 ax1.plot(x_samples, fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2) 976 977 ax1.set_xlabel(r'$x_0 / a$') 978 if ylabel: 979 ax1.set_ylabel(ylabel) 980 ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5]) 981 982 handles, labels = ax1.get_legend_handles_labels() 983 if labels: 984 ax1.legend() 985 986 if title: 987 plt.title(title) 988 989 plt.draw() 990 991 if save: 992 if isinstance(save, str): 993 fig.savefig(save, bbox_inches='tight') 994 else: 995 raise Exception("'save' has to be a string.") 996 997 def spaghetti_plot(self, logscale=True): 998 """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations. 999 1000 Parameters 1001 ---------- 1002 logscale : bool 1003 Determines whether the scale of the y-axis is logarithmic or standard. 1004 """ 1005 if self.N != 1: 1006 raise Exception("Correlator needs to be projected first.") 1007 1008 mc_names = list(set([item for sublist in [sum(map(o[0].e_content.get, o[0].mc_names), []) for o in self.content if o is not None] for item in sublist])) 1009 x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content) if o is not None] 1010 1011 for name in mc_names: 1012 data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T 1013 1014 fig = plt.figure() 1015 ax = fig.add_subplot(111) 1016 for dat in data: 1017 ax.plot(x0_vals, dat, ls='-', marker='') 1018 1019 if logscale is True: 1020 ax.set_yscale('log') 1021 1022 ax.set_xlabel(r'$x_0 / a$') 1023 plt.title(name) 1024 plt.draw() 1025 1026 def dump(self, filename, datatype="json.gz", **kwargs): 1027 """Dumps the Corr into a file of chosen type 1028 Parameters 1029 ---------- 1030 filename : str 1031 Name of the file to be saved. 1032 datatype : str 1033 Format of the exported file. Supported formats include 1034 "json.gz" and "pickle" 1035 path : str 1036 specifies a custom path for the file (default '.') 1037 """ 1038 if datatype == "json.gz": 1039 from .input.json import dump_to_json 1040 if 'path' in kwargs: 1041 file_name = kwargs.get('path') + '/' + filename 1042 else: 1043 file_name = filename 1044 dump_to_json(self, file_name) 1045 elif datatype == "pickle": 1046 dump_object(self, filename, **kwargs) 1047 else: 1048 raise Exception("Unknown datatype " + str(datatype)) 1049 1050 def print(self, print_range=None): 1051 print(self.__repr__(print_range)) 1052 1053 def __repr__(self, print_range=None): 1054 if print_range is None: 1055 print_range = [0, None] 1056 1057 content_string = "" 1058 content_string += "Corr T=" + str(self.T) + " N=" + str(self.N) + "\n" # +" filled with"+ str(type(self.content[0][0])) there should be a good solution here 1059 1060 if self.tag is not None: 1061 content_string += "Description: " + self.tag + "\n" 1062 if self.N != 1: 1063 return content_string 1064 1065 if print_range[1]: 1066 print_range[1] += 1 1067 content_string += 'x0/a\tCorr(x0/a)\n------------------\n' 1068 for i, sub_corr in enumerate(self.content[print_range[0]:print_range[1]]): 1069 if sub_corr is None: 1070 content_string += str(i + print_range[0]) + '\n' 1071 else: 1072 content_string += str(i + print_range[0]) 1073 for element in sub_corr: 1074 content_string += f"\t{element:+2}" 1075 content_string += '\n' 1076 return content_string 1077 1078 def __str__(self): 1079 return self.__repr__() 1080 1081 # We define the basic operations, that can be performed with correlators. 1082 # While */+- get defined here, they only work for Corr*Obs and not Obs*Corr. 1083 # This is because Obs*Corr checks Obs.__mul__ first and does not catch an exception. 1084 # One could try and tell Obs to check if the y in __mul__ is a Corr and 1085 1086 __array_priority__ = 10000 1087 1088 def __eq__(self, y): 1089 if isinstance(y, Corr): 1090 comp = np.asarray(y.content, dtype=object) 1091 else: 1092 comp = np.asarray(y) 1093 return np.asarray(self.content, dtype=object) == comp 1094 1095 def __add__(self, y): 1096 if isinstance(y, Corr): 1097 if ((self.N != y.N) or (self.T != y.T)): 1098 raise Exception("Addition of Corrs with different shape") 1099 newcontent = [] 1100 for t in range(self.T): 1101 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1102 newcontent.append(None) 1103 else: 1104 newcontent.append(self.content[t] + y.content[t]) 1105 return Corr(newcontent) 1106 1107 elif isinstance(y, (Obs, int, float, CObs, complex)): 1108 newcontent = [] 1109 for t in range(self.T): 1110 if _check_for_none(self, self.content[t]): 1111 newcontent.append(None) 1112 else: 1113 newcontent.append(self.content[t] + y) 1114 return Corr(newcontent, prange=self.prange) 1115 elif isinstance(y, np.ndarray): 1116 if y.shape == (self.T,): 1117 return Corr(list((np.array(self.content).T + y).T)) 1118 else: 1119 raise ValueError("operands could not be broadcast together") 1120 else: 1121 raise TypeError("Corr + wrong type") 1122 1123 def __mul__(self, y): 1124 if isinstance(y, Corr): 1125 if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): 1126 raise Exception("Multiplication of Corr object requires N=N or N=1 and T=T") 1127 newcontent = [] 1128 for t in range(self.T): 1129 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1130 newcontent.append(None) 1131 else: 1132 newcontent.append(self.content[t] * y.content[t]) 1133 return Corr(newcontent) 1134 1135 elif isinstance(y, (Obs, int, float, CObs, complex)): 1136 newcontent = [] 1137 for t in range(self.T): 1138 if _check_for_none(self, self.content[t]): 1139 newcontent.append(None) 1140 else: 1141 newcontent.append(self.content[t] * y) 1142 return Corr(newcontent, prange=self.prange) 1143 elif isinstance(y, np.ndarray): 1144 if y.shape == (self.T,): 1145 return Corr(list((np.array(self.content).T * y).T)) 1146 else: 1147 raise ValueError("operands could not be broadcast together") 1148 else: 1149 raise TypeError("Corr * wrong type") 1150 1151 def __matmul__(self, y): 1152 if isinstance(y, np.ndarray): 1153 if y.ndim != 2 or y.shape[0] != y.shape[1]: 1154 raise ValueError("Can only multiply correlators by square matrices.") 1155 if not self.N == y.shape[0]: 1156 raise ValueError("matmul: mismatch of matrix dimensions") 1157 newcontent = [] 1158 for t in range(self.T): 1159 if _check_for_none(self, self.content[t]): 1160 newcontent.append(None) 1161 else: 1162 newcontent.append(self.content[t] @ y) 1163 return Corr(newcontent) 1164 elif isinstance(y, Corr): 1165 if not self.N == y.N: 1166 raise ValueError("matmul: mismatch of matrix dimensions") 1167 newcontent = [] 1168 for t in range(self.T): 1169 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1170 newcontent.append(None) 1171 else: 1172 newcontent.append(self.content[t] @ y.content[t]) 1173 return Corr(newcontent) 1174 1175 else: 1176 return NotImplemented 1177 1178 def __rmatmul__(self, y): 1179 if isinstance(y, np.ndarray): 1180 if y.ndim != 2 or y.shape[0] != y.shape[1]: 1181 raise ValueError("Can only multiply correlators by square matrices.") 1182 if not self.N == y.shape[0]: 1183 raise ValueError("matmul: mismatch of matrix dimensions") 1184 newcontent = [] 1185 for t in range(self.T): 1186 if _check_for_none(self, self.content[t]): 1187 newcontent.append(None) 1188 else: 1189 newcontent.append(y @ self.content[t]) 1190 return Corr(newcontent) 1191 else: 1192 return NotImplemented 1193 1194 def __truediv__(self, y): 1195 if isinstance(y, Corr): 1196 if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): 1197 raise Exception("Multiplication of Corr object requires N=N or N=1 and T=T") 1198 newcontent = [] 1199 for t in range(self.T): 1200 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1201 newcontent.append(None) 1202 else: 1203 newcontent.append(self.content[t] / y.content[t]) 1204 for t in range(self.T): 1205 if _check_for_none(self, newcontent[t]): 1206 continue 1207 if np.isnan(np.sum(newcontent[t]).value): 1208 newcontent[t] = None 1209 1210 if all([item is None for item in newcontent]): 1211 raise Exception("Division returns completely undefined correlator") 1212 return Corr(newcontent) 1213 1214 elif isinstance(y, (Obs, CObs)): 1215 if isinstance(y, Obs): 1216 if y.value == 0: 1217 raise Exception('Division by zero will return undefined correlator') 1218 if isinstance(y, CObs): 1219 if y.is_zero(): 1220 raise Exception('Division by zero will return undefined correlator') 1221 1222 newcontent = [] 1223 for t in range(self.T): 1224 if _check_for_none(self, self.content[t]): 1225 newcontent.append(None) 1226 else: 1227 newcontent.append(self.content[t] / y) 1228 return Corr(newcontent, prange=self.prange) 1229 1230 elif isinstance(y, (int, float)): 1231 if y == 0: 1232 raise Exception('Division by zero will return undefined correlator') 1233 newcontent = [] 1234 for t in range(self.T): 1235 if _check_for_none(self, self.content[t]): 1236 newcontent.append(None) 1237 else: 1238 newcontent.append(self.content[t] / y) 1239 return Corr(newcontent, prange=self.prange) 1240 elif isinstance(y, np.ndarray): 1241 if y.shape == (self.T,): 1242 return Corr(list((np.array(self.content).T / y).T)) 1243 else: 1244 raise ValueError("operands could not be broadcast together") 1245 else: 1246 raise TypeError('Corr / wrong type') 1247 1248 def __neg__(self): 1249 newcontent = [None if _check_for_none(self, item) else -1. * item for item in self.content] 1250 return Corr(newcontent, prange=self.prange) 1251 1252 def __sub__(self, y): 1253 return self + (-y) 1254 1255 def __pow__(self, y): 1256 if isinstance(y, (Obs, int, float, CObs)): 1257 newcontent = [None if _check_for_none(self, item) else item**y for item in self.content] 1258 return Corr(newcontent, prange=self.prange) 1259 else: 1260 raise TypeError('Type of exponent not supported') 1261 1262 def __abs__(self): 1263 newcontent = [None if _check_for_none(self, item) else np.abs(item) for item in self.content] 1264 return Corr(newcontent, prange=self.prange) 1265 1266 # The numpy functions: 1267 def sqrt(self): 1268 return self ** 0.5 1269 1270 def log(self): 1271 newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content] 1272 return Corr(newcontent, prange=self.prange) 1273 1274 def exp(self): 1275 newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content] 1276 return Corr(newcontent, prange=self.prange) 1277 1278 def _apply_func_to_corr(self, func): 1279 newcontent = [None if _check_for_none(self, item) else func(item) for item in self.content] 1280 for t in range(self.T): 1281 if _check_for_none(self, newcontent[t]): 1282 continue 1283 tmp_sum = np.sum(newcontent[t]) 1284 if hasattr(tmp_sum, "value"): 1285 if np.isnan(tmp_sum.value): 1286 newcontent[t] = None 1287 if all([item is None for item in newcontent]): 1288 raise Exception('Operation returns undefined correlator') 1289 return Corr(newcontent) 1290 1291 def sin(self): 1292 return self._apply_func_to_corr(np.sin) 1293 1294 def cos(self): 1295 return self._apply_func_to_corr(np.cos) 1296 1297 def tan(self): 1298 return self._apply_func_to_corr(np.tan) 1299 1300 def sinh(self): 1301 return self._apply_func_to_corr(np.sinh) 1302 1303 def cosh(self): 1304 return self._apply_func_to_corr(np.cosh) 1305 1306 def tanh(self): 1307 return self._apply_func_to_corr(np.tanh) 1308 1309 def arcsin(self): 1310 return self._apply_func_to_corr(np.arcsin) 1311 1312 def arccos(self): 1313 return self._apply_func_to_corr(np.arccos) 1314 1315 def arctan(self): 1316 return self._apply_func_to_corr(np.arctan) 1317 1318 def arcsinh(self): 1319 return self._apply_func_to_corr(np.arcsinh) 1320 1321 def arccosh(self): 1322 return self._apply_func_to_corr(np.arccosh) 1323 1324 def arctanh(self): 1325 return self._apply_func_to_corr(np.arctanh) 1326 1327 # Right hand side operations (require tweak in main module to work) 1328 def __radd__(self, y): 1329 return self + y 1330 1331 def __rsub__(self, y): 1332 return -self + y 1333 1334 def __rmul__(self, y): 1335 return self * y 1336 1337 def __rtruediv__(self, y): 1338 return (self / y) ** (-1) 1339 1340 @property 1341 def real(self): 1342 def return_real(obs_OR_cobs): 1343 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1344 return np.vectorize(lambda x: x.real)(obs_OR_cobs) 1345 else: 1346 return obs_OR_cobs 1347 1348 return self._apply_func_to_corr(return_real) 1349 1350 @property 1351 def imag(self): 1352 def return_imag(obs_OR_cobs): 1353 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1354 return np.vectorize(lambda x: x.imag)(obs_OR_cobs) 1355 else: 1356 return obs_OR_cobs * 0 # So it stays the right type 1357 1358 return self._apply_func_to_corr(return_imag) 1359 1360 def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None): 1361 r''' Project large correlation matrix to lowest states 1362 1363 This method can be used to reduce the size of an (N x N) correlation matrix 1364 to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise 1365 is still small. 1366 1367 Parameters 1368 ---------- 1369 Ntrunc: int 1370 Rank of the target matrix. 1371 tproj: int 1372 Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method. 1373 The default value is 3. 1374 t0proj: int 1375 Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly 1376 discouraged for O(a) improved theories, since the correctness of the procedure 1377 cannot be granted in this case. The default value is 2. 1378 basematrix : Corr 1379 Correlation matrix that is used to determine the eigenvectors of the 1380 lowest states based on a GEVP. basematrix is taken to be the Corr itself if 1381 is is not specified. 1382 1383 Notes 1384 ----- 1385 We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving 1386 the GEVP $$C(t) v_n(t, t_0) = \lambda_n(t, t_0) C(t_0) v_n(t, t_0)$$ where $t \equiv t_\mathrm{proj}$ 1387 and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the 1388 resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via 1389 $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large 1390 correlation matrix and to remove some noise that is added by irrelevant operators. 1391 This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated 1392 bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$. 1393 ''' 1394 1395 if self.N == 1: 1396 raise Exception('Method cannot be applied to one-dimensional correlators.') 1397 if basematrix is None: 1398 basematrix = self 1399 if Ntrunc >= basematrix.N: 1400 raise Exception('Cannot truncate using Ntrunc <= %d' % (basematrix.N)) 1401 if basematrix.N != self.N: 1402 raise Exception('basematrix and targetmatrix have to be of the same size.') 1403 1404 evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc] 1405 1406 tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object) 1407 rmat = [] 1408 for t in range(basematrix.T): 1409 for i in range(Ntrunc): 1410 for j in range(Ntrunc): 1411 tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j] 1412 rmat.append(np.copy(tmpmat)) 1413 1414 newcontent = [None if (self.content[t] is None) else rmat[t] for t in range(self.T)] 1415 return Corr(newcontent)
The class for a correlator (time dependent sequence of pe.Obs).
Everything, this class does, can be achieved using lists or arrays of Obs. But it is simply more convenient to have a dedicated object for correlators. One often wants to add or multiply correlators of the same length at every timeslice and it is inconvenient to iterate over all timeslices for every operation. This is especially true, when dealing with matrices.
The correlator can have two types of content: An Obs at every timeslice OR a matrix at every timeslice. Other dependency (eg. spatial) are not supported.
The Corr class can also deal with missing measurements or paddings for fixed boundary conditions.
The missing entries are represented via the None
object.
Initialization
A simple correlator can be initialized with a list or a one-dimensional array of Obs
or Cobs
corr11 = pe.Corr([obs1, obs2])
corr11 = pe.Corr(np.array([obs1, obs2]))
A matrix-valued correlator can either be initialized via a two-dimensional array of Corr
objects
matrix_corr = pe.Corr(np.array([[corr11, corr12], [corr21, corr22]]))
or alternatively via a three-dimensional array of Obs
or CObs
of shape (T, N, N) where T is
the temporal extent of the correlator and N is the dimension of the matrix.
46 def __init__(self, data_input, padding=[0, 0], prange=None): 47 """ Initialize a Corr object. 48 49 Parameters 50 ---------- 51 data_input : list or array 52 list of Obs or list of arrays of Obs or array of Corrs (see class docstring for details). 53 padding : list, optional 54 List with two entries where the first labels the padding 55 at the front of the correlator and the second the padding 56 at the back. 57 prange : list, optional 58 List containing the first and last timeslice of the plateau 59 region identified for this correlator. 60 """ 61 62 if isinstance(data_input, np.ndarray): 63 if data_input.ndim == 1: 64 data_input = list(data_input) 65 elif data_input.ndim == 2: 66 if not data_input.shape[0] == data_input.shape[1]: 67 raise ValueError("Array needs to be square.") 68 if not all([isinstance(item, Corr) for item in data_input.flatten()]): 69 raise ValueError("If the input is an array, its elements must be of type pe.Corr.") 70 if not all([item.N == 1 for item in data_input.flatten()]): 71 raise ValueError("Can only construct matrix correlator from single valued correlators.") 72 if not len(set([item.T for item in data_input.flatten()])) == 1: 73 raise ValueError("All input Correlators must be defined over the same timeslices.") 74 75 T = data_input[0, 0].T 76 N = data_input.shape[0] 77 input_as_list = [] 78 for t in range(T): 79 if any([(item.content[t] is None) for item in data_input.flatten()]): 80 if not all([(item.content[t] is None) for item in data_input.flatten()]): 81 warnings.warn("Input ill-defined at different timeslices. Conversion leads to data loss.!", RuntimeWarning) 82 input_as_list.append(None) 83 else: 84 array_at_timeslace = np.empty([N, N], dtype="object") 85 for i in range(N): 86 for j in range(N): 87 array_at_timeslace[i, j] = data_input[i, j][t] 88 input_as_list.append(array_at_timeslace) 89 data_input = input_as_list 90 elif data_input.ndim == 3: 91 if not data_input.shape[1] == data_input.shape[2]: 92 raise ValueError("Array needs to be square.") 93 data_input = list(data_input) 94 else: 95 raise ValueError("Arrays with ndim>3 not supported.") 96 97 if isinstance(data_input, list): 98 99 if all([isinstance(item, (Obs, CObs)) or item is None for item in data_input]): 100 _assert_equal_properties([o for o in data_input if o is not None]) 101 self.content = [np.asarray([item]) if item is not None else None for item in data_input] 102 self.N = 1 103 elif all([isinstance(item, np.ndarray) or item is None for item in data_input]) and any([isinstance(item, np.ndarray) for item in data_input]): 104 self.content = data_input 105 noNull = [a for a in self.content if not (a is None)] # To check if the matrices are correct for all undefined elements 106 self.N = noNull[0].shape[0] 107 if self.N > 1 and noNull[0].shape[0] != noNull[0].shape[1]: 108 raise ValueError("Smearing matrices are not NxN.") 109 if (not all([item.shape == noNull[0].shape for item in noNull])): 110 raise ValueError("Items in data_input are not of identical shape." + str(noNull)) 111 else: 112 raise TypeError("'data_input' contains item of wrong type.") 113 else: 114 raise TypeError("Data input was not given as list or correct array.") 115 116 self.tag = None 117 118 # An undefined timeslice is represented by the None object 119 self.content = [None] * padding[0] + self.content + [None] * padding[1] 120 self.T = len(self.content) 121 self.prange = prange
Initialize a Corr object.
Parameters
- data_input (list or array): list of Obs or list of arrays of Obs or array of Corrs (see class docstring for details).
- padding (list, optional): List with two entries where the first labels the padding at the front of the correlator and the second the padding at the back.
- prange (list, optional): List containing the first and last timeslice of the plateau region identified for this correlator.
132 @property 133 def reweighted(self): 134 bool_array = np.array([list(map(lambda x: x.reweighted, o)) for o in [x for x in self.content if x is not None]]) 135 if np.all(bool_array == 1): 136 return True 137 elif np.all(bool_array == 0): 138 return False 139 else: 140 raise Exception("Reweighting status of correlator corrupted.")
142 def gamma_method(self, **kwargs): 143 """Apply the gamma method to the content of the Corr.""" 144 for item in self.content: 145 if not (item is None): 146 if self.N == 1: 147 item[0].gamma_method(**kwargs) 148 else: 149 for i in range(self.N): 150 for j in range(self.N): 151 item[i, j].gamma_method(**kwargs)
Apply the gamma method to the content of the Corr.
142 def gamma_method(self, **kwargs): 143 """Apply the gamma method to the content of the Corr.""" 144 for item in self.content: 145 if not (item is None): 146 if self.N == 1: 147 item[0].gamma_method(**kwargs) 148 else: 149 for i in range(self.N): 150 for j in range(self.N): 151 item[i, j].gamma_method(**kwargs)
Apply the gamma method to the content of the Corr.
155 def projected(self, vector_l=None, vector_r=None, normalize=False): 156 """We need to project the Correlator with a Vector to get a single value at each timeslice. 157 158 The method can use one or two vectors. 159 If two are specified it returns v1@G@v2 (the order might be very important.) 160 By default it will return the lowest source, which usually means unsmeared-unsmeared (0,0), but it does not have to 161 """ 162 if self.N == 1: 163 raise Exception("Trying to project a Corr, that already has N=1.") 164 165 if vector_l is None: 166 vector_l, vector_r = np.asarray([1.] + (self.N - 1) * [0.]), np.asarray([1.] + (self.N - 1) * [0.]) 167 elif (vector_r is None): 168 vector_r = vector_l 169 if isinstance(vector_l, list) and not isinstance(vector_r, list): 170 if len(vector_l) != self.T: 171 raise Exception("Length of vector list must be equal to T") 172 vector_r = [vector_r] * self.T 173 if isinstance(vector_r, list) and not isinstance(vector_l, list): 174 if len(vector_r) != self.T: 175 raise Exception("Length of vector list must be equal to T") 176 vector_l = [vector_l] * self.T 177 178 if not isinstance(vector_l, list): 179 if not vector_l.shape == vector_r.shape == (self.N,): 180 raise Exception("Vectors are of wrong shape!") 181 if normalize: 182 vector_l, vector_r = vector_l / np.sqrt((vector_l @ vector_l)), vector_r / np.sqrt(vector_r @ vector_r) 183 newcontent = [None if _check_for_none(self, item) else np.asarray([vector_l.T @ item @ vector_r]) for item in self.content] 184 185 else: 186 # There are no checks here yet. There are so many possible scenarios, where this can go wrong. 187 if normalize: 188 for t in range(self.T): 189 vector_l[t], vector_r[t] = vector_l[t] / np.sqrt((vector_l[t] @ vector_l[t])), vector_r[t] / np.sqrt(vector_r[t] @ vector_r[t]) 190 191 newcontent = [None if (_check_for_none(self, self.content[t]) or vector_l[t] is None or vector_r[t] is None) else np.asarray([vector_l[t].T @ self.content[t] @ vector_r[t]]) for t in range(self.T)] 192 return Corr(newcontent)
We need to project the Correlator with a Vector to get a single value at each timeslice.
The method can use one or two vectors. If two are specified it returns v1@G@v2 (the order might be very important.) By default it will return the lowest source, which usually means unsmeared-unsmeared (0,0), but it does not have to
194 def item(self, i, j): 195 """Picks the element [i,j] from every matrix and returns a correlator containing one Obs per timeslice. 196 197 Parameters 198 ---------- 199 i : int 200 First index to be picked. 201 j : int 202 Second index to be picked. 203 """ 204 if self.N == 1: 205 raise Exception("Trying to pick item from projected Corr") 206 newcontent = [None if (item is None) else item[i, j] for item in self.content] 207 return Corr(newcontent)
Picks the element [i,j] from every matrix and returns a correlator containing one Obs per timeslice.
Parameters
- i (int): First index to be picked.
- j (int): Second index to be picked.
209 def plottable(self): 210 """Outputs the correlator in a plotable format. 211 212 Outputs three lists containing the timeslice index, the value on each 213 timeslice and the error on each timeslice. 214 """ 215 if self.N != 1: 216 raise Exception("Can only make Corr[N=1] plottable") 217 x_list = [x for x in range(self.T) if not self.content[x] is None] 218 y_list = [y[0].value for y in self.content if y is not None] 219 y_err_list = [y[0].dvalue for y in self.content if y is not None] 220 221 return x_list, y_list, y_err_list
Outputs the correlator in a plotable format.
Outputs three lists containing the timeslice index, the value on each timeslice and the error on each timeslice.
223 def symmetric(self): 224 """ Symmetrize the correlator around x0=0.""" 225 if self.N != 1: 226 raise Exception('symmetric cannot be safely applied to multi-dimensional correlators.') 227 if self.T % 2 != 0: 228 raise Exception("Can not symmetrize odd T") 229 230 if self.content[0] is not None: 231 if np.argmax(np.abs([o[0].value if o is not None else 0 for o in self.content])) != 0: 232 warnings.warn("Correlator does not seem to be symmetric around x0=0.", RuntimeWarning) 233 234 newcontent = [self.content[0]] 235 for t in range(1, self.T): 236 if (self.content[t] is None) or (self.content[self.T - t] is None): 237 newcontent.append(None) 238 else: 239 newcontent.append(0.5 * (self.content[t] + self.content[self.T - t])) 240 if (all([x is None for x in newcontent])): 241 raise Exception("Corr could not be symmetrized: No redundant values") 242 return Corr(newcontent, prange=self.prange)
Symmetrize the correlator around x0=0.
244 def anti_symmetric(self): 245 """Anti-symmetrize the correlator around x0=0.""" 246 if self.N != 1: 247 raise TypeError('anti_symmetric cannot be safely applied to multi-dimensional correlators.') 248 if self.T % 2 != 0: 249 raise Exception("Can not symmetrize odd T") 250 251 test = 1 * self 252 test.gamma_method() 253 if not all([o.is_zero_within_error(3) for o in test.content[0]]): 254 warnings.warn("Correlator does not seem to be anti-symmetric around x0=0.", RuntimeWarning) 255 256 newcontent = [self.content[0]] 257 for t in range(1, self.T): 258 if (self.content[t] is None) or (self.content[self.T - t] is None): 259 newcontent.append(None) 260 else: 261 newcontent.append(0.5 * (self.content[t] - self.content[self.T - t])) 262 if (all([x is None for x in newcontent])): 263 raise Exception("Corr could not be symmetrized: No redundant values") 264 return Corr(newcontent, prange=self.prange)
Anti-symmetrize the correlator around x0=0.
266 def is_matrix_symmetric(self): 267 """Checks whether a correlator matrices is symmetric on every timeslice.""" 268 if self.N == 1: 269 raise TypeError("Only works for correlator matrices.") 270 for t in range(self.T): 271 if self[t] is None: 272 continue 273 for i in range(self.N): 274 for j in range(i + 1, self.N): 275 if self[t][i, j] is self[t][j, i]: 276 continue 277 if hash(self[t][i, j]) != hash(self[t][j, i]): 278 return False 279 return True
Checks whether a correlator matrices is symmetric on every timeslice.
281 def trace(self): 282 """Calculates the per-timeslice trace of a correlator matrix.""" 283 if self.N == 1: 284 raise ValueError("Only works for correlator matrices.") 285 newcontent = [] 286 for t in range(self.T): 287 if _check_for_none(self, self.content[t]): 288 newcontent.append(None) 289 else: 290 newcontent.append(np.trace(self.content[t])) 291 return Corr(newcontent)
Calculates the per-timeslice trace of a correlator matrix.
293 def matrix_symmetric(self): 294 """Symmetrizes the correlator matrices on every timeslice.""" 295 if self.N == 1: 296 raise Exception("Trying to symmetrize a correlator matrix, that already has N=1.") 297 if self.is_matrix_symmetric(): 298 return 1.0 * self 299 else: 300 transposed = [None if _check_for_none(self, G) else G.T for G in self.content] 301 return 0.5 * (Corr(transposed) + self)
Symmetrizes the correlator matrices on every timeslice.
303 def GEVP(self, t0, ts=None, sort="Eigenvalue", vector_obs=False, **kwargs): 304 r'''Solve the generalized eigenvalue problem on the correlator matrix and returns the corresponding eigenvectors. 305 306 The eigenvectors are sorted according to the descending eigenvalues, the zeroth eigenvector(s) correspond to the 307 largest eigenvalue(s). The eigenvector(s) for the individual states can be accessed via slicing 308 ```python 309 C.GEVP(t0=2)[0] # Ground state vector(s) 310 C.GEVP(t0=2)[:3] # Vectors for the lowest three states 311 ``` 312 313 Parameters 314 ---------- 315 t0 : int 316 The time t0 for the right hand side of the GEVP according to $G(t)v_i=\lambda_i G(t_0)v_i$ 317 ts : int 318 fixed time $G(t_s)v_i=\lambda_i G(t_0)v_i$ if sort=None. 319 If sort="Eigenvector" it gives a reference point for the sorting method. 320 sort : string 321 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. 322 - "Eigenvalue": The eigenvector is chosen according to which eigenvalue it belongs individually on every timeslice. (default) 323 - "Eigenvector": Use the method described in arXiv:2004.10472 to find the set of v(t) belonging to the state. 324 The reference state is identified by its eigenvalue at $t=t_s$. 325 - None: The GEVP is solved only at ts, no sorting is necessary 326 vector_obs : bool 327 If True, uncertainties are propagated in the eigenvector computation (default False). 328 329 Other Parameters 330 ---------------- 331 state : int 332 Returns only the vector(s) for a specified state. The lowest state is zero. 333 method : str 334 Method used to solve the GEVP. 335 - "eigh": Use scipy.linalg.eigh to solve the GEVP. (default for vector_obs=False) 336 - "cholesky": Use manually implemented solution via the Cholesky decomposition. Automatically chosen if vector_obs==True. 337 ''' 338 339 if self.N == 1: 340 raise Exception("GEVP methods only works on correlator matrices and not single correlators.") 341 if ts is not None: 342 if (ts <= t0): 343 raise Exception("ts has to be larger than t0.") 344 345 if "sorted_list" in kwargs: 346 warnings.warn("Argument 'sorted_list' is deprecated, use 'sort' instead.", DeprecationWarning) 347 sort = kwargs.get("sorted_list") 348 349 if self.is_matrix_symmetric(): 350 symmetric_corr = self 351 else: 352 symmetric_corr = self.matrix_symmetric() 353 354 def _get_mat_at_t(t, vector_obs=vector_obs): 355 if vector_obs: 356 return symmetric_corr[t] 357 else: 358 return np.vectorize(lambda x: x.value)(symmetric_corr[t]) 359 G0 = _get_mat_at_t(t0) 360 361 method = kwargs.get('method', 'eigh') 362 if vector_obs: 363 chol = linalg.cholesky(G0) 364 chol_inv = linalg.inv(chol) 365 method = 'cholesky' 366 else: 367 chol = np.linalg.cholesky(_get_mat_at_t(t0, vector_obs=False)) # Check if matrix G0 is positive-semidefinite. 368 if method == 'cholesky': 369 chol_inv = np.linalg.inv(chol) 370 else: 371 chol_inv = None 372 373 if sort is None: 374 if (ts is None): 375 raise Exception("ts is required if sort=None.") 376 if (self.content[t0] is None) or (self.content[ts] is None): 377 raise Exception("Corr not defined at t0/ts.") 378 Gt = _get_mat_at_t(ts) 379 reordered_vecs = _GEVP_solver(Gt, G0, method=method, chol_inv=chol_inv) 380 if kwargs.get('auto_gamma', False) and vector_obs: 381 [[o.gm() for o in ev if isinstance(o, Obs)] for ev in reordered_vecs] 382 383 elif sort in ["Eigenvalue", "Eigenvector"]: 384 if sort == "Eigenvalue" and ts is not None: 385 warnings.warn("ts has no effect when sorting by eigenvalue is chosen.", RuntimeWarning) 386 all_vecs = [None] * (t0 + 1) 387 for t in range(t0 + 1, self.T): 388 try: 389 Gt = _get_mat_at_t(t) 390 all_vecs.append(_GEVP_solver(Gt, G0, method=method, chol_inv=chol_inv)) 391 except Exception: 392 all_vecs.append(None) 393 if sort == "Eigenvector": 394 if ts is None: 395 raise Exception("ts is required for the Eigenvector sorting method.") 396 all_vecs = _sort_vectors(all_vecs, ts) 397 398 reordered_vecs = [[v[s] if v is not None else None for v in all_vecs] for s in range(self.N)] 399 if kwargs.get('auto_gamma', False) and vector_obs: 400 [[[o.gm() for o in evn] for evn in ev if evn is not None] for ev in reordered_vecs] 401 else: 402 raise Exception("Unknown value for 'sort'. Choose 'Eigenvalue', 'Eigenvector' or None.") 403 404 if "state" in kwargs: 405 return reordered_vecs[kwargs.get("state")] 406 else: 407 return reordered_vecs
Solve the generalized eigenvalue problem on the correlator matrix and returns the corresponding eigenvectors.
The eigenvectors are sorted according to the descending eigenvalues, the zeroth eigenvector(s) correspond to the largest eigenvalue(s). The eigenvector(s) for the individual states can be accessed via slicing
C.GEVP(t0=2)[0] # Ground state vector(s)
C.GEVP(t0=2)[:3] # Vectors for the lowest three states
Parameters
- t0 (int): The time t0 for the right hand side of the GEVP according to $G(t)v_i=\lambda_i G(t_0)v_i$
- ts (int): fixed time $G(t_s)v_i=\lambda_i G(t_0)v_i$ if sort=None. If sort="Eigenvector" it gives a reference point for the sorting method.
- sort (string):
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.
- "Eigenvalue": The eigenvector is chosen according to which eigenvalue it belongs individually on every timeslice. (default)
- "Eigenvector": Use the method described in arXiv:2004.10472 to find the set of v(t) belonging to the state. The reference state is identified by its eigenvalue at $t=t_s$.
- None: The GEVP is solved only at ts, no sorting is necessary
- vector_obs (bool): If True, uncertainties are propagated in the eigenvector computation (default False).
Other Parameters
- state (int): Returns only the vector(s) for a specified state. The lowest state is zero.
- method (str):
Method used to solve the GEVP.
- "eigh": Use scipy.linalg.eigh to solve the GEVP. (default for vector_obs=False)
- "cholesky": Use manually implemented solution via the Cholesky decomposition. Automatically chosen if vector_obs==True.
409 def Eigenvalue(self, t0, ts=None, state=0, sort="Eigenvalue", **kwargs): 410 """Determines the eigenvalue of the GEVP by solving and projecting the correlator 411 412 Parameters 413 ---------- 414 state : int 415 The state one is interested in ordered by energy. The lowest state is zero. 416 417 All other parameters are identical to the ones of Corr.GEVP. 418 """ 419 vec = self.GEVP(t0, ts=ts, sort=sort, **kwargs)[state] 420 return self.projected(vec)
Determines the eigenvalue of the GEVP by solving and projecting the correlator
Parameters
- state (int): The state one is interested in ordered by energy. The lowest state is zero.
- All other parameters are identical to the ones of Corr.GEVP.
422 def Hankel(self, N, periodic=False): 423 """Constructs an NxN Hankel matrix 424 425 C(t) c(t+1) ... c(t+n-1) 426 C(t+1) c(t+2) ... c(t+n) 427 ................. 428 C(t+(n-1)) c(t+n) ... c(t+2(n-1)) 429 430 Parameters 431 ---------- 432 N : int 433 Dimension of the Hankel matrix 434 periodic : bool, optional 435 determines whether the matrix is extended periodically 436 """ 437 438 if self.N != 1: 439 raise Exception("Multi-operator Prony not implemented!") 440 441 array = np.empty([N, N], dtype="object") 442 new_content = [] 443 for t in range(self.T): 444 new_content.append(array.copy()) 445 446 def wrap(i): 447 while i >= self.T: 448 i -= self.T 449 return i 450 451 for t in range(self.T): 452 for i in range(N): 453 for j in range(N): 454 if periodic: 455 new_content[t][i, j] = self.content[wrap(t + i + j)][0] 456 elif (t + i + j) >= self.T: 457 new_content[t] = None 458 else: 459 new_content[t][i, j] = self.content[t + i + j][0] 460 461 return Corr(new_content)
Constructs an NxN Hankel matrix
C(t) c(t+1) ... c(t+n-1) C(t+1) c(t+2) ... c(t+n) ................. C(t+(n-1)) c(t+n) ... c(t+2(n-1))
Parameters
- N (int): Dimension of the Hankel matrix
- periodic (bool, optional): determines whether the matrix is extended periodically
463 def roll(self, dt): 464 """Periodically shift the correlator by dt timeslices 465 466 Parameters 467 ---------- 468 dt : int 469 number of timeslices 470 """ 471 return Corr(list(np.roll(np.array(self.content, dtype=object), dt, axis=0)))
Periodically shift the correlator by dt timeslices
Parameters
- dt (int): number of timeslices
473 def reverse(self): 474 """Reverse the time ordering of the Corr""" 475 return Corr(self.content[:: -1])
Reverse the time ordering of the Corr
477 def thin(self, spacing=2, offset=0): 478 """Thin out a correlator to suppress correlations 479 480 Parameters 481 ---------- 482 spacing : int 483 Keep only every 'spacing'th entry of the correlator 484 offset : int 485 Offset the equal spacing 486 """ 487 new_content = [] 488 for t in range(self.T): 489 if (offset + t) % spacing != 0: 490 new_content.append(None) 491 else: 492 new_content.append(self.content[t]) 493 return Corr(new_content)
Thin out a correlator to suppress correlations
Parameters
- spacing (int): Keep only every 'spacing'th entry of the correlator
- offset (int): Offset the equal spacing
495 def correlate(self, partner): 496 """Correlate the correlator with another correlator or Obs 497 498 Parameters 499 ---------- 500 partner : Obs or Corr 501 partner to correlate the correlator with. 502 Can either be an Obs which is correlated with all entries of the 503 correlator or a Corr of same length. 504 """ 505 if self.N != 1: 506 raise Exception("Only one-dimensional correlators can be safely correlated.") 507 new_content = [] 508 for x0, t_slice in enumerate(self.content): 509 if _check_for_none(self, t_slice): 510 new_content.append(None) 511 else: 512 if isinstance(partner, Corr): 513 if _check_for_none(partner, partner.content[x0]): 514 new_content.append(None) 515 else: 516 new_content.append(np.array([correlate(o, partner.content[x0][0]) for o in t_slice])) 517 elif isinstance(partner, Obs): # Should this include CObs? 518 new_content.append(np.array([correlate(o, partner) for o in t_slice])) 519 else: 520 raise Exception("Can only correlate with an Obs or a Corr.") 521 522 return Corr(new_content)
Correlate the correlator with another correlator or Obs
Parameters
- partner (Obs or Corr): partner to correlate the correlator with. Can either be an Obs which is correlated with all entries of the correlator or a Corr of same length.
524 def reweight(self, weight, **kwargs): 525 """Reweight the correlator. 526 527 Parameters 528 ---------- 529 weight : Obs 530 Reweighting factor. An Observable that has to be defined on a superset of the 531 configurations in obs[i].idl for all i. 532 all_configs : bool 533 if True, the reweighted observables are normalized by the average of 534 the reweighting factor on all configurations in weight.idl and not 535 on the configurations in obs[i].idl. 536 """ 537 if self.N != 1: 538 raise Exception("Reweighting only implemented for one-dimensional correlators.") 539 new_content = [] 540 for t_slice in self.content: 541 if _check_for_none(self, t_slice): 542 new_content.append(None) 543 else: 544 new_content.append(np.array(reweight(weight, t_slice, **kwargs))) 545 return Corr(new_content)
Reweight the correlator.
Parameters
- weight (Obs): Reweighting factor. An Observable that has to be defined on a superset of the configurations in obs[i].idl for all i.
- all_configs (bool): if True, the reweighted observables are normalized by the average of the reweighting factor on all configurations in weight.idl and not on the configurations in obs[i].idl.
547 def T_symmetry(self, partner, parity=+1): 548 """Return the time symmetry average of the correlator and its partner 549 550 Parameters 551 ---------- 552 partner : Corr 553 Time symmetry partner of the Corr 554 parity : int 555 Parity quantum number of the correlator, can be +1 or -1 556 """ 557 if self.N != 1: 558 raise Exception("T_symmetry only implemented for one-dimensional correlators.") 559 if not isinstance(partner, Corr): 560 raise Exception("T partner has to be a Corr object.") 561 if parity not in [+1, -1]: 562 raise Exception("Parity has to be +1 or -1.") 563 T_partner = parity * partner.reverse() 564 565 t_slices = [] 566 test = (self - T_partner) 567 test.gamma_method() 568 for x0, t_slice in enumerate(test.content): 569 if t_slice is not None: 570 if not t_slice[0].is_zero_within_error(5): 571 t_slices.append(x0) 572 if t_slices: 573 warnings.warn("T symmetry partners do not agree within 5 sigma on time slices " + str(t_slices) + ".", RuntimeWarning) 574 575 return (self + T_partner) / 2
Return the time symmetry average of the correlator and its partner
Parameters
- partner (Corr): Time symmetry partner of the Corr
- parity (int): Parity quantum number of the correlator, can be +1 or -1
577 def deriv(self, variant="symmetric"): 578 """Return the first derivative of the correlator with respect to x0. 579 580 Parameters 581 ---------- 582 variant : str 583 decides which definition of the finite differences derivative is used. 584 Available choice: symmetric, forward, backward, improved, log, default: symmetric 585 """ 586 if self.N != 1: 587 raise Exception("deriv only implemented for one-dimensional correlators.") 588 if variant == "symmetric": 589 newcontent = [] 590 for t in range(1, self.T - 1): 591 if (self.content[t - 1] is None) or (self.content[t + 1] is None): 592 newcontent.append(None) 593 else: 594 newcontent.append(0.5 * (self.content[t + 1] - self.content[t - 1])) 595 if (all([x is None for x in newcontent])): 596 raise Exception('Derivative is undefined at all timeslices') 597 return Corr(newcontent, padding=[1, 1]) 598 elif variant == "forward": 599 newcontent = [] 600 for t in range(self.T - 1): 601 if (self.content[t] is None) or (self.content[t + 1] is None): 602 newcontent.append(None) 603 else: 604 newcontent.append(self.content[t + 1] - self.content[t]) 605 if (all([x is None for x in newcontent])): 606 raise Exception("Derivative is undefined at all timeslices") 607 return Corr(newcontent, padding=[0, 1]) 608 elif variant == "backward": 609 newcontent = [] 610 for t in range(1, self.T): 611 if (self.content[t - 1] is None) or (self.content[t] is None): 612 newcontent.append(None) 613 else: 614 newcontent.append(self.content[t] - self.content[t - 1]) 615 if (all([x is None for x in newcontent])): 616 raise Exception("Derivative is undefined at all timeslices") 617 return Corr(newcontent, padding=[1, 0]) 618 elif variant == "improved": 619 newcontent = [] 620 for t in range(2, self.T - 2): 621 if (self.content[t - 2] is None) or (self.content[t - 1] is None) or (self.content[t + 1] is None) or (self.content[t + 2] is None): 622 newcontent.append(None) 623 else: 624 newcontent.append((1 / 12) * (self.content[t - 2] - 8 * self.content[t - 1] + 8 * self.content[t + 1] - self.content[t + 2])) 625 if (all([x is None for x in newcontent])): 626 raise Exception('Derivative is undefined at all timeslices') 627 return Corr(newcontent, padding=[2, 2]) 628 elif variant == 'log': 629 newcontent = [] 630 for t in range(self.T): 631 if (self.content[t] is None) or (self.content[t] <= 0): 632 newcontent.append(None) 633 else: 634 newcontent.append(np.log(self.content[t])) 635 if (all([x is None for x in newcontent])): 636 raise Exception("Log is undefined at all timeslices") 637 logcorr = Corr(newcontent) 638 return self * logcorr.deriv('symmetric') 639 else: 640 raise Exception("Unknown variant.")
Return the first derivative of the correlator with respect to x0.
Parameters
- variant (str): decides which definition of the finite differences derivative is used. Available choice: symmetric, forward, backward, improved, log, default: symmetric
642 def second_deriv(self, variant="symmetric"): 643 r"""Return the second derivative of the correlator with respect to x0. 644 645 Parameters 646 ---------- 647 variant : str 648 decides which definition of the finite differences derivative is used. 649 Available choice: 650 - symmetric (default) 651 $$\tilde{\partial}^2_0 f(x_0) = f(x_0+1)-2f(x_0)+f(x_0-1)$$ 652 - big_symmetric 653 $$\partial^2_0 f(x_0) = \frac{f(x_0+2)-2f(x_0)+f(x_0-2)}{4}$$ 654 - improved 655 $$\partial^2_0 f(x_0) = \frac{-f(x_0+2) + 16 * f(x_0+1) - 30 * f(x_0) + 16 * f(x_0-1) - f(x_0-2)}{12}$$ 656 - log 657 $$f(x) = \tilde{\partial}^2_0 log(f(x_0))+(\tilde{\partial}_0 log(f(x_0)))^2$$ 658 """ 659 if self.N != 1: 660 raise Exception("second_deriv only implemented for one-dimensional correlators.") 661 if variant == "symmetric": 662 newcontent = [] 663 for t in range(1, self.T - 1): 664 if (self.content[t - 1] is None) or (self.content[t + 1] is None): 665 newcontent.append(None) 666 else: 667 newcontent.append((self.content[t + 1] - 2 * self.content[t] + self.content[t - 1])) 668 if (all([x is None for x in newcontent])): 669 raise Exception("Derivative is undefined at all timeslices") 670 return Corr(newcontent, padding=[1, 1]) 671 elif variant == "big_symmetric": 672 newcontent = [] 673 for t in range(2, self.T - 2): 674 if (self.content[t - 2] is None) or (self.content[t + 2] is None): 675 newcontent.append(None) 676 else: 677 newcontent.append((self.content[t + 2] - 2 * self.content[t] + self.content[t - 2]) / 4) 678 if (all([x is None for x in newcontent])): 679 raise Exception("Derivative is undefined at all timeslices") 680 return Corr(newcontent, padding=[2, 2]) 681 elif variant == "improved": 682 newcontent = [] 683 for t in range(2, self.T - 2): 684 if (self.content[t - 2] is None) or (self.content[t - 1] is None) or (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 2] is None): 685 newcontent.append(None) 686 else: 687 newcontent.append((1 / 12) * (-self.content[t + 2] + 16 * self.content[t + 1] - 30 * self.content[t] + 16 * self.content[t - 1] - self.content[t - 2])) 688 if (all([x is None for x in newcontent])): 689 raise Exception("Derivative is undefined at all timeslices") 690 return Corr(newcontent, padding=[2, 2]) 691 elif variant == 'log': 692 newcontent = [] 693 for t in range(self.T): 694 if (self.content[t] is None) or (self.content[t] <= 0): 695 newcontent.append(None) 696 else: 697 newcontent.append(np.log(self.content[t])) 698 if (all([x is None for x in newcontent])): 699 raise Exception("Log is undefined at all timeslices") 700 logcorr = Corr(newcontent) 701 return self * (logcorr.second_deriv('symmetric') + (logcorr.deriv('symmetric'))**2) 702 else: 703 raise Exception("Unknown variant.")
Return the second derivative of the correlator with respect to x0.
Parameters
- variant (str): decides which definition of the finite differences derivative is used. Available choice: - symmetric (default) $$\tilde{\partial}^2_0 f(x_0) = f(x_0+1)-2f(x_0)+f(x_0-1)$$ - big_symmetric $$\partial^2_0 f(x_0) = \frac{f(x_0+2)-2f(x_0)+f(x_0-2)}{4}$$ - improved $$\partial^2_0 f(x_0) = \frac{-f(x_0+2) + 16 * f(x_0+1) - 30 * f(x_0) + 16 * f(x_0-1) - f(x_0-2)}{12}$$ - log $$f(x) = \tilde{\partial}^2_0 log(f(x_0))+(\tilde{\partial}_0 log(f(x_0)))^2$$
705 def m_eff(self, variant='log', guess=1.0): 706 """Returns the effective mass of the correlator as correlator object 707 708 Parameters 709 ---------- 710 variant : str 711 log : uses the standard effective mass log(C(t) / C(t+1)) 712 cosh, periodic : Use periodicity of the correlator by solving C(t) / C(t+1) = cosh(m * (t - T/2)) / cosh(m * (t + 1 - T/2)) for m. 713 sinh : Use anti-periodicity of the correlator by solving C(t) / C(t+1) = sinh(m * (t - T/2)) / sinh(m * (t + 1 - T/2)) for m. 714 See, e.g., arXiv:1205.5380 715 arccosh : Uses the explicit form of the symmetrized correlator (not recommended) 716 logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2 717 guess : float 718 guess for the root finder, only relevant for the root variant 719 """ 720 if self.N != 1: 721 raise Exception('Correlator must be projected before getting m_eff') 722 if variant == 'log': 723 newcontent = [] 724 for t in range(self.T - 1): 725 if ((self.content[t] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 726 newcontent.append(None) 727 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 728 newcontent.append(None) 729 else: 730 newcontent.append(self.content[t] / self.content[t + 1]) 731 if (all([x is None for x in newcontent])): 732 raise Exception('m_eff is undefined at all timeslices') 733 734 return np.log(Corr(newcontent, padding=[0, 1])) 735 736 elif variant == 'logsym': 737 newcontent = [] 738 for t in range(1, self.T - 1): 739 if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 740 newcontent.append(None) 741 elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0: 742 newcontent.append(None) 743 else: 744 newcontent.append(self.content[t - 1] / self.content[t + 1]) 745 if (all([x is None for x in newcontent])): 746 raise Exception('m_eff is undefined at all timeslices') 747 748 return np.log(Corr(newcontent, padding=[1, 1])) / 2 749 750 elif variant in ['periodic', 'cosh', 'sinh']: 751 if variant in ['periodic', 'cosh']: 752 func = anp.cosh 753 else: 754 func = anp.sinh 755 756 def root_function(x, d): 757 return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d 758 759 newcontent = [] 760 for t in range(self.T - 1): 761 if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0): 762 newcontent.append(None) 763 # Fill the two timeslices in the middle of the lattice with their predecessors 764 elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]: 765 newcontent.append(newcontent[-1]) 766 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 767 newcontent.append(None) 768 else: 769 newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess))) 770 if (all([x is None for x in newcontent])): 771 raise Exception('m_eff is undefined at all timeslices') 772 773 return Corr(newcontent, padding=[0, 1]) 774 775 elif variant == 'arccosh': 776 newcontent = [] 777 for t in range(1, self.T - 1): 778 if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t - 1] is None) or (self.content[t][0].value == 0): 779 newcontent.append(None) 780 else: 781 newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t])) 782 if (all([x is None for x in newcontent])): 783 raise Exception("m_eff is undefined at all timeslices") 784 return np.arccosh(Corr(newcontent, padding=[1, 1])) 785 786 else: 787 raise Exception('Unknown variant.')
Returns the effective mass of the correlator as correlator object
Parameters
- variant (str): log : uses the standard effective mass log(C(t) / C(t+1)) cosh, periodic : Use periodicity of the correlator by solving C(t) / C(t+1) = cosh(m * (t - T/2)) / cosh(m * (t + 1 - T/2)) for m. sinh : Use anti-periodicity of the correlator by solving C(t) / C(t+1) = sinh(m * (t - T/2)) / sinh(m * (t + 1 - T/2)) for m. See, e.g., arXiv:1205.5380 arccosh : Uses the explicit form of the symmetrized correlator (not recommended) logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2
- guess (float): guess for the root finder, only relevant for the root variant
789 def fit(self, function, fitrange=None, silent=False, **kwargs): 790 r'''Fits function to the data 791 792 Parameters 793 ---------- 794 function : obj 795 function to fit to the data. See fits.least_squares for details. 796 fitrange : list 797 Two element list containing the timeslices on which the fit is supposed to start and stop. 798 Caution: This range is inclusive as opposed to standard python indexing. 799 `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6. 800 If not specified, self.prange or all timeslices are used. 801 silent : bool 802 Decides whether output is printed to the standard output. 803 ''' 804 if self.N != 1: 805 raise Exception("Correlator must be projected before fitting") 806 807 if fitrange is None: 808 if self.prange: 809 fitrange = self.prange 810 else: 811 fitrange = [0, self.T - 1] 812 else: 813 if not isinstance(fitrange, list): 814 raise Exception("fitrange has to be a list with two elements") 815 if len(fitrange) != 2: 816 raise Exception("fitrange has to have exactly two elements [fit_start, fit_stop]") 817 818 xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]) 819 ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]) 820 result = least_squares(xs, ys, function, silent=silent, **kwargs) 821 return result
Fits function to the data
Parameters
- function (obj): function to fit to the data. See fits.least_squares for details.
- fitrange (list):
Two element list containing the timeslices on which the fit is supposed to start and stop.
Caution: This range is inclusive as opposed to standard python indexing.
fitrange=[4, 6]
corresponds to the three entries 4, 5 and 6. If not specified, self.prange or all timeslices are used. - silent (bool): Decides whether output is printed to the standard output.
823 def plateau(self, plateau_range=None, method="fit", auto_gamma=False): 824 """ Extract a plateau value from a Corr object 825 826 Parameters 827 ---------- 828 plateau_range : list 829 list with two entries, indicating the first and the last timeslice 830 of the plateau region. 831 method : str 832 method to extract the plateau. 833 'fit' fits a constant to the plateau region 834 'avg', 'average' or 'mean' just average over the given timeslices. 835 auto_gamma : bool 836 apply gamma_method with default parameters to the Corr. Defaults to None 837 """ 838 if not plateau_range: 839 if self.prange: 840 plateau_range = self.prange 841 else: 842 raise Exception("no plateau range provided") 843 if self.N != 1: 844 raise Exception("Correlator must be projected before getting a plateau.") 845 if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])): 846 raise Exception("plateau is undefined at all timeslices in plateaurange.") 847 if auto_gamma: 848 self.gamma_method() 849 if method == "fit": 850 def const_func(a, t): 851 return a[0] 852 return self.fit(const_func, plateau_range)[0] 853 elif method in ["avg", "average", "mean"]: 854 returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None]) 855 return returnvalue 856 857 else: 858 raise Exception("Unsupported plateau method: " + method)
Extract a plateau value from a Corr object
Parameters
- plateau_range (list): list with two entries, indicating the first and the last timeslice of the plateau region.
- method (str): method to extract the plateau. 'fit' fits a constant to the plateau region 'avg', 'average' or 'mean' just average over the given timeslices.
- auto_gamma (bool): apply gamma_method with default parameters to the Corr. Defaults to None
860 def set_prange(self, prange): 861 """Sets the attribute prange of the Corr object.""" 862 if not len(prange) == 2: 863 raise Exception("prange must be a list or array with two values") 864 if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))): 865 raise Exception("Start and end point must be integers") 866 if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] < prange[1]): 867 raise Exception("Start and end point must define a range in the interval 0,T") 868 869 self.prange = prange 870 return
Sets the attribute prange of the Corr object.
872 def show(self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, fit_key=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None): 873 """Plots the correlator using the tag of the correlator as label if available. 874 875 Parameters 876 ---------- 877 x_range : list 878 list of two values, determining the range of the x-axis e.g. [4, 8]. 879 comp : Corr or list of Corr 880 Correlator or list of correlators which are plotted for comparison. 881 The tags of these correlators are used as labels if available. 882 logscale : bool 883 Sets y-axis to logscale. 884 plateau : Obs 885 Plateau value to be visualized in the figure. 886 fit_res : Fit_result 887 Fit_result object to be visualized. 888 fit_key : str 889 Key for the fit function in Fit_result.fit_function (for combined fits). 890 ylabel : str 891 Label for the y-axis. 892 save : str 893 path to file in which the figure should be saved. 894 auto_gamma : bool 895 Apply the gamma method with standard parameters to all correlators and plateau values before plotting. 896 hide_sigma : float 897 Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors. 898 references : list 899 List of floating point values that are displayed as horizontal lines for reference. 900 title : string 901 Optional title of the figure. 902 """ 903 if self.N != 1: 904 raise Exception("Correlator must be projected before plotting") 905 906 if auto_gamma: 907 self.gamma_method() 908 909 if x_range is None: 910 x_range = [0, self.T - 1] 911 912 fig = plt.figure() 913 ax1 = fig.add_subplot(111) 914 915 x, y, y_err = self.plottable() 916 if hide_sigma: 917 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 918 else: 919 hide_from = None 920 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag) 921 if logscale: 922 ax1.set_yscale('log') 923 else: 924 if y_range is None: 925 try: 926 y_min = min([(x[0].value - x[0].dvalue) for x in self.content[x_range[0]: x_range[1] + 1] if (x is not None) and x[0].dvalue < 2 * np.abs(x[0].value)]) 927 y_max = max([(x[0].value + x[0].dvalue) for x in self.content[x_range[0]: x_range[1] + 1] if (x is not None) and x[0].dvalue < 2 * np.abs(x[0].value)]) 928 ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)]) 929 except Exception: 930 pass 931 else: 932 ax1.set_ylim(y_range) 933 if comp: 934 if isinstance(comp, (Corr, list)): 935 for corr in comp if isinstance(comp, list) else [comp]: 936 if auto_gamma: 937 corr.gamma_method() 938 x, y, y_err = corr.plottable() 939 if hide_sigma: 940 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 941 else: 942 hide_from = None 943 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor']) 944 else: 945 raise Exception("'comp' must be a correlator or a list of correlators.") 946 947 if plateau: 948 if isinstance(plateau, Obs): 949 if auto_gamma: 950 plateau.gamma_method() 951 ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau)) 952 ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-') 953 else: 954 raise Exception("'plateau' must be an Obs") 955 956 if references: 957 if isinstance(references, list): 958 for ref in references: 959 ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--') 960 else: 961 raise Exception("'references' must be a list of floating pint values.") 962 963 if self.prange: 964 ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',', color="black", zorder=0) 965 ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',', color="black", zorder=0) 966 967 if fit_res: 968 x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05) 969 if isinstance(fit_res.fit_function, dict): 970 if fit_key: 971 ax1.plot(x_samples, fit_res.fit_function[fit_key]([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2) 972 else: 973 raise ValueError("Please provide a 'fit_key' for visualizing combined fits.") 974 else: 975 ax1.plot(x_samples, fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2) 976 977 ax1.set_xlabel(r'$x_0 / a$') 978 if ylabel: 979 ax1.set_ylabel(ylabel) 980 ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5]) 981 982 handles, labels = ax1.get_legend_handles_labels() 983 if labels: 984 ax1.legend() 985 986 if title: 987 plt.title(title) 988 989 plt.draw() 990 991 if save: 992 if isinstance(save, str): 993 fig.savefig(save, bbox_inches='tight') 994 else: 995 raise Exception("'save' has to be a string.")
Plots the correlator using the tag of the correlator as label if available.
Parameters
- x_range (list): list of two values, determining the range of the x-axis e.g. [4, 8].
- comp (Corr or list of Corr): Correlator or list of correlators which are plotted for comparison. The tags of these correlators are used as labels if available.
- logscale (bool): Sets y-axis to logscale.
- plateau (Obs): Plateau value to be visualized in the figure.
- fit_res (Fit_result): Fit_result object to be visualized.
- fit_key (str): Key for the fit function in Fit_result.fit_function (for combined fits).
- ylabel (str): Label for the y-axis.
- save (str): path to file in which the figure should be saved.
- auto_gamma (bool): Apply the gamma method with standard parameters to all correlators and plateau values before plotting.
- hide_sigma (float): Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors.
- references (list): List of floating point values that are displayed as horizontal lines for reference.
- title (string): Optional title of the figure.
997 def spaghetti_plot(self, logscale=True): 998 """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations. 999 1000 Parameters 1001 ---------- 1002 logscale : bool 1003 Determines whether the scale of the y-axis is logarithmic or standard. 1004 """ 1005 if self.N != 1: 1006 raise Exception("Correlator needs to be projected first.") 1007 1008 mc_names = list(set([item for sublist in [sum(map(o[0].e_content.get, o[0].mc_names), []) for o in self.content if o is not None] for item in sublist])) 1009 x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content) if o is not None] 1010 1011 for name in mc_names: 1012 data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T 1013 1014 fig = plt.figure() 1015 ax = fig.add_subplot(111) 1016 for dat in data: 1017 ax.plot(x0_vals, dat, ls='-', marker='') 1018 1019 if logscale is True: 1020 ax.set_yscale('log') 1021 1022 ax.set_xlabel(r'$x_0 / a$') 1023 plt.title(name) 1024 plt.draw()
Produces a spaghetti plot of the correlator suited to monitor exceptional configurations.
Parameters
- logscale (bool): Determines whether the scale of the y-axis is logarithmic or standard.
1026 def dump(self, filename, datatype="json.gz", **kwargs): 1027 """Dumps the Corr into a file of chosen type 1028 Parameters 1029 ---------- 1030 filename : str 1031 Name of the file to be saved. 1032 datatype : str 1033 Format of the exported file. Supported formats include 1034 "json.gz" and "pickle" 1035 path : str 1036 specifies a custom path for the file (default '.') 1037 """ 1038 if datatype == "json.gz": 1039 from .input.json import dump_to_json 1040 if 'path' in kwargs: 1041 file_name = kwargs.get('path') + '/' + filename 1042 else: 1043 file_name = filename 1044 dump_to_json(self, file_name) 1045 elif datatype == "pickle": 1046 dump_object(self, filename, **kwargs) 1047 else: 1048 raise Exception("Unknown datatype " + str(datatype))
Dumps the Corr into a file of chosen type
Parameters
- filename (str): Name of the file to be saved.
- datatype (str): Format of the exported file. Supported formats include "json.gz" and "pickle"
- path (str): specifies a custom path for the file (default '.')
1350 @property 1351 def imag(self): 1352 def return_imag(obs_OR_cobs): 1353 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1354 return np.vectorize(lambda x: x.imag)(obs_OR_cobs) 1355 else: 1356 return obs_OR_cobs * 0 # So it stays the right type 1357 1358 return self._apply_func_to_corr(return_imag)
1360 def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None): 1361 r''' Project large correlation matrix to lowest states 1362 1363 This method can be used to reduce the size of an (N x N) correlation matrix 1364 to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise 1365 is still small. 1366 1367 Parameters 1368 ---------- 1369 Ntrunc: int 1370 Rank of the target matrix. 1371 tproj: int 1372 Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method. 1373 The default value is 3. 1374 t0proj: int 1375 Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly 1376 discouraged for O(a) improved theories, since the correctness of the procedure 1377 cannot be granted in this case. The default value is 2. 1378 basematrix : Corr 1379 Correlation matrix that is used to determine the eigenvectors of the 1380 lowest states based on a GEVP. basematrix is taken to be the Corr itself if 1381 is is not specified. 1382 1383 Notes 1384 ----- 1385 We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving 1386 the GEVP $$C(t) v_n(t, t_0) = \lambda_n(t, t_0) C(t_0) v_n(t, t_0)$$ where $t \equiv t_\mathrm{proj}$ 1387 and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the 1388 resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via 1389 $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large 1390 correlation matrix and to remove some noise that is added by irrelevant operators. 1391 This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated 1392 bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$. 1393 ''' 1394 1395 if self.N == 1: 1396 raise Exception('Method cannot be applied to one-dimensional correlators.') 1397 if basematrix is None: 1398 basematrix = self 1399 if Ntrunc >= basematrix.N: 1400 raise Exception('Cannot truncate using Ntrunc <= %d' % (basematrix.N)) 1401 if basematrix.N != self.N: 1402 raise Exception('basematrix and targetmatrix have to be of the same size.') 1403 1404 evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc] 1405 1406 tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object) 1407 rmat = [] 1408 for t in range(basematrix.T): 1409 for i in range(Ntrunc): 1410 for j in range(Ntrunc): 1411 tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j] 1412 rmat.append(np.copy(tmpmat)) 1413 1414 newcontent = [None if (self.content[t] is None) else rmat[t] for t in range(self.T)] 1415 return Corr(newcontent)
Project large correlation matrix to lowest states
This method can be used to reduce the size of an (N x N) correlation matrix to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise is still small.
Parameters
- Ntrunc (int): Rank of the target matrix.
- tproj (int): Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method. The default value is 3.
- t0proj (int): Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly discouraged for O(a) improved theories, since the correctness of the procedure cannot be granted in this case. The default value is 2.
- basematrix (Corr): Correlation matrix that is used to determine the eigenvectors of the lowest states based on a GEVP. basematrix is taken to be the Corr itself if is is not specified.
Notes
We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving the GEVP $$C(t) v_n(t, t_0) = \lambda_n(t, t_0) C(t_0) v_n(t, t_0)$$ where $t \equiv t_\mathrm{proj}$ and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large correlation matrix and to remove some noise that is added by irrelevant operators. This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.