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): 731 newcontent.append(None) 732 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 733 newcontent.append(None) 734 else: 735 newcontent.append(self.content[t] / self.content[t + 1]) 736 if (all([x is None for x in newcontent])): 737 raise ValueError('m_eff is undefined at all timeslices') 738 739 return np.log(Corr(newcontent, padding=[0, 1])) 740 741 elif variant == 'logsym': 742 newcontent = [] 743 for t in range(1, self.T - 1): 744 if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 745 newcontent.append(None) 746 elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0: 747 newcontent.append(None) 748 else: 749 newcontent.append(self.content[t - 1] / self.content[t + 1]) 750 if (all([x is None for x in newcontent])): 751 raise ValueError('m_eff is undefined at all timeslices') 752 753 return np.log(Corr(newcontent, padding=[1, 1])) / 2 754 755 elif variant in ['periodic', 'cosh', 'sinh']: 756 if variant in ['periodic', 'cosh']: 757 func = anp.cosh 758 else: 759 func = anp.sinh 760 761 def root_function(x, d): 762 return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d 763 764 newcontent = [] 765 for t in range(self.T - 1): 766 if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0): 767 newcontent.append(None) 768 # Fill the two timeslices in the middle of the lattice with their predecessors 769 elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]: 770 newcontent.append(newcontent[-1]) 771 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 772 newcontent.append(None) 773 else: 774 newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess))) 775 if (all([x is None for x in newcontent])): 776 raise ValueError('m_eff is undefined at all timeslices') 777 778 return Corr(newcontent, padding=[0, 1]) 779 780 elif variant == 'arccosh': 781 newcontent = [] 782 for t in range(1, self.T - 1): 783 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): 784 newcontent.append(None) 785 else: 786 newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t])) 787 if (all([x is None for x in newcontent])): 788 raise ValueError("m_eff is undefined at all timeslices") 789 return np.arccosh(Corr(newcontent, padding=[1, 1])) 790 791 else: 792 raise ValueError('Unknown variant.') 793 794 def fit(self, function, fitrange=None, silent=False, **kwargs): 795 r'''Fits function to the data 796 797 Parameters 798 ---------- 799 function : obj 800 function to fit to the data. See fits.least_squares for details. 801 fitrange : list 802 Two element list containing the timeslices on which the fit is supposed to start and stop. 803 Caution: This range is inclusive as opposed to standard python indexing. 804 `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6. 805 If not specified, self.prange or all timeslices are used. 806 silent : bool 807 Decides whether output is printed to the standard output. 808 ''' 809 if self.N != 1: 810 raise ValueError("Correlator must be projected before fitting") 811 812 if fitrange is None: 813 if self.prange: 814 fitrange = self.prange 815 else: 816 fitrange = [0, self.T - 1] 817 else: 818 if not isinstance(fitrange, list): 819 raise TypeError("fitrange has to be a list with two elements") 820 if len(fitrange) != 2: 821 raise ValueError("fitrange has to have exactly two elements [fit_start, fit_stop]") 822 823 xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None]) 824 ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None]) 825 result = least_squares(xs, ys, function, silent=silent, **kwargs) 826 return result 827 828 def plateau(self, plateau_range=None, method="fit", auto_gamma=False): 829 """ Extract a plateau value from a Corr object 830 831 Parameters 832 ---------- 833 plateau_range : list 834 list with two entries, indicating the first and the last timeslice 835 of the plateau region. 836 method : str 837 method to extract the plateau. 838 'fit' fits a constant to the plateau region 839 'avg', 'average' or 'mean' just average over the given timeslices. 840 auto_gamma : bool 841 apply gamma_method with default parameters to the Corr. Defaults to None 842 """ 843 if not plateau_range: 844 if self.prange: 845 plateau_range = self.prange 846 else: 847 raise ValueError("no plateau range provided") 848 if self.N != 1: 849 raise ValueError("Correlator must be projected before getting a plateau.") 850 if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])): 851 raise ValueError("plateau is undefined at all timeslices in plateaurange.") 852 if auto_gamma: 853 self.gamma_method() 854 if method == "fit": 855 def const_func(a, t): 856 return a[0] 857 return self.fit(const_func, plateau_range)[0] 858 elif method in ["avg", "average", "mean"]: 859 returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None]) 860 return returnvalue 861 862 else: 863 raise ValueError("Unsupported plateau method: " + method) 864 865 def set_prange(self, prange): 866 """Sets the attribute prange of the Corr object.""" 867 if not len(prange) == 2: 868 raise ValueError("prange must be a list or array with two values") 869 if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))): 870 raise TypeError("Start and end point must be integers") 871 if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] <= prange[1]): 872 raise ValueError("Start and end point must define a range in the interval 0,T") 873 874 self.prange = prange 875 return 876 877 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): 878 """Plots the correlator using the tag of the correlator as label if available. 879 880 Parameters 881 ---------- 882 x_range : list 883 list of two values, determining the range of the x-axis e.g. [4, 8]. 884 comp : Corr or list of Corr 885 Correlator or list of correlators which are plotted for comparison. 886 The tags of these correlators are used as labels if available. 887 logscale : bool 888 Sets y-axis to logscale. 889 plateau : Obs 890 Plateau value to be visualized in the figure. 891 fit_res : Fit_result 892 Fit_result object to be visualized. 893 fit_key : str 894 Key for the fit function in Fit_result.fit_function (for combined fits). 895 ylabel : str 896 Label for the y-axis. 897 save : str 898 path to file in which the figure should be saved. 899 auto_gamma : bool 900 Apply the gamma method with standard parameters to all correlators and plateau values before plotting. 901 hide_sigma : float 902 Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors. 903 references : list 904 List of floating point values that are displayed as horizontal lines for reference. 905 title : string 906 Optional title of the figure. 907 """ 908 if self.N != 1: 909 raise ValueError("Correlator must be projected before plotting") 910 911 if auto_gamma: 912 self.gamma_method() 913 914 if x_range is None: 915 x_range = [0, self.T - 1] 916 917 fig = plt.figure() 918 ax1 = fig.add_subplot(111) 919 920 x, y, y_err = self.plottable() 921 if hide_sigma: 922 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 923 else: 924 hide_from = None 925 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag) 926 if logscale: 927 ax1.set_yscale('log') 928 else: 929 if y_range is None: 930 try: 931 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)]) 932 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)]) 933 ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)]) 934 except Exception: 935 pass 936 else: 937 ax1.set_ylim(y_range) 938 if comp: 939 if isinstance(comp, (Corr, list)): 940 for corr in comp if isinstance(comp, list) else [comp]: 941 if auto_gamma: 942 corr.gamma_method() 943 x, y, y_err = corr.plottable() 944 if hide_sigma: 945 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 946 else: 947 hide_from = None 948 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor']) 949 else: 950 raise TypeError("'comp' must be a correlator or a list of correlators.") 951 952 if plateau: 953 if isinstance(plateau, Obs): 954 if auto_gamma: 955 plateau.gamma_method() 956 ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau)) 957 ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-') 958 else: 959 raise TypeError("'plateau' must be an Obs") 960 961 if references: 962 if isinstance(references, list): 963 for ref in references: 964 ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--') 965 else: 966 raise TypeError("'references' must be a list of floating pint values.") 967 968 if self.prange: 969 ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',', color="black", zorder=0) 970 ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',', color="black", zorder=0) 971 972 if fit_res: 973 x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05) 974 if isinstance(fit_res.fit_function, dict): 975 if fit_key: 976 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) 977 else: 978 raise ValueError("Please provide a 'fit_key' for visualizing combined fits.") 979 else: 980 ax1.plot(x_samples, fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2) 981 982 ax1.set_xlabel(r'$x_0 / a$') 983 if ylabel: 984 ax1.set_ylabel(ylabel) 985 ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5]) 986 987 _handles, labels = ax1.get_legend_handles_labels() 988 if labels: 989 ax1.legend() 990 991 if title: 992 plt.title(title) 993 994 plt.draw() 995 996 if save: 997 if isinstance(save, str): 998 fig.savefig(save, bbox_inches='tight') 999 else: 1000 raise TypeError("'save' has to be a string.") 1001 1002 def spaghetti_plot(self, logscale=True): 1003 """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations. 1004 1005 Parameters 1006 ---------- 1007 logscale : bool 1008 Determines whether the scale of the y-axis is logarithmic or standard. 1009 """ 1010 if self.N != 1: 1011 raise ValueError("Correlator needs to be projected first.") 1012 1013 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])) 1014 x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content, strict=True) if o is not None] 1015 1016 for name in mc_names: 1017 data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T 1018 1019 fig = plt.figure() 1020 ax = fig.add_subplot(111) 1021 for dat in data: 1022 ax.plot(x0_vals, dat, ls='-', marker='') 1023 1024 if logscale is True: 1025 ax.set_yscale('log') 1026 1027 ax.set_xlabel(r'$x_0 / a$') 1028 plt.title(name) 1029 plt.draw() 1030 1031 def dump(self, filename, datatype="json.gz", **kwargs): 1032 """Dumps the Corr into a file of chosen type 1033 Parameters 1034 ---------- 1035 filename : str 1036 Name of the file to be saved. 1037 datatype : str 1038 Format of the exported file. Supported formats include 1039 "json.gz" and "pickle" 1040 path : str 1041 specifies a custom path for the file (default '.') 1042 """ 1043 if datatype == "json.gz": 1044 from .input.json import dump_to_json 1045 if 'path' in kwargs: 1046 file_name = kwargs.get('path') + '/' + filename 1047 else: 1048 file_name = filename 1049 dump_to_json(self, file_name) 1050 elif datatype == "pickle": 1051 dump_object(self, filename, **kwargs) 1052 else: 1053 raise ValueError("Unknown datatype " + str(datatype)) 1054 1055 def print(self, print_range=None): 1056 print(self.__repr__(print_range)) 1057 1058 def __repr__(self, print_range=None): 1059 if print_range is None: 1060 print_range = [0, None] 1061 1062 content_string = "" 1063 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 1064 1065 if self.tag is not None: 1066 content_string += "Description: " + self.tag + "\n" 1067 if self.N != 1: 1068 return content_string 1069 1070 if print_range[1]: 1071 print_range[1] += 1 1072 content_string += 'x0/a\tCorr(x0/a)\n------------------\n' 1073 for i, sub_corr in enumerate(self.content[print_range[0]:print_range[1]]): 1074 if sub_corr is None: 1075 content_string += str(i + print_range[0]) + '\n' 1076 else: 1077 content_string += str(i + print_range[0]) 1078 for element in sub_corr: 1079 content_string += f"\t{element:+2}" 1080 content_string += '\n' 1081 return content_string 1082 1083 def __str__(self): 1084 return self.__repr__() 1085 1086 # We define the basic operations, that can be performed with correlators. 1087 # While */+- get defined here, they only work for Corr*Obs and not Obs*Corr. 1088 # This is because Obs*Corr checks Obs.__mul__ first and does not catch an exception. 1089 # One could try and tell Obs to check if the y in __mul__ is a Corr and 1090 1091 __array_priority__ = 10000 1092 1093 def __eq__(self, y): 1094 if isinstance(y, Corr): 1095 comp = np.asarray(y.content, dtype=object) 1096 else: 1097 comp = np.asarray(y) 1098 return np.asarray(self.content, dtype=object) == comp 1099 1100 __hash__ = None 1101 1102 def __add__(self, y): 1103 if isinstance(y, Corr): 1104 if ((self.N != y.N) or (self.T != y.T)): 1105 raise ValueError("Addition of Corrs with different shape") 1106 newcontent = [] 1107 for t in range(self.T): 1108 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1109 newcontent.append(None) 1110 else: 1111 newcontent.append(self.content[t] + y.content[t]) 1112 return Corr(newcontent) 1113 1114 elif isinstance(y, (Obs, int, float, CObs, complex)): 1115 newcontent = [] 1116 for t in range(self.T): 1117 if _check_for_none(self, self.content[t]): 1118 newcontent.append(None) 1119 else: 1120 newcontent.append(self.content[t] + y) 1121 return Corr(newcontent, prange=self.prange) 1122 elif isinstance(y, np.ndarray): 1123 if y.shape == (self.T,): 1124 return Corr(list((np.array(self.content).T + y).T)) 1125 else: 1126 raise ValueError("operands could not be broadcast together") 1127 else: 1128 raise TypeError("Corr + wrong type") 1129 1130 def __mul__(self, y): 1131 if isinstance(y, Corr): 1132 if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): 1133 raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T") 1134 newcontent = [] 1135 for t in range(self.T): 1136 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1137 newcontent.append(None) 1138 else: 1139 newcontent.append(self.content[t] * y.content[t]) 1140 return Corr(newcontent) 1141 1142 elif isinstance(y, (Obs, int, float, CObs, complex)): 1143 newcontent = [] 1144 for t in range(self.T): 1145 if _check_for_none(self, self.content[t]): 1146 newcontent.append(None) 1147 else: 1148 newcontent.append(self.content[t] * y) 1149 return Corr(newcontent, prange=self.prange) 1150 elif isinstance(y, np.ndarray): 1151 if y.shape == (self.T,): 1152 return Corr(list((np.array(self.content).T * y).T)) 1153 else: 1154 raise ValueError("operands could not be broadcast together") 1155 else: 1156 raise TypeError("Corr * wrong type") 1157 1158 def __matmul__(self, y): 1159 if isinstance(y, np.ndarray): 1160 if y.ndim != 2 or y.shape[0] != y.shape[1]: 1161 raise ValueError("Can only multiply correlators by square matrices.") 1162 if not self.N == y.shape[0]: 1163 raise ValueError("matmul: mismatch of matrix dimensions") 1164 newcontent = [] 1165 for t in range(self.T): 1166 if _check_for_none(self, self.content[t]): 1167 newcontent.append(None) 1168 else: 1169 newcontent.append(self.content[t] @ y) 1170 return Corr(newcontent) 1171 elif isinstance(y, Corr): 1172 if not self.N == y.N: 1173 raise ValueError("matmul: mismatch of matrix dimensions") 1174 newcontent = [] 1175 for t in range(self.T): 1176 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1177 newcontent.append(None) 1178 else: 1179 newcontent.append(self.content[t] @ y.content[t]) 1180 return Corr(newcontent) 1181 1182 else: 1183 return NotImplemented 1184 1185 def __rmatmul__(self, y): 1186 if isinstance(y, np.ndarray): 1187 if y.ndim != 2 or y.shape[0] != y.shape[1]: 1188 raise ValueError("Can only multiply correlators by square matrices.") 1189 if not self.N == y.shape[0]: 1190 raise ValueError("matmul: mismatch of matrix dimensions") 1191 newcontent = [] 1192 for t in range(self.T): 1193 if _check_for_none(self, self.content[t]): 1194 newcontent.append(None) 1195 else: 1196 newcontent.append(y @ self.content[t]) 1197 return Corr(newcontent) 1198 else: 1199 return NotImplemented 1200 1201 def __truediv__(self, y): 1202 if isinstance(y, Corr): 1203 if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): 1204 raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T") 1205 newcontent = [] 1206 for t in range(self.T): 1207 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1208 newcontent.append(None) 1209 else: 1210 newcontent.append(self.content[t] / y.content[t]) 1211 for t in range(self.T): 1212 if _check_for_none(self, newcontent[t]): 1213 continue 1214 if np.isnan(np.sum(newcontent[t]).value): 1215 newcontent[t] = None 1216 1217 if all([item is None for item in newcontent]): 1218 raise ValueError("Division returns completely undefined correlator") 1219 return Corr(newcontent) 1220 1221 elif isinstance(y, (Obs, CObs)): 1222 if isinstance(y, Obs): 1223 if y.value == 0: 1224 raise ValueError('Division by zero will return undefined correlator') 1225 if isinstance(y, CObs): 1226 if y.is_zero(): 1227 raise ValueError('Division by zero will return undefined correlator') 1228 1229 newcontent = [] 1230 for t in range(self.T): 1231 if _check_for_none(self, self.content[t]): 1232 newcontent.append(None) 1233 else: 1234 newcontent.append(self.content[t] / y) 1235 return Corr(newcontent, prange=self.prange) 1236 1237 elif isinstance(y, (int, float)): 1238 if y == 0: 1239 raise ValueError('Division by zero will return undefined correlator') 1240 newcontent = [] 1241 for t in range(self.T): 1242 if _check_for_none(self, self.content[t]): 1243 newcontent.append(None) 1244 else: 1245 newcontent.append(self.content[t] / y) 1246 return Corr(newcontent, prange=self.prange) 1247 elif isinstance(y, np.ndarray): 1248 if y.shape == (self.T,): 1249 return Corr(list((np.array(self.content).T / y).T)) 1250 else: 1251 raise ValueError("operands could not be broadcast together") 1252 else: 1253 raise TypeError('Corr / wrong type') 1254 1255 def __neg__(self): 1256 newcontent = [None if _check_for_none(self, item) else -1. * item for item in self.content] 1257 return Corr(newcontent, prange=self.prange) 1258 1259 def __sub__(self, y): 1260 return self + (-y) 1261 1262 def __pow__(self, y): 1263 if isinstance(y, (Obs, int, float, CObs)): 1264 newcontent = [None if _check_for_none(self, item) else item**y for item in self.content] 1265 return Corr(newcontent, prange=self.prange) 1266 else: 1267 raise TypeError('Type of exponent not supported') 1268 1269 def __abs__(self): 1270 newcontent = [None if _check_for_none(self, item) else np.abs(item) for item in self.content] 1271 return Corr(newcontent, prange=self.prange) 1272 1273 # The numpy functions: 1274 def sqrt(self): 1275 return self ** 0.5 1276 1277 def log(self): 1278 newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content] 1279 return Corr(newcontent, prange=self.prange) 1280 1281 def exp(self): 1282 newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content] 1283 return Corr(newcontent, prange=self.prange) 1284 1285 def _apply_func_to_corr(self, func): 1286 newcontent = [None if _check_for_none(self, item) else func(item) for item in self.content] 1287 for t in range(self.T): 1288 if _check_for_none(self, newcontent[t]): 1289 continue 1290 tmp_sum = np.sum(newcontent[t]) 1291 if hasattr(tmp_sum, "value"): 1292 if np.isnan(tmp_sum.value): 1293 newcontent[t] = None 1294 if all([item is None for item in newcontent]): 1295 raise ValueError('Operation returns undefined correlator') 1296 return Corr(newcontent) 1297 1298 def sin(self): 1299 return self._apply_func_to_corr(np.sin) 1300 1301 def cos(self): 1302 return self._apply_func_to_corr(np.cos) 1303 1304 def tan(self): 1305 return self._apply_func_to_corr(np.tan) 1306 1307 def sinh(self): 1308 return self._apply_func_to_corr(np.sinh) 1309 1310 def cosh(self): 1311 return self._apply_func_to_corr(np.cosh) 1312 1313 def tanh(self): 1314 return self._apply_func_to_corr(np.tanh) 1315 1316 def arcsin(self): 1317 return self._apply_func_to_corr(np.arcsin) 1318 1319 def arccos(self): 1320 return self._apply_func_to_corr(np.arccos) 1321 1322 def arctan(self): 1323 return self._apply_func_to_corr(np.arctan) 1324 1325 def arcsinh(self): 1326 return self._apply_func_to_corr(np.arcsinh) 1327 1328 def arccosh(self): 1329 return self._apply_func_to_corr(np.arccosh) 1330 1331 def arctanh(self): 1332 return self._apply_func_to_corr(np.arctanh) 1333 1334 # Right hand side operations (require tweak in main module to work) 1335 def __radd__(self, y): 1336 return self + y 1337 1338 def __rsub__(self, y): 1339 return -self + y 1340 1341 def __rmul__(self, y): 1342 return self * y 1343 1344 def __rtruediv__(self, y): 1345 return (self / y) ** (-1) 1346 1347 @property 1348 def real(self): 1349 def return_real(obs_OR_cobs): 1350 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1351 return np.vectorize(lambda x: x.real)(obs_OR_cobs) 1352 else: 1353 return obs_OR_cobs 1354 1355 return self._apply_func_to_corr(return_real) 1356 1357 @property 1358 def imag(self): 1359 def return_imag(obs_OR_cobs): 1360 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1361 return np.vectorize(lambda x: x.imag)(obs_OR_cobs) 1362 else: 1363 return obs_OR_cobs * 0 # So it stays the right type 1364 1365 return self._apply_func_to_corr(return_imag) 1366 1367 def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None): 1368 r''' Project large correlation matrix to lowest states 1369 1370 This method can be used to reduce the size of an (N x N) correlation matrix 1371 to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise 1372 is still small. 1373 1374 Parameters 1375 ---------- 1376 Ntrunc: int 1377 Rank of the target matrix. 1378 tproj: int 1379 Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method. 1380 The default value is 3. 1381 t0proj: int 1382 Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly 1383 discouraged for O(a) improved theories, since the correctness of the procedure 1384 cannot be granted in this case. The default value is 2. 1385 basematrix : Corr 1386 Correlation matrix that is used to determine the eigenvectors of the 1387 lowest states based on a GEVP. basematrix is taken to be the Corr itself if 1388 is is not specified. 1389 1390 Notes 1391 ----- 1392 We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving 1393 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}$ 1394 and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the 1395 resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via 1396 $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large 1397 correlation matrix and to remove some noise that is added by irrelevant operators. 1398 This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated 1399 bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$. 1400 ''' 1401 1402 if self.N == 1: 1403 raise ValueError('Method cannot be applied to one-dimensional correlators.') 1404 if basematrix is None: 1405 basematrix = self 1406 if Ntrunc >= basematrix.N: 1407 raise ValueError(f'Cannot truncate using Ntrunc >= {basematrix.N}') 1408 if basematrix.N != self.N: 1409 raise ValueError('basematrix and targetmatrix have to be of the same size.') 1410 1411 evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc] 1412 1413 tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object) 1414 rmat = [] 1415 for t in range(basematrix.T): 1416 if self.content[t] is None: 1417 rmat.append(None) 1418 else: 1419 for i in range(Ntrunc): 1420 for j in range(Ntrunc): 1421 tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j] 1422 rmat.append(np.copy(tmpmat)) 1423 1424 return Corr(rmat) 1425 1426 1427def _sort_vectors(vec_set_in, ts): 1428 """Helper function used to find a set of Eigenvectors consistent over all timeslices""" 1429 1430 if isinstance(vec_set_in[ts][0][0], Obs): 1431 vec_set = [anp.vectorize(float)(vi) if vi is not None else vi for vi in vec_set_in] 1432 else: 1433 vec_set = vec_set_in 1434 reference_sorting = np.array(vec_set[ts]) 1435 N = reference_sorting.shape[0] 1436 sorted_vec_set = [] 1437 for t in range(len(vec_set)): 1438 if vec_set[t] is None: 1439 sorted_vec_set.append(None) 1440 elif not t == ts: 1441 perms = [list(o) for o in permutations([i for i in range(N)], N)] 1442 best_score = 0 1443 for perm in perms: 1444 current_score = 1 1445 for k in range(N): 1446 new_sorting = reference_sorting.copy() 1447 new_sorting[perm[k], :] = vec_set[t][k] 1448 current_score *= abs(np.linalg.det(new_sorting)) 1449 if current_score > best_score: 1450 best_score = current_score 1451 best_perm = perm 1452 sorted_vec_set.append([vec_set_in[t][k] for k in best_perm]) 1453 else: 1454 sorted_vec_set.append(vec_set_in[t]) 1455 1456 return sorted_vec_set 1457 1458 1459def _check_for_none(corr, entry): 1460 """Checks if entry for correlator corr is None""" 1461 return len(list(filter(None, np.asarray(entry).flatten()))) < corr.N ** 2 1462 1463 1464def _GEVP_solver(Gt, G0, method='eigh', chol_inv=None): 1465 r"""Helper function for solving the GEVP and sorting the eigenvectors. 1466 1467 Solves $G(t)v_i=\lambda_i G(t_0)v_i$ and returns the eigenvectors v_i 1468 1469 The helper function assumes that both provided matrices are symmetric and 1470 only processes the lower triangular part of both matrices. In case the matrices 1471 are not symmetric the upper triangular parts are effectively discarded. 1472 1473 Parameters 1474 ---------- 1475 Gt : array 1476 The correlator at time t for the left hand side of the GEVP 1477 G0 : array 1478 The correlator at time t0 for the right hand side of the GEVP 1479 Method used to solve the GEVP. 1480 - "eigh": Use scipy.linalg.eigh to solve the GEVP. 1481 - "cholesky": Use manually implemented solution via the Cholesky decomposition. 1482 chol_inv : array, optional 1483 Inverse of the Cholesky decomposition of G0. May be provided to 1484 speed up the computation in the case of method=='cholesky' 1485 1486 """ 1487 if isinstance(G0[0][0], Obs): 1488 vector_obs = True 1489 else: 1490 vector_obs = False 1491 1492 if method == 'cholesky': 1493 if vector_obs: 1494 cholesky = linalg.cholesky 1495 inv = linalg.inv 1496 eigv = linalg.eigv 1497 matmul = linalg.matmul 1498 else: 1499 cholesky = np.linalg.cholesky 1500 inv = np.linalg.inv 1501 1502 def eigv(x, **kwargs): 1503 return np.linalg.eigh(x)[1] 1504 1505 def matmul(*operands): 1506 return np.linalg.multi_dot(operands) 1507 N = Gt.shape[0] 1508 output = [[] for j in range(N)] 1509 if chol_inv is None: 1510 chol = cholesky(G0) # This will automatically report if the matrix is not pos-def 1511 chol_inv = inv(chol) 1512 1513 try: 1514 new_matrix = matmul(chol_inv, Gt, chol_inv.T) 1515 ev = eigv(new_matrix) 1516 ev = matmul(chol_inv.T, ev) 1517 output = np.flip(ev, axis=1).T 1518 except (np.linalg.LinAlgError, TypeError, ValueError): # The above code can fail because of linalg-errors or because the entry of the corr is None 1519 for s in range(N): 1520 output[s] = None 1521 return output 1522 elif method == 'eigh': 1523 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): 732 newcontent.append(None) 733 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 734 newcontent.append(None) 735 else: 736 newcontent.append(self.content[t] / self.content[t + 1]) 737 if (all([x is None for x in newcontent])): 738 raise ValueError('m_eff is undefined at all timeslices') 739 740 return np.log(Corr(newcontent, padding=[0, 1])) 741 742 elif variant == 'logsym': 743 newcontent = [] 744 for t in range(1, self.T - 1): 745 if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 746 newcontent.append(None) 747 elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0: 748 newcontent.append(None) 749 else: 750 newcontent.append(self.content[t - 1] / self.content[t + 1]) 751 if (all([x is None for x in newcontent])): 752 raise ValueError('m_eff is undefined at all timeslices') 753 754 return np.log(Corr(newcontent, padding=[1, 1])) / 2 755 756 elif variant in ['periodic', 'cosh', 'sinh']: 757 if variant in ['periodic', 'cosh']: 758 func = anp.cosh 759 else: 760 func = anp.sinh 761 762 def root_function(x, d): 763 return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d 764 765 newcontent = [] 766 for t in range(self.T - 1): 767 if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0): 768 newcontent.append(None) 769 # Fill the two timeslices in the middle of the lattice with their predecessors 770 elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]: 771 newcontent.append(newcontent[-1]) 772 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 773 newcontent.append(None) 774 else: 775 newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess))) 776 if (all([x is None for x in newcontent])): 777 raise ValueError('m_eff is undefined at all timeslices') 778 779 return Corr(newcontent, padding=[0, 1]) 780 781 elif variant == 'arccosh': 782 newcontent = [] 783 for t in range(1, self.T - 1): 784 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): 785 newcontent.append(None) 786 else: 787 newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t])) 788 if (all([x is None for x in newcontent])): 789 raise ValueError("m_eff is undefined at all timeslices") 790 return np.arccosh(Corr(newcontent, padding=[1, 1])) 791 792 else: 793 raise ValueError('Unknown variant.') 794 795 def fit(self, function, fitrange=None, silent=False, **kwargs): 796 r'''Fits function to the data 797 798 Parameters 799 ---------- 800 function : obj 801 function to fit to the data. See fits.least_squares for details. 802 fitrange : list 803 Two element list containing the timeslices on which the fit is supposed to start and stop. 804 Caution: This range is inclusive as opposed to standard python indexing. 805 `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6. 806 If not specified, self.prange or all timeslices are used. 807 silent : bool 808 Decides whether output is printed to the standard output. 809 ''' 810 if self.N != 1: 811 raise ValueError("Correlator must be projected before fitting") 812 813 if fitrange is None: 814 if self.prange: 815 fitrange = self.prange 816 else: 817 fitrange = [0, self.T - 1] 818 else: 819 if not isinstance(fitrange, list): 820 raise TypeError("fitrange has to be a list with two elements") 821 if len(fitrange) != 2: 822 raise ValueError("fitrange has to have exactly two elements [fit_start, fit_stop]") 823 824 xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None]) 825 ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None]) 826 result = least_squares(xs, ys, function, silent=silent, **kwargs) 827 return result 828 829 def plateau(self, plateau_range=None, method="fit", auto_gamma=False): 830 """ Extract a plateau value from a Corr object 831 832 Parameters 833 ---------- 834 plateau_range : list 835 list with two entries, indicating the first and the last timeslice 836 of the plateau region. 837 method : str 838 method to extract the plateau. 839 'fit' fits a constant to the plateau region 840 'avg', 'average' or 'mean' just average over the given timeslices. 841 auto_gamma : bool 842 apply gamma_method with default parameters to the Corr. Defaults to None 843 """ 844 if not plateau_range: 845 if self.prange: 846 plateau_range = self.prange 847 else: 848 raise ValueError("no plateau range provided") 849 if self.N != 1: 850 raise ValueError("Correlator must be projected before getting a plateau.") 851 if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])): 852 raise ValueError("plateau is undefined at all timeslices in plateaurange.") 853 if auto_gamma: 854 self.gamma_method() 855 if method == "fit": 856 def const_func(a, t): 857 return a[0] 858 return self.fit(const_func, plateau_range)[0] 859 elif method in ["avg", "average", "mean"]: 860 returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None]) 861 return returnvalue 862 863 else: 864 raise ValueError("Unsupported plateau method: " + method) 865 866 def set_prange(self, prange): 867 """Sets the attribute prange of the Corr object.""" 868 if not len(prange) == 2: 869 raise ValueError("prange must be a list or array with two values") 870 if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))): 871 raise TypeError("Start and end point must be integers") 872 if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] <= prange[1]): 873 raise ValueError("Start and end point must define a range in the interval 0,T") 874 875 self.prange = prange 876 return 877 878 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): 879 """Plots the correlator using the tag of the correlator as label if available. 880 881 Parameters 882 ---------- 883 x_range : list 884 list of two values, determining the range of the x-axis e.g. [4, 8]. 885 comp : Corr or list of Corr 886 Correlator or list of correlators which are plotted for comparison. 887 The tags of these correlators are used as labels if available. 888 logscale : bool 889 Sets y-axis to logscale. 890 plateau : Obs 891 Plateau value to be visualized in the figure. 892 fit_res : Fit_result 893 Fit_result object to be visualized. 894 fit_key : str 895 Key for the fit function in Fit_result.fit_function (for combined fits). 896 ylabel : str 897 Label for the y-axis. 898 save : str 899 path to file in which the figure should be saved. 900 auto_gamma : bool 901 Apply the gamma method with standard parameters to all correlators and plateau values before plotting. 902 hide_sigma : float 903 Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors. 904 references : list 905 List of floating point values that are displayed as horizontal lines for reference. 906 title : string 907 Optional title of the figure. 908 """ 909 if self.N != 1: 910 raise ValueError("Correlator must be projected before plotting") 911 912 if auto_gamma: 913 self.gamma_method() 914 915 if x_range is None: 916 x_range = [0, self.T - 1] 917 918 fig = plt.figure() 919 ax1 = fig.add_subplot(111) 920 921 x, y, y_err = self.plottable() 922 if hide_sigma: 923 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 924 else: 925 hide_from = None 926 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag) 927 if logscale: 928 ax1.set_yscale('log') 929 else: 930 if y_range is None: 931 try: 932 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)]) 933 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)]) 934 ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)]) 935 except Exception: 936 pass 937 else: 938 ax1.set_ylim(y_range) 939 if comp: 940 if isinstance(comp, (Corr, list)): 941 for corr in comp if isinstance(comp, list) else [comp]: 942 if auto_gamma: 943 corr.gamma_method() 944 x, y, y_err = corr.plottable() 945 if hide_sigma: 946 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 947 else: 948 hide_from = None 949 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor']) 950 else: 951 raise TypeError("'comp' must be a correlator or a list of correlators.") 952 953 if plateau: 954 if isinstance(plateau, Obs): 955 if auto_gamma: 956 plateau.gamma_method() 957 ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau)) 958 ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-') 959 else: 960 raise TypeError("'plateau' must be an Obs") 961 962 if references: 963 if isinstance(references, list): 964 for ref in references: 965 ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--') 966 else: 967 raise TypeError("'references' must be a list of floating pint values.") 968 969 if self.prange: 970 ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',', color="black", zorder=0) 971 ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',', color="black", zorder=0) 972 973 if fit_res: 974 x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05) 975 if isinstance(fit_res.fit_function, dict): 976 if fit_key: 977 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) 978 else: 979 raise ValueError("Please provide a 'fit_key' for visualizing combined fits.") 980 else: 981 ax1.plot(x_samples, fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2) 982 983 ax1.set_xlabel(r'$x_0 / a$') 984 if ylabel: 985 ax1.set_ylabel(ylabel) 986 ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5]) 987 988 _handles, labels = ax1.get_legend_handles_labels() 989 if labels: 990 ax1.legend() 991 992 if title: 993 plt.title(title) 994 995 plt.draw() 996 997 if save: 998 if isinstance(save, str): 999 fig.savefig(save, bbox_inches='tight') 1000 else: 1001 raise TypeError("'save' has to be a string.") 1002 1003 def spaghetti_plot(self, logscale=True): 1004 """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations. 1005 1006 Parameters 1007 ---------- 1008 logscale : bool 1009 Determines whether the scale of the y-axis is logarithmic or standard. 1010 """ 1011 if self.N != 1: 1012 raise ValueError("Correlator needs to be projected first.") 1013 1014 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])) 1015 x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content, strict=True) if o is not None] 1016 1017 for name in mc_names: 1018 data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T 1019 1020 fig = plt.figure() 1021 ax = fig.add_subplot(111) 1022 for dat in data: 1023 ax.plot(x0_vals, dat, ls='-', marker='') 1024 1025 if logscale is True: 1026 ax.set_yscale('log') 1027 1028 ax.set_xlabel(r'$x_0 / a$') 1029 plt.title(name) 1030 plt.draw() 1031 1032 def dump(self, filename, datatype="json.gz", **kwargs): 1033 """Dumps the Corr into a file of chosen type 1034 Parameters 1035 ---------- 1036 filename : str 1037 Name of the file to be saved. 1038 datatype : str 1039 Format of the exported file. Supported formats include 1040 "json.gz" and "pickle" 1041 path : str 1042 specifies a custom path for the file (default '.') 1043 """ 1044 if datatype == "json.gz": 1045 from .input.json import dump_to_json 1046 if 'path' in kwargs: 1047 file_name = kwargs.get('path') + '/' + filename 1048 else: 1049 file_name = filename 1050 dump_to_json(self, file_name) 1051 elif datatype == "pickle": 1052 dump_object(self, filename, **kwargs) 1053 else: 1054 raise ValueError("Unknown datatype " + str(datatype)) 1055 1056 def print(self, print_range=None): 1057 print(self.__repr__(print_range)) 1058 1059 def __repr__(self, print_range=None): 1060 if print_range is None: 1061 print_range = [0, None] 1062 1063 content_string = "" 1064 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 1065 1066 if self.tag is not None: 1067 content_string += "Description: " + self.tag + "\n" 1068 if self.N != 1: 1069 return content_string 1070 1071 if print_range[1]: 1072 print_range[1] += 1 1073 content_string += 'x0/a\tCorr(x0/a)\n------------------\n' 1074 for i, sub_corr in enumerate(self.content[print_range[0]:print_range[1]]): 1075 if sub_corr is None: 1076 content_string += str(i + print_range[0]) + '\n' 1077 else: 1078 content_string += str(i + print_range[0]) 1079 for element in sub_corr: 1080 content_string += f"\t{element:+2}" 1081 content_string += '\n' 1082 return content_string 1083 1084 def __str__(self): 1085 return self.__repr__() 1086 1087 # We define the basic operations, that can be performed with correlators. 1088 # While */+- get defined here, they only work for Corr*Obs and not Obs*Corr. 1089 # This is because Obs*Corr checks Obs.__mul__ first and does not catch an exception. 1090 # One could try and tell Obs to check if the y in __mul__ is a Corr and 1091 1092 __array_priority__ = 10000 1093 1094 def __eq__(self, y): 1095 if isinstance(y, Corr): 1096 comp = np.asarray(y.content, dtype=object) 1097 else: 1098 comp = np.asarray(y) 1099 return np.asarray(self.content, dtype=object) == comp 1100 1101 __hash__ = None 1102 1103 def __add__(self, y): 1104 if isinstance(y, Corr): 1105 if ((self.N != y.N) or (self.T != y.T)): 1106 raise ValueError("Addition of Corrs with different shape") 1107 newcontent = [] 1108 for t in range(self.T): 1109 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1110 newcontent.append(None) 1111 else: 1112 newcontent.append(self.content[t] + y.content[t]) 1113 return Corr(newcontent) 1114 1115 elif isinstance(y, (Obs, int, float, CObs, complex)): 1116 newcontent = [] 1117 for t in range(self.T): 1118 if _check_for_none(self, self.content[t]): 1119 newcontent.append(None) 1120 else: 1121 newcontent.append(self.content[t] + y) 1122 return Corr(newcontent, prange=self.prange) 1123 elif isinstance(y, np.ndarray): 1124 if y.shape == (self.T,): 1125 return Corr(list((np.array(self.content).T + y).T)) 1126 else: 1127 raise ValueError("operands could not be broadcast together") 1128 else: 1129 raise TypeError("Corr + wrong type") 1130 1131 def __mul__(self, y): 1132 if isinstance(y, Corr): 1133 if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): 1134 raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T") 1135 newcontent = [] 1136 for t in range(self.T): 1137 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1138 newcontent.append(None) 1139 else: 1140 newcontent.append(self.content[t] * y.content[t]) 1141 return Corr(newcontent) 1142 1143 elif isinstance(y, (Obs, int, float, CObs, complex)): 1144 newcontent = [] 1145 for t in range(self.T): 1146 if _check_for_none(self, self.content[t]): 1147 newcontent.append(None) 1148 else: 1149 newcontent.append(self.content[t] * y) 1150 return Corr(newcontent, prange=self.prange) 1151 elif isinstance(y, np.ndarray): 1152 if y.shape == (self.T,): 1153 return Corr(list((np.array(self.content).T * y).T)) 1154 else: 1155 raise ValueError("operands could not be broadcast together") 1156 else: 1157 raise TypeError("Corr * wrong type") 1158 1159 def __matmul__(self, y): 1160 if isinstance(y, np.ndarray): 1161 if y.ndim != 2 or y.shape[0] != y.shape[1]: 1162 raise ValueError("Can only multiply correlators by square matrices.") 1163 if not self.N == y.shape[0]: 1164 raise ValueError("matmul: mismatch of matrix dimensions") 1165 newcontent = [] 1166 for t in range(self.T): 1167 if _check_for_none(self, self.content[t]): 1168 newcontent.append(None) 1169 else: 1170 newcontent.append(self.content[t] @ y) 1171 return Corr(newcontent) 1172 elif isinstance(y, Corr): 1173 if not self.N == y.N: 1174 raise ValueError("matmul: mismatch of matrix dimensions") 1175 newcontent = [] 1176 for t in range(self.T): 1177 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1178 newcontent.append(None) 1179 else: 1180 newcontent.append(self.content[t] @ y.content[t]) 1181 return Corr(newcontent) 1182 1183 else: 1184 return NotImplemented 1185 1186 def __rmatmul__(self, y): 1187 if isinstance(y, np.ndarray): 1188 if y.ndim != 2 or y.shape[0] != y.shape[1]: 1189 raise ValueError("Can only multiply correlators by square matrices.") 1190 if not self.N == y.shape[0]: 1191 raise ValueError("matmul: mismatch of matrix dimensions") 1192 newcontent = [] 1193 for t in range(self.T): 1194 if _check_for_none(self, self.content[t]): 1195 newcontent.append(None) 1196 else: 1197 newcontent.append(y @ self.content[t]) 1198 return Corr(newcontent) 1199 else: 1200 return NotImplemented 1201 1202 def __truediv__(self, y): 1203 if isinstance(y, Corr): 1204 if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): 1205 raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T") 1206 newcontent = [] 1207 for t in range(self.T): 1208 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1209 newcontent.append(None) 1210 else: 1211 newcontent.append(self.content[t] / y.content[t]) 1212 for t in range(self.T): 1213 if _check_for_none(self, newcontent[t]): 1214 continue 1215 if np.isnan(np.sum(newcontent[t]).value): 1216 newcontent[t] = None 1217 1218 if all([item is None for item in newcontent]): 1219 raise ValueError("Division returns completely undefined correlator") 1220 return Corr(newcontent) 1221 1222 elif isinstance(y, (Obs, CObs)): 1223 if isinstance(y, Obs): 1224 if y.value == 0: 1225 raise ValueError('Division by zero will return undefined correlator') 1226 if isinstance(y, CObs): 1227 if y.is_zero(): 1228 raise ValueError('Division by zero will return undefined correlator') 1229 1230 newcontent = [] 1231 for t in range(self.T): 1232 if _check_for_none(self, self.content[t]): 1233 newcontent.append(None) 1234 else: 1235 newcontent.append(self.content[t] / y) 1236 return Corr(newcontent, prange=self.prange) 1237 1238 elif isinstance(y, (int, float)): 1239 if y == 0: 1240 raise ValueError('Division by zero will return undefined correlator') 1241 newcontent = [] 1242 for t in range(self.T): 1243 if _check_for_none(self, self.content[t]): 1244 newcontent.append(None) 1245 else: 1246 newcontent.append(self.content[t] / y) 1247 return Corr(newcontent, prange=self.prange) 1248 elif isinstance(y, np.ndarray): 1249 if y.shape == (self.T,): 1250 return Corr(list((np.array(self.content).T / y).T)) 1251 else: 1252 raise ValueError("operands could not be broadcast together") 1253 else: 1254 raise TypeError('Corr / wrong type') 1255 1256 def __neg__(self): 1257 newcontent = [None if _check_for_none(self, item) else -1. * item for item in self.content] 1258 return Corr(newcontent, prange=self.prange) 1259 1260 def __sub__(self, y): 1261 return self + (-y) 1262 1263 def __pow__(self, y): 1264 if isinstance(y, (Obs, int, float, CObs)): 1265 newcontent = [None if _check_for_none(self, item) else item**y for item in self.content] 1266 return Corr(newcontent, prange=self.prange) 1267 else: 1268 raise TypeError('Type of exponent not supported') 1269 1270 def __abs__(self): 1271 newcontent = [None if _check_for_none(self, item) else np.abs(item) for item in self.content] 1272 return Corr(newcontent, prange=self.prange) 1273 1274 # The numpy functions: 1275 def sqrt(self): 1276 return self ** 0.5 1277 1278 def log(self): 1279 newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content] 1280 return Corr(newcontent, prange=self.prange) 1281 1282 def exp(self): 1283 newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content] 1284 return Corr(newcontent, prange=self.prange) 1285 1286 def _apply_func_to_corr(self, func): 1287 newcontent = [None if _check_for_none(self, item) else func(item) for item in self.content] 1288 for t in range(self.T): 1289 if _check_for_none(self, newcontent[t]): 1290 continue 1291 tmp_sum = np.sum(newcontent[t]) 1292 if hasattr(tmp_sum, "value"): 1293 if np.isnan(tmp_sum.value): 1294 newcontent[t] = None 1295 if all([item is None for item in newcontent]): 1296 raise ValueError('Operation returns undefined correlator') 1297 return Corr(newcontent) 1298 1299 def sin(self): 1300 return self._apply_func_to_corr(np.sin) 1301 1302 def cos(self): 1303 return self._apply_func_to_corr(np.cos) 1304 1305 def tan(self): 1306 return self._apply_func_to_corr(np.tan) 1307 1308 def sinh(self): 1309 return self._apply_func_to_corr(np.sinh) 1310 1311 def cosh(self): 1312 return self._apply_func_to_corr(np.cosh) 1313 1314 def tanh(self): 1315 return self._apply_func_to_corr(np.tanh) 1316 1317 def arcsin(self): 1318 return self._apply_func_to_corr(np.arcsin) 1319 1320 def arccos(self): 1321 return self._apply_func_to_corr(np.arccos) 1322 1323 def arctan(self): 1324 return self._apply_func_to_corr(np.arctan) 1325 1326 def arcsinh(self): 1327 return self._apply_func_to_corr(np.arcsinh) 1328 1329 def arccosh(self): 1330 return self._apply_func_to_corr(np.arccosh) 1331 1332 def arctanh(self): 1333 return self._apply_func_to_corr(np.arctanh) 1334 1335 # Right hand side operations (require tweak in main module to work) 1336 def __radd__(self, y): 1337 return self + y 1338 1339 def __rsub__(self, y): 1340 return -self + y 1341 1342 def __rmul__(self, y): 1343 return self * y 1344 1345 def __rtruediv__(self, y): 1346 return (self / y) ** (-1) 1347 1348 @property 1349 def real(self): 1350 def return_real(obs_OR_cobs): 1351 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1352 return np.vectorize(lambda x: x.real)(obs_OR_cobs) 1353 else: 1354 return obs_OR_cobs 1355 1356 return self._apply_func_to_corr(return_real) 1357 1358 @property 1359 def imag(self): 1360 def return_imag(obs_OR_cobs): 1361 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1362 return np.vectorize(lambda x: x.imag)(obs_OR_cobs) 1363 else: 1364 return obs_OR_cobs * 0 # So it stays the right type 1365 1366 return self._apply_func_to_corr(return_imag) 1367 1368 def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None): 1369 r''' Project large correlation matrix to lowest states 1370 1371 This method can be used to reduce the size of an (N x N) correlation matrix 1372 to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise 1373 is still small. 1374 1375 Parameters 1376 ---------- 1377 Ntrunc: int 1378 Rank of the target matrix. 1379 tproj: int 1380 Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method. 1381 The default value is 3. 1382 t0proj: int 1383 Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly 1384 discouraged for O(a) improved theories, since the correctness of the procedure 1385 cannot be granted in this case. The default value is 2. 1386 basematrix : Corr 1387 Correlation matrix that is used to determine the eigenvectors of the 1388 lowest states based on a GEVP. basematrix is taken to be the Corr itself if 1389 is is not specified. 1390 1391 Notes 1392 ----- 1393 We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving 1394 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}$ 1395 and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the 1396 resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via 1397 $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large 1398 correlation matrix and to remove some noise that is added by irrelevant operators. 1399 This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated 1400 bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$. 1401 ''' 1402 1403 if self.N == 1: 1404 raise ValueError('Method cannot be applied to one-dimensional correlators.') 1405 if basematrix is None: 1406 basematrix = self 1407 if Ntrunc >= basematrix.N: 1408 raise ValueError(f'Cannot truncate using Ntrunc >= {basematrix.N}') 1409 if basematrix.N != self.N: 1410 raise ValueError('basematrix and targetmatrix have to be of the same size.') 1411 1412 evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc] 1413 1414 tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object) 1415 rmat = [] 1416 for t in range(basematrix.T): 1417 if self.content[t] is None: 1418 rmat.append(None) 1419 else: 1420 for i in range(Ntrunc): 1421 for j in range(Ntrunc): 1422 tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j] 1423 rmat.append(np.copy(tmpmat)) 1424 1425 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): 732 newcontent.append(None) 733 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 734 newcontent.append(None) 735 else: 736 newcontent.append(self.content[t] / self.content[t + 1]) 737 if (all([x is None for x in newcontent])): 738 raise ValueError('m_eff is undefined at all timeslices') 739 740 return np.log(Corr(newcontent, padding=[0, 1])) 741 742 elif variant == 'logsym': 743 newcontent = [] 744 for t in range(1, self.T - 1): 745 if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 746 newcontent.append(None) 747 elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0: 748 newcontent.append(None) 749 else: 750 newcontent.append(self.content[t - 1] / self.content[t + 1]) 751 if (all([x is None for x in newcontent])): 752 raise ValueError('m_eff is undefined at all timeslices') 753 754 return np.log(Corr(newcontent, padding=[1, 1])) / 2 755 756 elif variant in ['periodic', 'cosh', 'sinh']: 757 if variant in ['periodic', 'cosh']: 758 func = anp.cosh 759 else: 760 func = anp.sinh 761 762 def root_function(x, d): 763 return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d 764 765 newcontent = [] 766 for t in range(self.T - 1): 767 if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0): 768 newcontent.append(None) 769 # Fill the two timeslices in the middle of the lattice with their predecessors 770 elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]: 771 newcontent.append(newcontent[-1]) 772 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 773 newcontent.append(None) 774 else: 775 newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess))) 776 if (all([x is None for x in newcontent])): 777 raise ValueError('m_eff is undefined at all timeslices') 778 779 return Corr(newcontent, padding=[0, 1]) 780 781 elif variant == 'arccosh': 782 newcontent = [] 783 for t in range(1, self.T - 1): 784 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): 785 newcontent.append(None) 786 else: 787 newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t])) 788 if (all([x is None for x in newcontent])): 789 raise ValueError("m_eff is undefined at all timeslices") 790 return np.arccosh(Corr(newcontent, padding=[1, 1])) 791 792 else: 793 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
795 def fit(self, function, fitrange=None, silent=False, **kwargs): 796 r'''Fits function to the data 797 798 Parameters 799 ---------- 800 function : obj 801 function to fit to the data. See fits.least_squares for details. 802 fitrange : list 803 Two element list containing the timeslices on which the fit is supposed to start and stop. 804 Caution: This range is inclusive as opposed to standard python indexing. 805 `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6. 806 If not specified, self.prange or all timeslices are used. 807 silent : bool 808 Decides whether output is printed to the standard output. 809 ''' 810 if self.N != 1: 811 raise ValueError("Correlator must be projected before fitting") 812 813 if fitrange is None: 814 if self.prange: 815 fitrange = self.prange 816 else: 817 fitrange = [0, self.T - 1] 818 else: 819 if not isinstance(fitrange, list): 820 raise TypeError("fitrange has to be a list with two elements") 821 if len(fitrange) != 2: 822 raise ValueError("fitrange has to have exactly two elements [fit_start, fit_stop]") 823 824 xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None]) 825 ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None]) 826 result = least_squares(xs, ys, function, silent=silent, **kwargs) 827 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.
829 def plateau(self, plateau_range=None, method="fit", auto_gamma=False): 830 """ Extract a plateau value from a Corr object 831 832 Parameters 833 ---------- 834 plateau_range : list 835 list with two entries, indicating the first and the last timeslice 836 of the plateau region. 837 method : str 838 method to extract the plateau. 839 'fit' fits a constant to the plateau region 840 'avg', 'average' or 'mean' just average over the given timeslices. 841 auto_gamma : bool 842 apply gamma_method with default parameters to the Corr. Defaults to None 843 """ 844 if not plateau_range: 845 if self.prange: 846 plateau_range = self.prange 847 else: 848 raise ValueError("no plateau range provided") 849 if self.N != 1: 850 raise ValueError("Correlator must be projected before getting a plateau.") 851 if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])): 852 raise ValueError("plateau is undefined at all timeslices in plateaurange.") 853 if auto_gamma: 854 self.gamma_method() 855 if method == "fit": 856 def const_func(a, t): 857 return a[0] 858 return self.fit(const_func, plateau_range)[0] 859 elif method in ["avg", "average", "mean"]: 860 returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None]) 861 return returnvalue 862 863 else: 864 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
866 def set_prange(self, prange): 867 """Sets the attribute prange of the Corr object.""" 868 if not len(prange) == 2: 869 raise ValueError("prange must be a list or array with two values") 870 if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))): 871 raise TypeError("Start and end point must be integers") 872 if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] <= prange[1]): 873 raise ValueError("Start and end point must define a range in the interval 0,T") 874 875 self.prange = prange 876 return
Sets the attribute prange of the Corr object.
878 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): 879 """Plots the correlator using the tag of the correlator as label if available. 880 881 Parameters 882 ---------- 883 x_range : list 884 list of two values, determining the range of the x-axis e.g. [4, 8]. 885 comp : Corr or list of Corr 886 Correlator or list of correlators which are plotted for comparison. 887 The tags of these correlators are used as labels if available. 888 logscale : bool 889 Sets y-axis to logscale. 890 plateau : Obs 891 Plateau value to be visualized in the figure. 892 fit_res : Fit_result 893 Fit_result object to be visualized. 894 fit_key : str 895 Key for the fit function in Fit_result.fit_function (for combined fits). 896 ylabel : str 897 Label for the y-axis. 898 save : str 899 path to file in which the figure should be saved. 900 auto_gamma : bool 901 Apply the gamma method with standard parameters to all correlators and plateau values before plotting. 902 hide_sigma : float 903 Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors. 904 references : list 905 List of floating point values that are displayed as horizontal lines for reference. 906 title : string 907 Optional title of the figure. 908 """ 909 if self.N != 1: 910 raise ValueError("Correlator must be projected before plotting") 911 912 if auto_gamma: 913 self.gamma_method() 914 915 if x_range is None: 916 x_range = [0, self.T - 1] 917 918 fig = plt.figure() 919 ax1 = fig.add_subplot(111) 920 921 x, y, y_err = self.plottable() 922 if hide_sigma: 923 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 924 else: 925 hide_from = None 926 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag) 927 if logscale: 928 ax1.set_yscale('log') 929 else: 930 if y_range is None: 931 try: 932 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)]) 933 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)]) 934 ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)]) 935 except Exception: 936 pass 937 else: 938 ax1.set_ylim(y_range) 939 if comp: 940 if isinstance(comp, (Corr, list)): 941 for corr in comp if isinstance(comp, list) else [comp]: 942 if auto_gamma: 943 corr.gamma_method() 944 x, y, y_err = corr.plottable() 945 if hide_sigma: 946 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 947 else: 948 hide_from = None 949 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor']) 950 else: 951 raise TypeError("'comp' must be a correlator or a list of correlators.") 952 953 if plateau: 954 if isinstance(plateau, Obs): 955 if auto_gamma: 956 plateau.gamma_method() 957 ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau)) 958 ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-') 959 else: 960 raise TypeError("'plateau' must be an Obs") 961 962 if references: 963 if isinstance(references, list): 964 for ref in references: 965 ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--') 966 else: 967 raise TypeError("'references' must be a list of floating pint values.") 968 969 if self.prange: 970 ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',', color="black", zorder=0) 971 ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',', color="black", zorder=0) 972 973 if fit_res: 974 x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05) 975 if isinstance(fit_res.fit_function, dict): 976 if fit_key: 977 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) 978 else: 979 raise ValueError("Please provide a 'fit_key' for visualizing combined fits.") 980 else: 981 ax1.plot(x_samples, fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2) 982 983 ax1.set_xlabel(r'$x_0 / a$') 984 if ylabel: 985 ax1.set_ylabel(ylabel) 986 ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5]) 987 988 _handles, labels = ax1.get_legend_handles_labels() 989 if labels: 990 ax1.legend() 991 992 if title: 993 plt.title(title) 994 995 plt.draw() 996 997 if save: 998 if isinstance(save, str): 999 fig.savefig(save, bbox_inches='tight') 1000 else: 1001 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.
1003 def spaghetti_plot(self, logscale=True): 1004 """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations. 1005 1006 Parameters 1007 ---------- 1008 logscale : bool 1009 Determines whether the scale of the y-axis is logarithmic or standard. 1010 """ 1011 if self.N != 1: 1012 raise ValueError("Correlator needs to be projected first.") 1013 1014 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])) 1015 x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content, strict=True) if o is not None] 1016 1017 for name in mc_names: 1018 data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T 1019 1020 fig = plt.figure() 1021 ax = fig.add_subplot(111) 1022 for dat in data: 1023 ax.plot(x0_vals, dat, ls='-', marker='') 1024 1025 if logscale is True: 1026 ax.set_yscale('log') 1027 1028 ax.set_xlabel(r'$x_0 / a$') 1029 plt.title(name) 1030 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.
1032 def dump(self, filename, datatype="json.gz", **kwargs): 1033 """Dumps the Corr into a file of chosen type 1034 Parameters 1035 ---------- 1036 filename : str 1037 Name of the file to be saved. 1038 datatype : str 1039 Format of the exported file. Supported formats include 1040 "json.gz" and "pickle" 1041 path : str 1042 specifies a custom path for the file (default '.') 1043 """ 1044 if datatype == "json.gz": 1045 from .input.json import dump_to_json 1046 if 'path' in kwargs: 1047 file_name = kwargs.get('path') + '/' + filename 1048 else: 1049 file_name = filename 1050 dump_to_json(self, file_name) 1051 elif datatype == "pickle": 1052 dump_object(self, filename, **kwargs) 1053 else: 1054 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 '.')
1358 @property 1359 def imag(self): 1360 def return_imag(obs_OR_cobs): 1361 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1362 return np.vectorize(lambda x: x.imag)(obs_OR_cobs) 1363 else: 1364 return obs_OR_cobs * 0 # So it stays the right type 1365 1366 return self._apply_func_to_corr(return_imag)
1368 def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None): 1369 r''' Project large correlation matrix to lowest states 1370 1371 This method can be used to reduce the size of an (N x N) correlation matrix 1372 to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise 1373 is still small. 1374 1375 Parameters 1376 ---------- 1377 Ntrunc: int 1378 Rank of the target matrix. 1379 tproj: int 1380 Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method. 1381 The default value is 3. 1382 t0proj: int 1383 Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly 1384 discouraged for O(a) improved theories, since the correctness of the procedure 1385 cannot be granted in this case. The default value is 2. 1386 basematrix : Corr 1387 Correlation matrix that is used to determine the eigenvectors of the 1388 lowest states based on a GEVP. basematrix is taken to be the Corr itself if 1389 is is not specified. 1390 1391 Notes 1392 ----- 1393 We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving 1394 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}$ 1395 and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the 1396 resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via 1397 $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large 1398 correlation matrix and to remove some noise that is added by irrelevant operators. 1399 This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated 1400 bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$. 1401 ''' 1402 1403 if self.N == 1: 1404 raise ValueError('Method cannot be applied to one-dimensional correlators.') 1405 if basematrix is None: 1406 basematrix = self 1407 if Ntrunc >= basematrix.N: 1408 raise ValueError(f'Cannot truncate using Ntrunc >= {basematrix.N}') 1409 if basematrix.N != self.N: 1410 raise ValueError('basematrix and targetmatrix have to be of the same size.') 1411 1412 evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc] 1413 1414 tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object) 1415 rmat = [] 1416 for t in range(basematrix.T): 1417 if self.content[t] is None: 1418 rmat.append(None) 1419 else: 1420 for i in range(Ntrunc): 1421 for j in range(Ntrunc): 1422 tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j] 1423 rmat.append(np.copy(tmpmat)) 1424 1425 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)$.