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