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