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