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 """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: symmetric, improved, log, default: symmetric 587 """ 588 if self.N != 1: 589 raise Exception("second_deriv only implemented for one-dimensional correlators.") 590 if variant == "symmetric": 591 newcontent = [] 592 for t in range(1, self.T - 1): 593 if (self.content[t - 1] is None) or (self.content[t + 1] is None): 594 newcontent.append(None) 595 else: 596 newcontent.append((self.content[t + 1] - 2 * self.content[t] + self.content[t - 1])) 597 if (all([x is None for x in newcontent])): 598 raise Exception("Derivative is undefined at all timeslices") 599 return Corr(newcontent, padding=[1, 1]) 600 elif variant == "improved": 601 newcontent = [] 602 for t in range(2, self.T - 2): 603 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): 604 newcontent.append(None) 605 else: 606 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])) 607 if (all([x is None for x in newcontent])): 608 raise Exception("Derivative is undefined at all timeslices") 609 return Corr(newcontent, padding=[2, 2]) 610 elif variant == 'log': 611 newcontent = [] 612 for t in range(self.T): 613 if (self.content[t] is None) or (self.content[t] <= 0): 614 newcontent.append(None) 615 else: 616 newcontent.append(np.log(self.content[t])) 617 if (all([x is None for x in newcontent])): 618 raise Exception("Log is undefined at all timeslices") 619 logcorr = Corr(newcontent) 620 return self * (logcorr.second_deriv('symmetric') + (logcorr.deriv('symmetric'))**2) 621 else: 622 raise Exception("Unknown variant.") 623 624 def m_eff(self, variant='log', guess=1.0): 625 """Returns the effective mass of the correlator as correlator object 626 627 Parameters 628 ---------- 629 variant : str 630 log : uses the standard effective mass log(C(t) / C(t+1)) 631 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. 632 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. 633 See, e.g., arXiv:1205.5380 634 arccosh : Uses the explicit form of the symmetrized correlator (not recommended) 635 logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2 636 guess : float 637 guess for the root finder, only relevant for the root variant 638 """ 639 if self.N != 1: 640 raise Exception('Correlator must be projected before getting m_eff') 641 if variant == 'log': 642 newcontent = [] 643 for t in range(self.T - 1): 644 if ((self.content[t] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 645 newcontent.append(None) 646 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 647 newcontent.append(None) 648 else: 649 newcontent.append(self.content[t] / self.content[t + 1]) 650 if (all([x is None for x in newcontent])): 651 raise Exception('m_eff is undefined at all timeslices') 652 653 return np.log(Corr(newcontent, padding=[0, 1])) 654 655 elif variant == 'logsym': 656 newcontent = [] 657 for t in range(1, self.T - 1): 658 if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 659 newcontent.append(None) 660 elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0: 661 newcontent.append(None) 662 else: 663 newcontent.append(self.content[t - 1] / self.content[t + 1]) 664 if (all([x is None for x in newcontent])): 665 raise Exception('m_eff is undefined at all timeslices') 666 667 return np.log(Corr(newcontent, padding=[1, 1])) / 2 668 669 elif variant in ['periodic', 'cosh', 'sinh']: 670 if variant in ['periodic', 'cosh']: 671 func = anp.cosh 672 else: 673 func = anp.sinh 674 675 def root_function(x, d): 676 return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d 677 678 newcontent = [] 679 for t in range(self.T - 1): 680 if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0): 681 newcontent.append(None) 682 # Fill the two timeslices in the middle of the lattice with their predecessors 683 elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]: 684 newcontent.append(newcontent[-1]) 685 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 686 newcontent.append(None) 687 else: 688 newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess))) 689 if (all([x is None for x in newcontent])): 690 raise Exception('m_eff is undefined at all timeslices') 691 692 return Corr(newcontent, padding=[0, 1]) 693 694 elif variant == 'arccosh': 695 newcontent = [] 696 for t in range(1, self.T - 1): 697 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): 698 newcontent.append(None) 699 else: 700 newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t])) 701 if (all([x is None for x in newcontent])): 702 raise Exception("m_eff is undefined at all timeslices") 703 return np.arccosh(Corr(newcontent, padding=[1, 1])) 704 705 else: 706 raise Exception('Unknown variant.') 707 708 def fit(self, function, fitrange=None, silent=False, **kwargs): 709 r'''Fits function to the data 710 711 Parameters 712 ---------- 713 function : obj 714 function to fit to the data. See fits.least_squares for details. 715 fitrange : list 716 Two element list containing the timeslices on which the fit is supposed to start and stop. 717 Caution: This range is inclusive as opposed to standard python indexing. 718 `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6. 719 If not specified, self.prange or all timeslices are used. 720 silent : bool 721 Decides whether output is printed to the standard output. 722 ''' 723 if self.N != 1: 724 raise Exception("Correlator must be projected before fitting") 725 726 if fitrange is None: 727 if self.prange: 728 fitrange = self.prange 729 else: 730 fitrange = [0, self.T - 1] 731 else: 732 if not isinstance(fitrange, list): 733 raise Exception("fitrange has to be a list with two elements") 734 if len(fitrange) != 2: 735 raise Exception("fitrange has to have exactly two elements [fit_start, fit_stop]") 736 737 xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]) 738 ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]) 739 result = least_squares(xs, ys, function, silent=silent, **kwargs) 740 return result 741 742 def plateau(self, plateau_range=None, method="fit", auto_gamma=False): 743 """ Extract a plateau value from a Corr object 744 745 Parameters 746 ---------- 747 plateau_range : list 748 list with two entries, indicating the first and the last timeslice 749 of the plateau region. 750 method : str 751 method to extract the plateau. 752 'fit' fits a constant to the plateau region 753 'avg', 'average' or 'mean' just average over the given timeslices. 754 auto_gamma : bool 755 apply gamma_method with default parameters to the Corr. Defaults to None 756 """ 757 if not plateau_range: 758 if self.prange: 759 plateau_range = self.prange 760 else: 761 raise Exception("no plateau range provided") 762 if self.N != 1: 763 raise Exception("Correlator must be projected before getting a plateau.") 764 if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])): 765 raise Exception("plateau is undefined at all timeslices in plateaurange.") 766 if auto_gamma: 767 self.gamma_method() 768 if method == "fit": 769 def const_func(a, t): 770 return a[0] 771 return self.fit(const_func, plateau_range)[0] 772 elif method in ["avg", "average", "mean"]: 773 returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None]) 774 return returnvalue 775 776 else: 777 raise Exception("Unsupported plateau method: " + method) 778 779 def set_prange(self, prange): 780 """Sets the attribute prange of the Corr object.""" 781 if not len(prange) == 2: 782 raise Exception("prange must be a list or array with two values") 783 if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))): 784 raise Exception("Start and end point must be integers") 785 if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] < prange[1]): 786 raise Exception("Start and end point must define a range in the interval 0,T") 787 788 self.prange = prange 789 return 790 791 def show(self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None): 792 """Plots the correlator using the tag of the correlator as label if available. 793 794 Parameters 795 ---------- 796 x_range : list 797 list of two values, determining the range of the x-axis e.g. [4, 8]. 798 comp : Corr or list of Corr 799 Correlator or list of correlators which are plotted for comparison. 800 The tags of these correlators are used as labels if available. 801 logscale : bool 802 Sets y-axis to logscale. 803 plateau : Obs 804 Plateau value to be visualized in the figure. 805 fit_res : Fit_result 806 Fit_result object to be visualized. 807 ylabel : str 808 Label for the y-axis. 809 save : str 810 path to file in which the figure should be saved. 811 auto_gamma : bool 812 Apply the gamma method with standard parameters to all correlators and plateau values before plotting. 813 hide_sigma : float 814 Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors. 815 references : list 816 List of floating point values that are displayed as horizontal lines for reference. 817 title : string 818 Optional title of the figure. 819 """ 820 if self.N != 1: 821 raise Exception("Correlator must be projected before plotting") 822 823 if auto_gamma: 824 self.gamma_method() 825 826 if x_range is None: 827 x_range = [0, self.T - 1] 828 829 fig = plt.figure() 830 ax1 = fig.add_subplot(111) 831 832 x, y, y_err = self.plottable() 833 if hide_sigma: 834 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 835 else: 836 hide_from = None 837 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag) 838 if logscale: 839 ax1.set_yscale('log') 840 else: 841 if y_range is None: 842 try: 843 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)]) 844 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)]) 845 ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)]) 846 except Exception: 847 pass 848 else: 849 ax1.set_ylim(y_range) 850 if comp: 851 if isinstance(comp, (Corr, list)): 852 for corr in comp if isinstance(comp, list) else [comp]: 853 if auto_gamma: 854 corr.gamma_method() 855 x, y, y_err = corr.plottable() 856 if hide_sigma: 857 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 858 else: 859 hide_from = None 860 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor']) 861 else: 862 raise Exception("'comp' must be a correlator or a list of correlators.") 863 864 if plateau: 865 if isinstance(plateau, Obs): 866 if auto_gamma: 867 plateau.gamma_method() 868 ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau)) 869 ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-') 870 else: 871 raise Exception("'plateau' must be an Obs") 872 873 if references: 874 if isinstance(references, list): 875 for ref in references: 876 ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--') 877 else: 878 raise Exception("'references' must be a list of floating pint values.") 879 880 if self.prange: 881 ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',') 882 ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',') 883 884 if fit_res: 885 x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05) 886 ax1.plot(x_samples, 887 fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), 888 ls='-', marker=',', lw=2) 889 890 ax1.set_xlabel(r'$x_0 / a$') 891 if ylabel: 892 ax1.set_ylabel(ylabel) 893 ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5]) 894 895 handles, labels = ax1.get_legend_handles_labels() 896 if labels: 897 ax1.legend() 898 899 if title: 900 plt.title(title) 901 902 plt.draw() 903 904 if save: 905 if isinstance(save, str): 906 fig.savefig(save, bbox_inches='tight') 907 else: 908 raise Exception("'save' has to be a string.") 909 910 def spaghetti_plot(self, logscale=True): 911 """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations. 912 913 Parameters 914 ---------- 915 logscale : bool 916 Determines whether the scale of the y-axis is logarithmic or standard. 917 """ 918 if self.N != 1: 919 raise Exception("Correlator needs to be projected first.") 920 921 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])) 922 x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content) if o is not None] 923 924 for name in mc_names: 925 data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T 926 927 fig = plt.figure() 928 ax = fig.add_subplot(111) 929 for dat in data: 930 ax.plot(x0_vals, dat, ls='-', marker='') 931 932 if logscale is True: 933 ax.set_yscale('log') 934 935 ax.set_xlabel(r'$x_0 / a$') 936 plt.title(name) 937 plt.draw() 938 939 def dump(self, filename, datatype="json.gz", **kwargs): 940 """Dumps the Corr into a file of chosen type 941 Parameters 942 ---------- 943 filename : str 944 Name of the file to be saved. 945 datatype : str 946 Format of the exported file. Supported formats include 947 "json.gz" and "pickle" 948 path : str 949 specifies a custom path for the file (default '.') 950 """ 951 if datatype == "json.gz": 952 from .input.json import dump_to_json 953 if 'path' in kwargs: 954 file_name = kwargs.get('path') + '/' + filename 955 else: 956 file_name = filename 957 dump_to_json(self, file_name) 958 elif datatype == "pickle": 959 dump_object(self, filename, **kwargs) 960 else: 961 raise Exception("Unknown datatype " + str(datatype)) 962 963 def print(self, print_range=None): 964 print(self.__repr__(print_range)) 965 966 def __repr__(self, print_range=None): 967 if print_range is None: 968 print_range = [0, None] 969 970 content_string = "" 971 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 972 973 if self.tag is not None: 974 content_string += "Description: " + self.tag + "\n" 975 if self.N != 1: 976 return content_string 977 if isinstance(self[0], CObs): 978 return content_string 979 980 if print_range[1]: 981 print_range[1] += 1 982 content_string += 'x0/a\tCorr(x0/a)\n------------------\n' 983 for i, sub_corr in enumerate(self.content[print_range[0]:print_range[1]]): 984 if sub_corr is None: 985 content_string += str(i + print_range[0]) + '\n' 986 else: 987 content_string += str(i + print_range[0]) 988 for element in sub_corr: 989 content_string += '\t' + ' ' * int(element >= 0) + str(element) 990 content_string += '\n' 991 return content_string 992 993 def __str__(self): 994 return self.__repr__() 995 996 # We define the basic operations, that can be performed with correlators. 997 # While */+- get defined here, they only work for Corr*Obs and not Obs*Corr. 998 # This is because Obs*Corr checks Obs.__mul__ first and does not catch an exception. 999 # One could try and tell Obs to check if the y in __mul__ is a Corr and 1000 1001 def __add__(self, y): 1002 if isinstance(y, Corr): 1003 if ((self.N != y.N) or (self.T != y.T)): 1004 raise Exception("Addition of Corrs with different shape") 1005 newcontent = [] 1006 for t in range(self.T): 1007 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1008 newcontent.append(None) 1009 else: 1010 newcontent.append(self.content[t] + y.content[t]) 1011 return Corr(newcontent) 1012 1013 elif isinstance(y, (Obs, int, float, CObs)): 1014 newcontent = [] 1015 for t in range(self.T): 1016 if _check_for_none(self, self.content[t]): 1017 newcontent.append(None) 1018 else: 1019 newcontent.append(self.content[t] + y) 1020 return Corr(newcontent, prange=self.prange) 1021 elif isinstance(y, np.ndarray): 1022 if y.shape == (self.T,): 1023 return Corr(list((np.array(self.content).T + y).T)) 1024 else: 1025 raise ValueError("operands could not be broadcast together") 1026 else: 1027 raise TypeError("Corr + wrong type") 1028 1029 def __mul__(self, y): 1030 if isinstance(y, Corr): 1031 if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): 1032 raise Exception("Multiplication of Corr object requires N=N or N=1 and T=T") 1033 newcontent = [] 1034 for t in range(self.T): 1035 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1036 newcontent.append(None) 1037 else: 1038 newcontent.append(self.content[t] * y.content[t]) 1039 return Corr(newcontent) 1040 1041 elif isinstance(y, (Obs, int, float, CObs)): 1042 newcontent = [] 1043 for t in range(self.T): 1044 if _check_for_none(self, self.content[t]): 1045 newcontent.append(None) 1046 else: 1047 newcontent.append(self.content[t] * y) 1048 return Corr(newcontent, prange=self.prange) 1049 elif isinstance(y, np.ndarray): 1050 if y.shape == (self.T,): 1051 return Corr(list((np.array(self.content).T * y).T)) 1052 else: 1053 raise ValueError("operands could not be broadcast together") 1054 else: 1055 raise TypeError("Corr * wrong type") 1056 1057 def __truediv__(self, y): 1058 if isinstance(y, Corr): 1059 if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): 1060 raise Exception("Multiplication of Corr object requires N=N or N=1 and T=T") 1061 newcontent = [] 1062 for t in range(self.T): 1063 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1064 newcontent.append(None) 1065 else: 1066 newcontent.append(self.content[t] / y.content[t]) 1067 for t in range(self.T): 1068 if _check_for_none(self, newcontent[t]): 1069 continue 1070 if np.isnan(np.sum(newcontent[t]).value): 1071 newcontent[t] = None 1072 1073 if all([item is None for item in newcontent]): 1074 raise Exception("Division returns completely undefined correlator") 1075 return Corr(newcontent) 1076 1077 elif isinstance(y, (Obs, CObs)): 1078 if isinstance(y, Obs): 1079 if y.value == 0: 1080 raise Exception('Division by zero will return undefined correlator') 1081 if isinstance(y, CObs): 1082 if y.is_zero(): 1083 raise Exception('Division by zero will return undefined correlator') 1084 1085 newcontent = [] 1086 for t in range(self.T): 1087 if _check_for_none(self, self.content[t]): 1088 newcontent.append(None) 1089 else: 1090 newcontent.append(self.content[t] / y) 1091 return Corr(newcontent, prange=self.prange) 1092 1093 elif isinstance(y, (int, float)): 1094 if y == 0: 1095 raise Exception('Division by zero will return undefined correlator') 1096 newcontent = [] 1097 for t in range(self.T): 1098 if _check_for_none(self, self.content[t]): 1099 newcontent.append(None) 1100 else: 1101 newcontent.append(self.content[t] / y) 1102 return Corr(newcontent, prange=self.prange) 1103 elif isinstance(y, np.ndarray): 1104 if y.shape == (self.T,): 1105 return Corr(list((np.array(self.content).T / y).T)) 1106 else: 1107 raise ValueError("operands could not be broadcast together") 1108 else: 1109 raise TypeError('Corr / wrong type') 1110 1111 def __neg__(self): 1112 newcontent = [None if _check_for_none(self, item) else -1. * item for item in self.content] 1113 return Corr(newcontent, prange=self.prange) 1114 1115 def __sub__(self, y): 1116 return self + (-y) 1117 1118 def __pow__(self, y): 1119 if isinstance(y, (Obs, int, float, CObs)): 1120 newcontent = [None if _check_for_none(self, item) else item**y for item in self.content] 1121 return Corr(newcontent, prange=self.prange) 1122 else: 1123 raise TypeError('Type of exponent not supported') 1124 1125 def __abs__(self): 1126 newcontent = [None if _check_for_none(self, item) else np.abs(item) for item in self.content] 1127 return Corr(newcontent, prange=self.prange) 1128 1129 # The numpy functions: 1130 def sqrt(self): 1131 return self ** 0.5 1132 1133 def log(self): 1134 newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content] 1135 return Corr(newcontent, prange=self.prange) 1136 1137 def exp(self): 1138 newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content] 1139 return Corr(newcontent, prange=self.prange) 1140 1141 def _apply_func_to_corr(self, func): 1142 newcontent = [None if _check_for_none(self, item) else func(item) for item in self.content] 1143 for t in range(self.T): 1144 if _check_for_none(self, newcontent[t]): 1145 continue 1146 tmp_sum = np.sum(newcontent[t]) 1147 if hasattr(tmp_sum, "value"): 1148 if np.isnan(tmp_sum.value): 1149 newcontent[t] = None 1150 if all([item is None for item in newcontent]): 1151 raise Exception('Operation returns undefined correlator') 1152 return Corr(newcontent) 1153 1154 def sin(self): 1155 return self._apply_func_to_corr(np.sin) 1156 1157 def cos(self): 1158 return self._apply_func_to_corr(np.cos) 1159 1160 def tan(self): 1161 return self._apply_func_to_corr(np.tan) 1162 1163 def sinh(self): 1164 return self._apply_func_to_corr(np.sinh) 1165 1166 def cosh(self): 1167 return self._apply_func_to_corr(np.cosh) 1168 1169 def tanh(self): 1170 return self._apply_func_to_corr(np.tanh) 1171 1172 def arcsin(self): 1173 return self._apply_func_to_corr(np.arcsin) 1174 1175 def arccos(self): 1176 return self._apply_func_to_corr(np.arccos) 1177 1178 def arctan(self): 1179 return self._apply_func_to_corr(np.arctan) 1180 1181 def arcsinh(self): 1182 return self._apply_func_to_corr(np.arcsinh) 1183 1184 def arccosh(self): 1185 return self._apply_func_to_corr(np.arccosh) 1186 1187 def arctanh(self): 1188 return self._apply_func_to_corr(np.arctanh) 1189 1190 # Right hand side operations (require tweak in main module to work) 1191 def __radd__(self, y): 1192 return self + y 1193 1194 def __rsub__(self, y): 1195 return -self + y 1196 1197 def __rmul__(self, y): 1198 return self * y 1199 1200 def __rtruediv__(self, y): 1201 return (self / y) ** (-1) 1202 1203 @property 1204 def real(self): 1205 def return_real(obs_OR_cobs): 1206 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1207 return np.vectorize(lambda x: x.real)(obs_OR_cobs) 1208 else: 1209 return obs_OR_cobs 1210 1211 return self._apply_func_to_corr(return_real) 1212 1213 @property 1214 def imag(self): 1215 def return_imag(obs_OR_cobs): 1216 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1217 return np.vectorize(lambda x: x.imag)(obs_OR_cobs) 1218 else: 1219 return obs_OR_cobs * 0 # So it stays the right type 1220 1221 return self._apply_func_to_corr(return_imag) 1222 1223 def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None): 1224 r''' Project large correlation matrix to lowest states 1225 1226 This method can be used to reduce the size of an (N x N) correlation matrix 1227 to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise 1228 is still small. 1229 1230 Parameters 1231 ---------- 1232 Ntrunc: int 1233 Rank of the target matrix. 1234 tproj: int 1235 Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method. 1236 The default value is 3. 1237 t0proj: int 1238 Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly 1239 discouraged for O(a) improved theories, since the correctness of the procedure 1240 cannot be granted in this case. The default value is 2. 1241 basematrix : Corr 1242 Correlation matrix that is used to determine the eigenvectors of the 1243 lowest states based on a GEVP. basematrix is taken to be the Corr itself if 1244 is is not specified. 1245 1246 Notes 1247 ----- 1248 We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving 1249 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}$ 1250 and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the 1251 resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via 1252 $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large 1253 correlation matrix and to remove some noise that is added by irrelevant operators. 1254 This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated 1255 bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$. 1256 ''' 1257 1258 if self.N == 1: 1259 raise Exception('Method cannot be applied to one-dimensional correlators.') 1260 if basematrix is None: 1261 basematrix = self 1262 if Ntrunc >= basematrix.N: 1263 raise Exception('Cannot truncate using Ntrunc <= %d' % (basematrix.N)) 1264 if basematrix.N != self.N: 1265 raise Exception('basematrix and targetmatrix have to be of the same size.') 1266 1267 evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc] 1268 1269 tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object) 1270 rmat = [] 1271 for t in range(basematrix.T): 1272 for i in range(Ntrunc): 1273 for j in range(Ntrunc): 1274 tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j] 1275 rmat.append(np.copy(tmpmat)) 1276 1277 newcontent = [None if (self.content[t] is None) else rmat[t] for t in range(self.T)] 1278 return Corr(newcontent) 1279 1280 1281def _sort_vectors(vec_set, ts): 1282 """Helper function used to find a set of Eigenvectors consistent over all timeslices""" 1283 reference_sorting = np.array(vec_set[ts]) 1284 N = reference_sorting.shape[0] 1285 sorted_vec_set = [] 1286 for t in range(len(vec_set)): 1287 if vec_set[t] is None: 1288 sorted_vec_set.append(None) 1289 elif not t == ts: 1290 perms = [list(o) for o in permutations([i for i in range(N)], N)] 1291 best_score = 0 1292 for perm in perms: 1293 current_score = 1 1294 for k in range(N): 1295 new_sorting = reference_sorting.copy() 1296 new_sorting[perm[k], :] = vec_set[t][k] 1297 current_score *= abs(np.linalg.det(new_sorting)) 1298 if current_score > best_score: 1299 best_score = current_score 1300 best_perm = perm 1301 sorted_vec_set.append([vec_set[t][k] for k in best_perm]) 1302 else: 1303 sorted_vec_set.append(vec_set[t]) 1304 1305 return sorted_vec_set 1306 1307 1308def _check_for_none(corr, entry): 1309 """Checks if entry for correlator corr is None""" 1310 return len(list(filter(None, np.asarray(entry).flatten()))) < corr.N ** 2 1311 1312 1313def _GEVP_solver(Gt, G0): 1314 """Helper function for solving the GEVP and sorting the eigenvectors. 1315 1316 The helper function assumes that both provided matrices are symmetric and 1317 only processes the lower triangular part of both matrices. In case the matrices 1318 are not symmetric the upper triangular parts are effectively discarded.""" 1319 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 """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: symmetric, improved, log, default: symmetric 588 """ 589 if self.N != 1: 590 raise Exception("second_deriv only implemented for one-dimensional correlators.") 591 if variant == "symmetric": 592 newcontent = [] 593 for t in range(1, self.T - 1): 594 if (self.content[t - 1] is None) or (self.content[t + 1] is None): 595 newcontent.append(None) 596 else: 597 newcontent.append((self.content[t + 1] - 2 * self.content[t] + self.content[t - 1])) 598 if (all([x is None for x in newcontent])): 599 raise Exception("Derivative is undefined at all timeslices") 600 return Corr(newcontent, padding=[1, 1]) 601 elif variant == "improved": 602 newcontent = [] 603 for t in range(2, self.T - 2): 604 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): 605 newcontent.append(None) 606 else: 607 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])) 608 if (all([x is None for x in newcontent])): 609 raise Exception("Derivative is undefined at all timeslices") 610 return Corr(newcontent, padding=[2, 2]) 611 elif variant == 'log': 612 newcontent = [] 613 for t in range(self.T): 614 if (self.content[t] is None) or (self.content[t] <= 0): 615 newcontent.append(None) 616 else: 617 newcontent.append(np.log(self.content[t])) 618 if (all([x is None for x in newcontent])): 619 raise Exception("Log is undefined at all timeslices") 620 logcorr = Corr(newcontent) 621 return self * (logcorr.second_deriv('symmetric') + (logcorr.deriv('symmetric'))**2) 622 else: 623 raise Exception("Unknown variant.") 624 625 def m_eff(self, variant='log', guess=1.0): 626 """Returns the effective mass of the correlator as correlator object 627 628 Parameters 629 ---------- 630 variant : str 631 log : uses the standard effective mass log(C(t) / C(t+1)) 632 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. 633 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. 634 See, e.g., arXiv:1205.5380 635 arccosh : Uses the explicit form of the symmetrized correlator (not recommended) 636 logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2 637 guess : float 638 guess for the root finder, only relevant for the root variant 639 """ 640 if self.N != 1: 641 raise Exception('Correlator must be projected before getting m_eff') 642 if variant == 'log': 643 newcontent = [] 644 for t in range(self.T - 1): 645 if ((self.content[t] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 646 newcontent.append(None) 647 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 648 newcontent.append(None) 649 else: 650 newcontent.append(self.content[t] / self.content[t + 1]) 651 if (all([x is None for x in newcontent])): 652 raise Exception('m_eff is undefined at all timeslices') 653 654 return np.log(Corr(newcontent, padding=[0, 1])) 655 656 elif variant == 'logsym': 657 newcontent = [] 658 for t in range(1, self.T - 1): 659 if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 660 newcontent.append(None) 661 elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0: 662 newcontent.append(None) 663 else: 664 newcontent.append(self.content[t - 1] / self.content[t + 1]) 665 if (all([x is None for x in newcontent])): 666 raise Exception('m_eff is undefined at all timeslices') 667 668 return np.log(Corr(newcontent, padding=[1, 1])) / 2 669 670 elif variant in ['periodic', 'cosh', 'sinh']: 671 if variant in ['periodic', 'cosh']: 672 func = anp.cosh 673 else: 674 func = anp.sinh 675 676 def root_function(x, d): 677 return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d 678 679 newcontent = [] 680 for t in range(self.T - 1): 681 if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0): 682 newcontent.append(None) 683 # Fill the two timeslices in the middle of the lattice with their predecessors 684 elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]: 685 newcontent.append(newcontent[-1]) 686 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 687 newcontent.append(None) 688 else: 689 newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess))) 690 if (all([x is None for x in newcontent])): 691 raise Exception('m_eff is undefined at all timeslices') 692 693 return Corr(newcontent, padding=[0, 1]) 694 695 elif variant == 'arccosh': 696 newcontent = [] 697 for t in range(1, self.T - 1): 698 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): 699 newcontent.append(None) 700 else: 701 newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t])) 702 if (all([x is None for x in newcontent])): 703 raise Exception("m_eff is undefined at all timeslices") 704 return np.arccosh(Corr(newcontent, padding=[1, 1])) 705 706 else: 707 raise Exception('Unknown variant.') 708 709 def fit(self, function, fitrange=None, silent=False, **kwargs): 710 r'''Fits function to the data 711 712 Parameters 713 ---------- 714 function : obj 715 function to fit to the data. See fits.least_squares for details. 716 fitrange : list 717 Two element list containing the timeslices on which the fit is supposed to start and stop. 718 Caution: This range is inclusive as opposed to standard python indexing. 719 `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6. 720 If not specified, self.prange or all timeslices are used. 721 silent : bool 722 Decides whether output is printed to the standard output. 723 ''' 724 if self.N != 1: 725 raise Exception("Correlator must be projected before fitting") 726 727 if fitrange is None: 728 if self.prange: 729 fitrange = self.prange 730 else: 731 fitrange = [0, self.T - 1] 732 else: 733 if not isinstance(fitrange, list): 734 raise Exception("fitrange has to be a list with two elements") 735 if len(fitrange) != 2: 736 raise Exception("fitrange has to have exactly two elements [fit_start, fit_stop]") 737 738 xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]) 739 ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]) 740 result = least_squares(xs, ys, function, silent=silent, **kwargs) 741 return result 742 743 def plateau(self, plateau_range=None, method="fit", auto_gamma=False): 744 """ Extract a plateau value from a Corr object 745 746 Parameters 747 ---------- 748 plateau_range : list 749 list with two entries, indicating the first and the last timeslice 750 of the plateau region. 751 method : str 752 method to extract the plateau. 753 'fit' fits a constant to the plateau region 754 'avg', 'average' or 'mean' just average over the given timeslices. 755 auto_gamma : bool 756 apply gamma_method with default parameters to the Corr. Defaults to None 757 """ 758 if not plateau_range: 759 if self.prange: 760 plateau_range = self.prange 761 else: 762 raise Exception("no plateau range provided") 763 if self.N != 1: 764 raise Exception("Correlator must be projected before getting a plateau.") 765 if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])): 766 raise Exception("plateau is undefined at all timeslices in plateaurange.") 767 if auto_gamma: 768 self.gamma_method() 769 if method == "fit": 770 def const_func(a, t): 771 return a[0] 772 return self.fit(const_func, plateau_range)[0] 773 elif method in ["avg", "average", "mean"]: 774 returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None]) 775 return returnvalue 776 777 else: 778 raise Exception("Unsupported plateau method: " + method) 779 780 def set_prange(self, prange): 781 """Sets the attribute prange of the Corr object.""" 782 if not len(prange) == 2: 783 raise Exception("prange must be a list or array with two values") 784 if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))): 785 raise Exception("Start and end point must be integers") 786 if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] < prange[1]): 787 raise Exception("Start and end point must define a range in the interval 0,T") 788 789 self.prange = prange 790 return 791 792 def show(self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None): 793 """Plots the correlator using the tag of the correlator as label if available. 794 795 Parameters 796 ---------- 797 x_range : list 798 list of two values, determining the range of the x-axis e.g. [4, 8]. 799 comp : Corr or list of Corr 800 Correlator or list of correlators which are plotted for comparison. 801 The tags of these correlators are used as labels if available. 802 logscale : bool 803 Sets y-axis to logscale. 804 plateau : Obs 805 Plateau value to be visualized in the figure. 806 fit_res : Fit_result 807 Fit_result object to be visualized. 808 ylabel : str 809 Label for the y-axis. 810 save : str 811 path to file in which the figure should be saved. 812 auto_gamma : bool 813 Apply the gamma method with standard parameters to all correlators and plateau values before plotting. 814 hide_sigma : float 815 Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors. 816 references : list 817 List of floating point values that are displayed as horizontal lines for reference. 818 title : string 819 Optional title of the figure. 820 """ 821 if self.N != 1: 822 raise Exception("Correlator must be projected before plotting") 823 824 if auto_gamma: 825 self.gamma_method() 826 827 if x_range is None: 828 x_range = [0, self.T - 1] 829 830 fig = plt.figure() 831 ax1 = fig.add_subplot(111) 832 833 x, y, y_err = self.plottable() 834 if hide_sigma: 835 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 836 else: 837 hide_from = None 838 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag) 839 if logscale: 840 ax1.set_yscale('log') 841 else: 842 if y_range is None: 843 try: 844 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)]) 845 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)]) 846 ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)]) 847 except Exception: 848 pass 849 else: 850 ax1.set_ylim(y_range) 851 if comp: 852 if isinstance(comp, (Corr, list)): 853 for corr in comp if isinstance(comp, list) else [comp]: 854 if auto_gamma: 855 corr.gamma_method() 856 x, y, y_err = corr.plottable() 857 if hide_sigma: 858 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 859 else: 860 hide_from = None 861 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor']) 862 else: 863 raise Exception("'comp' must be a correlator or a list of correlators.") 864 865 if plateau: 866 if isinstance(plateau, Obs): 867 if auto_gamma: 868 plateau.gamma_method() 869 ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau)) 870 ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-') 871 else: 872 raise Exception("'plateau' must be an Obs") 873 874 if references: 875 if isinstance(references, list): 876 for ref in references: 877 ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--') 878 else: 879 raise Exception("'references' must be a list of floating pint values.") 880 881 if self.prange: 882 ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',') 883 ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',') 884 885 if fit_res: 886 x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05) 887 ax1.plot(x_samples, 888 fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), 889 ls='-', marker=',', lw=2) 890 891 ax1.set_xlabel(r'$x_0 / a$') 892 if ylabel: 893 ax1.set_ylabel(ylabel) 894 ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5]) 895 896 handles, labels = ax1.get_legend_handles_labels() 897 if labels: 898 ax1.legend() 899 900 if title: 901 plt.title(title) 902 903 plt.draw() 904 905 if save: 906 if isinstance(save, str): 907 fig.savefig(save, bbox_inches='tight') 908 else: 909 raise Exception("'save' has to be a string.") 910 911 def spaghetti_plot(self, logscale=True): 912 """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations. 913 914 Parameters 915 ---------- 916 logscale : bool 917 Determines whether the scale of the y-axis is logarithmic or standard. 918 """ 919 if self.N != 1: 920 raise Exception("Correlator needs to be projected first.") 921 922 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])) 923 x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content) if o is not None] 924 925 for name in mc_names: 926 data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T 927 928 fig = plt.figure() 929 ax = fig.add_subplot(111) 930 for dat in data: 931 ax.plot(x0_vals, dat, ls='-', marker='') 932 933 if logscale is True: 934 ax.set_yscale('log') 935 936 ax.set_xlabel(r'$x_0 / a$') 937 plt.title(name) 938 plt.draw() 939 940 def dump(self, filename, datatype="json.gz", **kwargs): 941 """Dumps the Corr into a file of chosen type 942 Parameters 943 ---------- 944 filename : str 945 Name of the file to be saved. 946 datatype : str 947 Format of the exported file. Supported formats include 948 "json.gz" and "pickle" 949 path : str 950 specifies a custom path for the file (default '.') 951 """ 952 if datatype == "json.gz": 953 from .input.json import dump_to_json 954 if 'path' in kwargs: 955 file_name = kwargs.get('path') + '/' + filename 956 else: 957 file_name = filename 958 dump_to_json(self, file_name) 959 elif datatype == "pickle": 960 dump_object(self, filename, **kwargs) 961 else: 962 raise Exception("Unknown datatype " + str(datatype)) 963 964 def print(self, print_range=None): 965 print(self.__repr__(print_range)) 966 967 def __repr__(self, print_range=None): 968 if print_range is None: 969 print_range = [0, None] 970 971 content_string = "" 972 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 973 974 if self.tag is not None: 975 content_string += "Description: " + self.tag + "\n" 976 if self.N != 1: 977 return content_string 978 if isinstance(self[0], CObs): 979 return content_string 980 981 if print_range[1]: 982 print_range[1] += 1 983 content_string += 'x0/a\tCorr(x0/a)\n------------------\n' 984 for i, sub_corr in enumerate(self.content[print_range[0]:print_range[1]]): 985 if sub_corr is None: 986 content_string += str(i + print_range[0]) + '\n' 987 else: 988 content_string += str(i + print_range[0]) 989 for element in sub_corr: 990 content_string += '\t' + ' ' * int(element >= 0) + str(element) 991 content_string += '\n' 992 return content_string 993 994 def __str__(self): 995 return self.__repr__() 996 997 # We define the basic operations, that can be performed with correlators. 998 # While */+- get defined here, they only work for Corr*Obs and not Obs*Corr. 999 # This is because Obs*Corr checks Obs.__mul__ first and does not catch an exception. 1000 # One could try and tell Obs to check if the y in __mul__ is a Corr and 1001 1002 def __add__(self, y): 1003 if isinstance(y, Corr): 1004 if ((self.N != y.N) or (self.T != y.T)): 1005 raise Exception("Addition of Corrs with different shape") 1006 newcontent = [] 1007 for t in range(self.T): 1008 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1009 newcontent.append(None) 1010 else: 1011 newcontent.append(self.content[t] + y.content[t]) 1012 return Corr(newcontent) 1013 1014 elif isinstance(y, (Obs, int, float, CObs)): 1015 newcontent = [] 1016 for t in range(self.T): 1017 if _check_for_none(self, self.content[t]): 1018 newcontent.append(None) 1019 else: 1020 newcontent.append(self.content[t] + y) 1021 return Corr(newcontent, prange=self.prange) 1022 elif isinstance(y, np.ndarray): 1023 if y.shape == (self.T,): 1024 return Corr(list((np.array(self.content).T + y).T)) 1025 else: 1026 raise ValueError("operands could not be broadcast together") 1027 else: 1028 raise TypeError("Corr + wrong type") 1029 1030 def __mul__(self, y): 1031 if isinstance(y, Corr): 1032 if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): 1033 raise Exception("Multiplication of Corr object requires N=N or N=1 and T=T") 1034 newcontent = [] 1035 for t in range(self.T): 1036 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1037 newcontent.append(None) 1038 else: 1039 newcontent.append(self.content[t] * y.content[t]) 1040 return Corr(newcontent) 1041 1042 elif isinstance(y, (Obs, int, float, CObs)): 1043 newcontent = [] 1044 for t in range(self.T): 1045 if _check_for_none(self, self.content[t]): 1046 newcontent.append(None) 1047 else: 1048 newcontent.append(self.content[t] * y) 1049 return Corr(newcontent, prange=self.prange) 1050 elif isinstance(y, np.ndarray): 1051 if y.shape == (self.T,): 1052 return Corr(list((np.array(self.content).T * y).T)) 1053 else: 1054 raise ValueError("operands could not be broadcast together") 1055 else: 1056 raise TypeError("Corr * wrong type") 1057 1058 def __truediv__(self, y): 1059 if isinstance(y, Corr): 1060 if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): 1061 raise Exception("Multiplication of Corr object requires N=N or N=1 and T=T") 1062 newcontent = [] 1063 for t in range(self.T): 1064 if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): 1065 newcontent.append(None) 1066 else: 1067 newcontent.append(self.content[t] / y.content[t]) 1068 for t in range(self.T): 1069 if _check_for_none(self, newcontent[t]): 1070 continue 1071 if np.isnan(np.sum(newcontent[t]).value): 1072 newcontent[t] = None 1073 1074 if all([item is None for item in newcontent]): 1075 raise Exception("Division returns completely undefined correlator") 1076 return Corr(newcontent) 1077 1078 elif isinstance(y, (Obs, CObs)): 1079 if isinstance(y, Obs): 1080 if y.value == 0: 1081 raise Exception('Division by zero will return undefined correlator') 1082 if isinstance(y, CObs): 1083 if y.is_zero(): 1084 raise Exception('Division by zero will return undefined correlator') 1085 1086 newcontent = [] 1087 for t in range(self.T): 1088 if _check_for_none(self, self.content[t]): 1089 newcontent.append(None) 1090 else: 1091 newcontent.append(self.content[t] / y) 1092 return Corr(newcontent, prange=self.prange) 1093 1094 elif isinstance(y, (int, float)): 1095 if y == 0: 1096 raise Exception('Division by zero will return undefined correlator') 1097 newcontent = [] 1098 for t in range(self.T): 1099 if _check_for_none(self, self.content[t]): 1100 newcontent.append(None) 1101 else: 1102 newcontent.append(self.content[t] / y) 1103 return Corr(newcontent, prange=self.prange) 1104 elif isinstance(y, np.ndarray): 1105 if y.shape == (self.T,): 1106 return Corr(list((np.array(self.content).T / y).T)) 1107 else: 1108 raise ValueError("operands could not be broadcast together") 1109 else: 1110 raise TypeError('Corr / wrong type') 1111 1112 def __neg__(self): 1113 newcontent = [None if _check_for_none(self, item) else -1. * item for item in self.content] 1114 return Corr(newcontent, prange=self.prange) 1115 1116 def __sub__(self, y): 1117 return self + (-y) 1118 1119 def __pow__(self, y): 1120 if isinstance(y, (Obs, int, float, CObs)): 1121 newcontent = [None if _check_for_none(self, item) else item**y for item in self.content] 1122 return Corr(newcontent, prange=self.prange) 1123 else: 1124 raise TypeError('Type of exponent not supported') 1125 1126 def __abs__(self): 1127 newcontent = [None if _check_for_none(self, item) else np.abs(item) for item in self.content] 1128 return Corr(newcontent, prange=self.prange) 1129 1130 # The numpy functions: 1131 def sqrt(self): 1132 return self ** 0.5 1133 1134 def log(self): 1135 newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content] 1136 return Corr(newcontent, prange=self.prange) 1137 1138 def exp(self): 1139 newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content] 1140 return Corr(newcontent, prange=self.prange) 1141 1142 def _apply_func_to_corr(self, func): 1143 newcontent = [None if _check_for_none(self, item) else func(item) for item in self.content] 1144 for t in range(self.T): 1145 if _check_for_none(self, newcontent[t]): 1146 continue 1147 tmp_sum = np.sum(newcontent[t]) 1148 if hasattr(tmp_sum, "value"): 1149 if np.isnan(tmp_sum.value): 1150 newcontent[t] = None 1151 if all([item is None for item in newcontent]): 1152 raise Exception('Operation returns undefined correlator') 1153 return Corr(newcontent) 1154 1155 def sin(self): 1156 return self._apply_func_to_corr(np.sin) 1157 1158 def cos(self): 1159 return self._apply_func_to_corr(np.cos) 1160 1161 def tan(self): 1162 return self._apply_func_to_corr(np.tan) 1163 1164 def sinh(self): 1165 return self._apply_func_to_corr(np.sinh) 1166 1167 def cosh(self): 1168 return self._apply_func_to_corr(np.cosh) 1169 1170 def tanh(self): 1171 return self._apply_func_to_corr(np.tanh) 1172 1173 def arcsin(self): 1174 return self._apply_func_to_corr(np.arcsin) 1175 1176 def arccos(self): 1177 return self._apply_func_to_corr(np.arccos) 1178 1179 def arctan(self): 1180 return self._apply_func_to_corr(np.arctan) 1181 1182 def arcsinh(self): 1183 return self._apply_func_to_corr(np.arcsinh) 1184 1185 def arccosh(self): 1186 return self._apply_func_to_corr(np.arccosh) 1187 1188 def arctanh(self): 1189 return self._apply_func_to_corr(np.arctanh) 1190 1191 # Right hand side operations (require tweak in main module to work) 1192 def __radd__(self, y): 1193 return self + y 1194 1195 def __rsub__(self, y): 1196 return -self + y 1197 1198 def __rmul__(self, y): 1199 return self * y 1200 1201 def __rtruediv__(self, y): 1202 return (self / y) ** (-1) 1203 1204 @property 1205 def real(self): 1206 def return_real(obs_OR_cobs): 1207 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1208 return np.vectorize(lambda x: x.real)(obs_OR_cobs) 1209 else: 1210 return obs_OR_cobs 1211 1212 return self._apply_func_to_corr(return_real) 1213 1214 @property 1215 def imag(self): 1216 def return_imag(obs_OR_cobs): 1217 if isinstance(obs_OR_cobs.flatten()[0], CObs): 1218 return np.vectorize(lambda x: x.imag)(obs_OR_cobs) 1219 else: 1220 return obs_OR_cobs * 0 # So it stays the right type 1221 1222 return self._apply_func_to_corr(return_imag) 1223 1224 def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None): 1225 r''' Project large correlation matrix to lowest states 1226 1227 This method can be used to reduce the size of an (N x N) correlation matrix 1228 to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise 1229 is still small. 1230 1231 Parameters 1232 ---------- 1233 Ntrunc: int 1234 Rank of the target matrix. 1235 tproj: int 1236 Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method. 1237 The default value is 3. 1238 t0proj: int 1239 Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly 1240 discouraged for O(a) improved theories, since the correctness of the procedure 1241 cannot be granted in this case. The default value is 2. 1242 basematrix : Corr 1243 Correlation matrix that is used to determine the eigenvectors of the 1244 lowest states based on a GEVP. basematrix is taken to be the Corr itself if 1245 is is not specified. 1246 1247 Notes 1248 ----- 1249 We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving 1250 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}$ 1251 and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the 1252 resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via 1253 $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large 1254 correlation matrix and to remove some noise that is added by irrelevant operators. 1255 This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated 1256 bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$. 1257 ''' 1258 1259 if self.N == 1: 1260 raise Exception('Method cannot be applied to one-dimensional correlators.') 1261 if basematrix is None: 1262 basematrix = self 1263 if Ntrunc >= basematrix.N: 1264 raise Exception('Cannot truncate using Ntrunc <= %d' % (basematrix.N)) 1265 if basematrix.N != self.N: 1266 raise Exception('basematrix and targetmatrix have to be of the same size.') 1267 1268 evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc] 1269 1270 tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object) 1271 rmat = [] 1272 for t in range(basematrix.T): 1273 for i in range(Ntrunc): 1274 for j in range(Ntrunc): 1275 tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j] 1276 rmat.append(np.copy(tmpmat)) 1277 1278 newcontent = [None if (self.content[t] is None) else rmat[t] for t in range(self.T)] 1279 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 """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: symmetric, improved, log, default: symmetric 588 """ 589 if self.N != 1: 590 raise Exception("second_deriv only implemented for one-dimensional correlators.") 591 if variant == "symmetric": 592 newcontent = [] 593 for t in range(1, self.T - 1): 594 if (self.content[t - 1] is None) or (self.content[t + 1] is None): 595 newcontent.append(None) 596 else: 597 newcontent.append((self.content[t + 1] - 2 * self.content[t] + self.content[t - 1])) 598 if (all([x is None for x in newcontent])): 599 raise Exception("Derivative is undefined at all timeslices") 600 return Corr(newcontent, padding=[1, 1]) 601 elif variant == "improved": 602 newcontent = [] 603 for t in range(2, self.T - 2): 604 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): 605 newcontent.append(None) 606 else: 607 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])) 608 if (all([x is None for x in newcontent])): 609 raise Exception("Derivative is undefined at all timeslices") 610 return Corr(newcontent, padding=[2, 2]) 611 elif variant == 'log': 612 newcontent = [] 613 for t in range(self.T): 614 if (self.content[t] is None) or (self.content[t] <= 0): 615 newcontent.append(None) 616 else: 617 newcontent.append(np.log(self.content[t])) 618 if (all([x is None for x in newcontent])): 619 raise Exception("Log is undefined at all timeslices") 620 logcorr = Corr(newcontent) 621 return self * (logcorr.second_deriv('symmetric') + (logcorr.deriv('symmetric'))**2) 622 else: 623 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, improved, log, default: symmetric
625 def m_eff(self, variant='log', guess=1.0): 626 """Returns the effective mass of the correlator as correlator object 627 628 Parameters 629 ---------- 630 variant : str 631 log : uses the standard effective mass log(C(t) / C(t+1)) 632 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. 633 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. 634 See, e.g., arXiv:1205.5380 635 arccosh : Uses the explicit form of the symmetrized correlator (not recommended) 636 logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2 637 guess : float 638 guess for the root finder, only relevant for the root variant 639 """ 640 if self.N != 1: 641 raise Exception('Correlator must be projected before getting m_eff') 642 if variant == 'log': 643 newcontent = [] 644 for t in range(self.T - 1): 645 if ((self.content[t] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 646 newcontent.append(None) 647 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 648 newcontent.append(None) 649 else: 650 newcontent.append(self.content[t] / self.content[t + 1]) 651 if (all([x is None for x in newcontent])): 652 raise Exception('m_eff is undefined at all timeslices') 653 654 return np.log(Corr(newcontent, padding=[0, 1])) 655 656 elif variant == 'logsym': 657 newcontent = [] 658 for t in range(1, self.T - 1): 659 if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): 660 newcontent.append(None) 661 elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0: 662 newcontent.append(None) 663 else: 664 newcontent.append(self.content[t - 1] / self.content[t + 1]) 665 if (all([x is None for x in newcontent])): 666 raise Exception('m_eff is undefined at all timeslices') 667 668 return np.log(Corr(newcontent, padding=[1, 1])) / 2 669 670 elif variant in ['periodic', 'cosh', 'sinh']: 671 if variant in ['periodic', 'cosh']: 672 func = anp.cosh 673 else: 674 func = anp.sinh 675 676 def root_function(x, d): 677 return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d 678 679 newcontent = [] 680 for t in range(self.T - 1): 681 if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0): 682 newcontent.append(None) 683 # Fill the two timeslices in the middle of the lattice with their predecessors 684 elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]: 685 newcontent.append(newcontent[-1]) 686 elif self.content[t][0].value / self.content[t + 1][0].value < 0: 687 newcontent.append(None) 688 else: 689 newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess))) 690 if (all([x is None for x in newcontent])): 691 raise Exception('m_eff is undefined at all timeslices') 692 693 return Corr(newcontent, padding=[0, 1]) 694 695 elif variant == 'arccosh': 696 newcontent = [] 697 for t in range(1, self.T - 1): 698 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): 699 newcontent.append(None) 700 else: 701 newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t])) 702 if (all([x is None for x in newcontent])): 703 raise Exception("m_eff is undefined at all timeslices") 704 return np.arccosh(Corr(newcontent, padding=[1, 1])) 705 706 else: 707 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
709 def fit(self, function, fitrange=None, silent=False, **kwargs): 710 r'''Fits function to the data 711 712 Parameters 713 ---------- 714 function : obj 715 function to fit to the data. See fits.least_squares for details. 716 fitrange : list 717 Two element list containing the timeslices on which the fit is supposed to start and stop. 718 Caution: This range is inclusive as opposed to standard python indexing. 719 `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6. 720 If not specified, self.prange or all timeslices are used. 721 silent : bool 722 Decides whether output is printed to the standard output. 723 ''' 724 if self.N != 1: 725 raise Exception("Correlator must be projected before fitting") 726 727 if fitrange is None: 728 if self.prange: 729 fitrange = self.prange 730 else: 731 fitrange = [0, self.T - 1] 732 else: 733 if not isinstance(fitrange, list): 734 raise Exception("fitrange has to be a list with two elements") 735 if len(fitrange) != 2: 736 raise Exception("fitrange has to have exactly two elements [fit_start, fit_stop]") 737 738 xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]) 739 ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]) 740 result = least_squares(xs, ys, function, silent=silent, **kwargs) 741 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.
743 def plateau(self, plateau_range=None, method="fit", auto_gamma=False): 744 """ Extract a plateau value from a Corr object 745 746 Parameters 747 ---------- 748 plateau_range : list 749 list with two entries, indicating the first and the last timeslice 750 of the plateau region. 751 method : str 752 method to extract the plateau. 753 'fit' fits a constant to the plateau region 754 'avg', 'average' or 'mean' just average over the given timeslices. 755 auto_gamma : bool 756 apply gamma_method with default parameters to the Corr. Defaults to None 757 """ 758 if not plateau_range: 759 if self.prange: 760 plateau_range = self.prange 761 else: 762 raise Exception("no plateau range provided") 763 if self.N != 1: 764 raise Exception("Correlator must be projected before getting a plateau.") 765 if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])): 766 raise Exception("plateau is undefined at all timeslices in plateaurange.") 767 if auto_gamma: 768 self.gamma_method() 769 if method == "fit": 770 def const_func(a, t): 771 return a[0] 772 return self.fit(const_func, plateau_range)[0] 773 elif method in ["avg", "average", "mean"]: 774 returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None]) 775 return returnvalue 776 777 else: 778 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
780 def set_prange(self, prange): 781 """Sets the attribute prange of the Corr object.""" 782 if not len(prange) == 2: 783 raise Exception("prange must be a list or array with two values") 784 if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))): 785 raise Exception("Start and end point must be integers") 786 if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] < prange[1]): 787 raise Exception("Start and end point must define a range in the interval 0,T") 788 789 self.prange = prange 790 return
Sets the attribute prange of the Corr object.
792 def show(self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None): 793 """Plots the correlator using the tag of the correlator as label if available. 794 795 Parameters 796 ---------- 797 x_range : list 798 list of two values, determining the range of the x-axis e.g. [4, 8]. 799 comp : Corr or list of Corr 800 Correlator or list of correlators which are plotted for comparison. 801 The tags of these correlators are used as labels if available. 802 logscale : bool 803 Sets y-axis to logscale. 804 plateau : Obs 805 Plateau value to be visualized in the figure. 806 fit_res : Fit_result 807 Fit_result object to be visualized. 808 ylabel : str 809 Label for the y-axis. 810 save : str 811 path to file in which the figure should be saved. 812 auto_gamma : bool 813 Apply the gamma method with standard parameters to all correlators and plateau values before plotting. 814 hide_sigma : float 815 Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors. 816 references : list 817 List of floating point values that are displayed as horizontal lines for reference. 818 title : string 819 Optional title of the figure. 820 """ 821 if self.N != 1: 822 raise Exception("Correlator must be projected before plotting") 823 824 if auto_gamma: 825 self.gamma_method() 826 827 if x_range is None: 828 x_range = [0, self.T - 1] 829 830 fig = plt.figure() 831 ax1 = fig.add_subplot(111) 832 833 x, y, y_err = self.plottable() 834 if hide_sigma: 835 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 836 else: 837 hide_from = None 838 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag) 839 if logscale: 840 ax1.set_yscale('log') 841 else: 842 if y_range is None: 843 try: 844 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)]) 845 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)]) 846 ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)]) 847 except Exception: 848 pass 849 else: 850 ax1.set_ylim(y_range) 851 if comp: 852 if isinstance(comp, (Corr, list)): 853 for corr in comp if isinstance(comp, list) else [comp]: 854 if auto_gamma: 855 corr.gamma_method() 856 x, y, y_err = corr.plottable() 857 if hide_sigma: 858 hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1 859 else: 860 hide_from = None 861 ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor']) 862 else: 863 raise Exception("'comp' must be a correlator or a list of correlators.") 864 865 if plateau: 866 if isinstance(plateau, Obs): 867 if auto_gamma: 868 plateau.gamma_method() 869 ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau)) 870 ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-') 871 else: 872 raise Exception("'plateau' must be an Obs") 873 874 if references: 875 if isinstance(references, list): 876 for ref in references: 877 ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--') 878 else: 879 raise Exception("'references' must be a list of floating pint values.") 880 881 if self.prange: 882 ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',') 883 ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',') 884 885 if fit_res: 886 x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05) 887 ax1.plot(x_samples, 888 fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), 889 ls='-', marker=',', lw=2) 890 891 ax1.set_xlabel(r'$x_0 / a$') 892 if ylabel: 893 ax1.set_ylabel(ylabel) 894 ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5]) 895 896 handles, labels = ax1.get_legend_handles_labels() 897 if labels: 898 ax1.legend() 899 900 if title: 901 plt.title(title) 902 903 plt.draw() 904 905 if save: 906 if isinstance(save, str): 907 fig.savefig(save, bbox_inches='tight') 908 else: 909 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.
- 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.
911 def spaghetti_plot(self, logscale=True): 912 """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations. 913 914 Parameters 915 ---------- 916 logscale : bool 917 Determines whether the scale of the y-axis is logarithmic or standard. 918 """ 919 if self.N != 1: 920 raise Exception("Correlator needs to be projected first.") 921 922 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])) 923 x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content) if o is not None] 924 925 for name in mc_names: 926 data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T 927 928 fig = plt.figure() 929 ax = fig.add_subplot(111) 930 for dat in data: 931 ax.plot(x0_vals, dat, ls='-', marker='') 932 933 if logscale is True: 934 ax.set_yscale('log') 935 936 ax.set_xlabel(r'$x_0 / a$') 937 plt.title(name) 938 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.
940 def dump(self, filename, datatype="json.gz", **kwargs): 941 """Dumps the Corr into a file of chosen type 942 Parameters 943 ---------- 944 filename : str 945 Name of the file to be saved. 946 datatype : str 947 Format of the exported file. Supported formats include 948 "json.gz" and "pickle" 949 path : str 950 specifies a custom path for the file (default '.') 951 """ 952 if datatype == "json.gz": 953 from .input.json import dump_to_json 954 if 'path' in kwargs: 955 file_name = kwargs.get('path') + '/' + filename 956 else: 957 file_name = filename 958 dump_to_json(self, file_name) 959 elif datatype == "pickle": 960 dump_object(self, filename, **kwargs) 961 else: 962 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 '.')
1224 def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None): 1225 r''' Project large correlation matrix to lowest states 1226 1227 This method can be used to reduce the size of an (N x N) correlation matrix 1228 to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise 1229 is still small. 1230 1231 Parameters 1232 ---------- 1233 Ntrunc: int 1234 Rank of the target matrix. 1235 tproj: int 1236 Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method. 1237 The default value is 3. 1238 t0proj: int 1239 Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly 1240 discouraged for O(a) improved theories, since the correctness of the procedure 1241 cannot be granted in this case. The default value is 2. 1242 basematrix : Corr 1243 Correlation matrix that is used to determine the eigenvectors of the 1244 lowest states based on a GEVP. basematrix is taken to be the Corr itself if 1245 is is not specified. 1246 1247 Notes 1248 ----- 1249 We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving 1250 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}$ 1251 and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the 1252 resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via 1253 $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large 1254 correlation matrix and to remove some noise that is added by irrelevant operators. 1255 This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated 1256 bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$. 1257 ''' 1258 1259 if self.N == 1: 1260 raise Exception('Method cannot be applied to one-dimensional correlators.') 1261 if basematrix is None: 1262 basematrix = self 1263 if Ntrunc >= basematrix.N: 1264 raise Exception('Cannot truncate using Ntrunc <= %d' % (basematrix.N)) 1265 if basematrix.N != self.N: 1266 raise Exception('basematrix and targetmatrix have to be of the same size.') 1267 1268 evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc] 1269 1270 tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object) 1271 rmat = [] 1272 for t in range(basematrix.T): 1273 for i in range(Ntrunc): 1274 for j in range(Ntrunc): 1275 tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j] 1276 rmat.append(np.copy(tmpmat)) 1277 1278 newcontent = [None if (self.content[t] is None) else rmat[t] for t in range(self.T)] 1279 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)$.