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

#   Corr(data_input, padding=[0, 0], prange=None)
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.
#   reweighted
#   def gamma_method(self, **kwargs):
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.

#   def projected(self, vector_l=None, vector_r=None, normalize=False):
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

#   def item(self, i, j):
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.
#   def plottable(self):
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.

#   def symmetric(self):
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.

#   def anti_symmetric(self):
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.

#   def matrix_symmetric(self):
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.

#   def GEVP(self, t0, ts=None, state=0, sorted_list='Eigenvalue'):
View Source
244    def GEVP(self, t0, ts=None, state=0, sorted_list="Eigenvalue"):
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
263        if self.N == 1:
264            raise Exception("GEVP methods only works on correlator matrices and not single correlators.")
265
266        symmetric_corr = self.matrix_symmetric()
267        if sorted_list is None:
268            if (ts is None):
269                raise Exception("ts is required if sorted_list=None")
270            if (self.content[t0] is None) or (self.content[ts] is None):
271                raise Exception("Corr not defined at t0/ts")
272            G0, Gt = np.empty([self.N, self.N], dtype="double"), np.empty([self.N, self.N], dtype="double")
273            for i in range(self.N):
274                for j in range(self.N):
275                    G0[i, j] = symmetric_corr[t0][i, j].value
276                    Gt[i, j] = symmetric_corr[ts][i, j].value
277
278            sp_vecs = _GEVP_solver(Gt, G0)
279            sp_vec = sp_vecs[state]
280            return sp_vec
281        elif sorted_list in ["Eigenvalue", "Eigenvector"]:
282            if sorted_list == "Eigenvalue" and ts is not None:
283                warnings.warn("ts has no effect when sorting by eigenvalue is chosen.", RuntimeWarning)
284            all_vecs = []
285            for t in range(self.T):
286                try:
287                    G0, Gt = np.empty([self.N, self.N], dtype="double"), np.empty([self.N, self.N], dtype="double")
288                    for i in range(self.N):
289                        for j in range(self.N):
290                            G0[i, j] = symmetric_corr[t0][i, j].value
291                            Gt[i, j] = symmetric_corr[t][i, j].value
292
293                    sp_vecs = _GEVP_solver(Gt, G0)
294                    if sorted_list == "Eigenvalue":
295                        sp_vec = sp_vecs[state]
296                        all_vecs.append(sp_vec)
297                    else:
298                        all_vecs.append(sp_vecs)
299                except Exception:
300                    all_vecs.append(None)
301            if sorted_list == "Eigenvector":
302                if (ts is None):
303                    raise Exception("ts is required for the Eigenvector sorting method.")
304                all_vecs = _sort_vectors(all_vecs, ts)
305                all_vecs = [a[state] for a in all_vecs]
306        else:
307            raise Exception("Unkown value for 'sorted_list'.")
308
309        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
#   def Eigenvalue(self, t0, ts=None, state=0, sorted_list=None):
View Source
311    def Eigenvalue(self, t0, ts=None, state=0, sorted_list=None):
312        """Determines the eigenvalue of the GEVP by solving and projecting the correlator
313
314        Parameters
315        ----------
316        t0 : int
317            The time t0 for G(t)v= lambda G(t_0)v
318        ts : int
319            fixed time G(t_s)v= lambda G(t_0)v  if return_list=False
320            If return_list=True and sorting=Eigenvector it gives a reference point for the sorting method.
321        state : int
322            The state one is interested in ordered by energy. The lowest state is zero.
323        sorted_list : string
324            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.
325             "Eigenvalue"  -  The eigenvector is chosen according to which eigenvalue it belongs individually on every timeslice.
326             "Eigenvector" -  Use the method described in arXiv:2004.10472 [hep-lat] to find the set of v(t) belonging to the state.
327                              The reference state is identified by its eigenvalue at t=ts
328        """
329        vec = self.GEVP(t0, ts=ts, state=state, sorted_list=sorted_list)
330        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
#   def Hankel(self, N, periodic=False):
View Source
332    def Hankel(self, N, periodic=False):
333        """Constructs an NxN Hankel matrix
334
335        C(t) c(t+1) ... c(t+n-1)
336        C(t+1) c(t+2) ... c(t+n)
337        .................
338        C(t+(n-1)) c(t+n) ... c(t+2(n-1))
339
340        Parameters
341        ----------
342        N : int
343            Dimension of the Hankel matrix
344        periodic : bool, optional
345            determines whether the matrix is extended periodically
346        """
347
348        if self.N != 1:
349            raise Exception("Multi-operator Prony not implemented!")
350
351        array = np.empty([N, N], dtype="object")
352        new_content = []
353        for t in range(self.T):
354            new_content.append(array.copy())
355
356        def wrap(i):
357            while i >= self.T:
358                i -= self.T
359            return i
360
361        for t in range(self.T):
362            for i in range(N):
363                for j in range(N):
364                    if periodic:
365                        new_content[t][i, j] = self.content[wrap(t + i + j)][0]
366                    elif (t + i + j) >= self.T:
367                        new_content[t] = None
368                    else:
369                        new_content[t][i, j] = self.content[t + i + j][0]
370
371        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
#   def roll(self, dt):
View Source
373    def roll(self, dt):
374        """Periodically shift the correlator by dt timeslices
375
376        Parameters
377        ----------
378        dt : int
379            number of timeslices
380        """
381        return Corr(list(np.roll(np.array(self.content, dtype=object), dt)))

Periodically shift the correlator by dt timeslices

Parameters
  • dt (int): number of timeslices
#   def reverse(self):
View Source
383    def reverse(self):
384        """Reverse the time ordering of the Corr"""
385        return Corr(self.content[:: -1])

Reverse the time ordering of the Corr

#   def thin(self, spacing=2, offset=0):
View Source
387    def thin(self, spacing=2, offset=0):
388        """Thin out a correlator to suppress correlations
389
390        Parameters
391        ----------
392        spacing : int
393            Keep only every 'spacing'th entry of the correlator
394        offset : int
395            Offset the equal spacing
396        """
397        new_content = []
398        for t in range(self.T):
399            if (offset + t) % spacing != 0:
400                new_content.append(None)
401            else:
402                new_content.append(self.content[t])
403        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
#   def correlate(self, partner):
View Source
405    def correlate(self, partner):
406        """Correlate the correlator with another correlator or Obs
407
408        Parameters
409        ----------
410        partner : Obs or Corr
411            partner to correlate the correlator with.
412            Can either be an Obs which is correlated with all entries of the
413            correlator or a Corr of same length.
414        """
415        new_content = []
416        for x0, t_slice in enumerate(self.content):
417            if t_slice is None:
418                new_content.append(None)
419            else:
420                if isinstance(partner, Corr):
421                    if partner.content[x0] is None:
422                        new_content.append(None)
423                    else:
424                        new_content.append(np.array([correlate(o, partner.content[x0][0]) for o in t_slice]))
425                elif isinstance(partner, Obs):  # Should this include CObs?
426                    new_content.append(np.array([correlate(o, partner) for o in t_slice]))
427                else:
428                    raise Exception("Can only correlate with an Obs or a Corr.")
429
430        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.
#   def reweight(self, weight, **kwargs):
View Source
432    def reweight(self, weight, **kwargs):
433        """Reweight the correlator.
434
435        Parameters
436        ----------
437        weight : Obs
438            Reweighting factor. An Observable that has to be defined on a superset of the
439            configurations in obs[i].idl for all i.
440        all_configs : bool
441            if True, the reweighted observables are normalized by the average of
442            the reweighting factor on all configurations in weight.idl and not
443            on the configurations in obs[i].idl.
444        """
445        new_content = []
446        for t_slice in self.content:
447            if t_slice is None:
448                new_content.append(None)
449            else:
450                new_content.append(np.array(reweight(weight, t_slice, **kwargs)))
451        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.
#   def T_symmetry(self, partner, parity=1):
View Source
453    def T_symmetry(self, partner, parity=+1):
454        """Return the time symmetry average of the correlator and its partner
455
456        Parameters
457        ----------
458        partner : Corr
459            Time symmetry partner of the Corr
460        partity : int
461            Parity quantum number of the correlator, can be +1 or -1
462        """
463        if not isinstance(partner, Corr):
464            raise Exception("T partner has to be a Corr object.")
465        if parity not in [+1, -1]:
466            raise Exception("Parity has to be +1 or -1.")
467        T_partner = parity * partner.reverse()
468
469        t_slices = []
470        test = (self - T_partner)
471        test.gamma_method()
472        for x0, t_slice in enumerate(test.content):
473            if t_slice is not None:
474                if not t_slice[0].is_zero_within_error(5):
475                    t_slices.append(x0)
476        if t_slices:
477            warnings.warn("T symmetry partners do not agree within 5 sigma on time slices " + str(t_slices) + ".", RuntimeWarning)
478
479        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
#   def deriv(self, variant='symmetric'):
View Source
481    def deriv(self, variant="symmetric"):
482        """Return the first derivative of the correlator with respect to x0.
483
484        Parameters
485        ----------
486        variant : str
487            decides which definition of the finite differences derivative is used.
488            Available choice: symmetric, forward, backward, improved, default: symmetric
489        """
490        if variant == "symmetric":
491            newcontent = []
492            for t in range(1, self.T - 1):
493                if (self.content[t - 1] is None) or (self.content[t + 1] is None):
494                    newcontent.append(None)
495                else:
496                    newcontent.append(0.5 * (self.content[t + 1] - self.content[t - 1]))
497            if(all([x is None for x in newcontent])):
498                raise Exception('Derivative is undefined at all timeslices')
499            return Corr(newcontent, padding=[1, 1])
500        elif variant == "forward":
501            newcontent = []
502            for t in range(self.T - 1):
503                if (self.content[t] is None) or (self.content[t + 1] is None):
504                    newcontent.append(None)
505                else:
506                    newcontent.append(self.content[t + 1] - self.content[t])
507            if(all([x is None for x in newcontent])):
508                raise Exception("Derivative is undefined at all timeslices")
509            return Corr(newcontent, padding=[0, 1])
510        elif variant == "backward":
511            newcontent = []
512            for t in range(1, self.T):
513                if (self.content[t - 1] is None) or (self.content[t] is None):
514                    newcontent.append(None)
515                else:
516                    newcontent.append(self.content[t] - self.content[t - 1])
517            if(all([x is None for x in newcontent])):
518                raise Exception("Derivative is undefined at all timeslices")
519            return Corr(newcontent, padding=[1, 0])
520        elif variant == "improved":
521            newcontent = []
522            for t in range(2, self.T - 2):
523                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):
524                    newcontent.append(None)
525                else:
526                    newcontent.append((1 / 12) * (self.content[t - 2] - 8 * self.content[t - 1] + 8 * self.content[t + 1] - self.content[t + 2]))
527            if(all([x is None for x in newcontent])):
528                raise Exception('Derivative is undefined at all timeslices')
529            return Corr(newcontent, padding=[2, 2])
530        else:
531            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
#   def second_deriv(self, variant='symmetric'):
View Source
533    def second_deriv(self, variant="symmetric"):
534        """Return the second derivative of the correlator with respect to x0.
535
536        Parameters
537        ----------
538        variant : str
539            decides which definition of the finite differences derivative is used.
540            Available choice: symmetric, improved, default: symmetric
541        """
542        if variant == "symmetric":
543            newcontent = []
544            for t in range(1, self.T - 1):
545                if (self.content[t - 1] is None) or (self.content[t + 1] is None):
546                    newcontent.append(None)
547                else:
548                    newcontent.append((self.content[t + 1] - 2 * self.content[t] + self.content[t - 1]))
549            if(all([x is None for x in newcontent])):
550                raise Exception("Derivative is undefined at all timeslices")
551            return Corr(newcontent, padding=[1, 1])
552        elif variant == "improved":
553            newcontent = []
554            for t in range(2, self.T - 2):
555                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):
556                    newcontent.append(None)
557                else:
558                    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]))
559            if(all([x is None for x in newcontent])):
560                raise Exception("Derivative is undefined at all timeslices")
561            return Corr(newcontent, padding=[2, 2])
562        else:
563            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
#   def m_eff(self, variant='log', guess=1.0):
View Source
565    def m_eff(self, variant='log', guess=1.0):
566        """Returns the effective mass of the correlator as correlator object
567
568        Parameters
569        ----------
570        variant : str
571            log : uses the standard effective mass log(C(t) / C(t+1))
572            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.
573            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.
574            See, e.g., arXiv:1205.5380
575            arccosh : Uses the explicit form of the symmetrized correlator (not recommended)
576        guess : float
577            guess for the root finder, only relevant for the root variant
578        """
579        if self.N != 1:
580            raise Exception('Correlator must be projected before getting m_eff')
581        if variant == 'log':
582            newcontent = []
583            for t in range(self.T - 1):
584                if (self.content[t] is None) or (self.content[t + 1] is None):
585                    newcontent.append(None)
586                else:
587                    newcontent.append(self.content[t] / self.content[t + 1])
588            if(all([x is None for x in newcontent])):
589                raise Exception('m_eff is undefined at all timeslices')
590
591            return np.log(Corr(newcontent, padding=[0, 1]))
592
593        elif variant in ['periodic', 'cosh', 'sinh']:
594            if variant in ['periodic', 'cosh']:
595                func = anp.cosh
596            else:
597                func = anp.sinh
598
599            def root_function(x, d):
600                return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d
601
602            newcontent = []
603            for t in range(self.T - 1):
604                if (self.content[t] is None) or (self.content[t + 1] is None):
605                    newcontent.append(None)
606                # Fill the two timeslices in the middle of the lattice with their predecessors
607                elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]:
608                    newcontent.append(newcontent[-1])
609                else:
610                    newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess)))
611            if(all([x is None for x in newcontent])):
612                raise Exception('m_eff is undefined at all timeslices')
613
614            return Corr(newcontent, padding=[0, 1])
615
616        elif variant == 'arccosh':
617            newcontent = []
618            for t in range(1, self.T - 1):
619                if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t - 1] is None):
620                    newcontent.append(None)
621                else:
622                    newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t]))
623            if(all([x is None for x in newcontent])):
624                raise Exception("m_eff is undefined at all timeslices")
625            return np.arccosh(Corr(newcontent, padding=[1, 1]))
626
627        else:
628            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
#   def fit(self, function, fitrange=None, silent=False, **kwargs):
View Source
630    def fit(self, function, fitrange=None, silent=False, **kwargs):
631        r'''Fits function to the data
632
633        Parameters
634        ----------
635        function : obj
636            function to fit to the data. See fits.least_squares for details.
637        fitrange : list
638            Two element list containing the timeslices on which the fit is supposed to start and stop.
639            Caution: This range is inclusive as opposed to standard python indexing.
640            `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6.
641            If not specified, self.prange or all timeslices are used.
642        silent : bool
643            Decides whether output is printed to the standard output.
644        '''
645        if self.N != 1:
646            raise Exception("Correlator must be projected before fitting")
647
648        if fitrange is None:
649            if self.prange:
650                fitrange = self.prange
651            else:
652                fitrange = [0, self.T - 1]
653        else:
654            if not isinstance(fitrange, list):
655                raise Exception("fitrange has to be a list with two elements")
656            if len(fitrange) != 2:
657                raise Exception("fitrange has to have exactly two elements [fit_start, fit_stop]")
658
659        xs = [x for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]
660        ys = [self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None]
661        result = least_squares(xs, ys, function, silent=silent, **kwargs)
662        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.
#   def plateau(self, plateau_range=None, method='fit', auto_gamma=False):
View Source
664    def plateau(self, plateau_range=None, method="fit", auto_gamma=False):
665        """ Extract a plateau value from a Corr object
666
667        Parameters
668        ----------
669        plateau_range : list
670            list with two entries, indicating the first and the last timeslice
671            of the plateau region.
672        method : str
673            method to extract the plateau.
674                'fit' fits a constant to the plateau region
675                'avg', 'average' or 'mean' just average over the given timeslices.
676        auto_gamma : bool
677            apply gamma_method with default parameters to the Corr. Defaults to None
678        """
679        if not plateau_range:
680            if self.prange:
681                plateau_range = self.prange
682            else:
683                raise Exception("no plateau range provided")
684        if self.N != 1:
685            raise Exception("Correlator must be projected before getting a plateau.")
686        if(all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])):
687            raise Exception("plateau is undefined at all timeslices in plateaurange.")
688        if auto_gamma:
689            self.gamma_method()
690        if method == "fit":
691            def const_func(a, t):
692                return a[0]
693            return self.fit(const_func, plateau_range)[0]
694        elif method in ["avg", "average", "mean"]:
695            returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None])
696            return returnvalue
697
698        else:
699            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
#   def set_prange(self, prange):
View Source
701    def set_prange(self, prange):
702        """Sets the attribute prange of the Corr object."""
703        if not len(prange) == 2:
704            raise Exception("prange must be a list or array with two values")
705        if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))):
706            raise Exception("Start and end point must be integers")
707        if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] < prange[1]):
708            raise Exception("Start and end point must define a range in the interval 0,T")
709
710        self.prange = prange
711        return

Sets the attribute prange of the Corr object.

#   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 ):
View Source
713    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):
714        """Plots the correlator using the tag of the correlator as label if available.
715
716        Parameters
717        ----------
718        x_range : list
719            list of two values, determining the range of the x-axis e.g. [4, 8]
720        comp : Corr or list of Corr
721            Correlator or list of correlators which are plotted for comparison.
722            The tags of these correlators are used as labels if available.
723        logscale : bool
724            Sets y-axis to logscale
725        plateau : Obs
726            Plateau value to be visualized in the figure
727        fit_res : Fit_result
728            Fit_result object to be visualized
729        ylabel : str
730            Label for the y-axis
731        save : str
732            path to file in which the figure should be saved
733        auto_gamma : bool
734            Apply the gamma method with standard parameters to all correlators and plateau values before plotting.
735        hide_sigma : float
736            Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors.
737        references : list
738            List of floating point values that are displayed as horizontal lines for reference.
739        """
740        if self.N != 1:
741            raise Exception("Correlator must be projected before plotting")
742
743        if auto_gamma:
744            self.gamma_method()
745
746        if x_range is None:
747            x_range = [0, self.T - 1]
748
749        fig = plt.figure()
750        ax1 = fig.add_subplot(111)
751
752        x, y, y_err = self.plottable()
753        if hide_sigma:
754            hide_from = np.argmax((hide_sigma * np.array(y_err)) > np.abs(y)) - 1
755        else:
756            hide_from = None
757        ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag)
758        if logscale:
759            ax1.set_yscale('log')
760        else:
761            if y_range is None:
762                try:
763                    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)])
764                    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)])
765                    ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)])
766                except Exception:
767                    pass
768            else:
769                ax1.set_ylim(y_range)
770        if comp:
771            if isinstance(comp, (Corr, list)):
772                for corr in comp if isinstance(comp, list) else [comp]:
773                    if auto_gamma:
774                        corr.gamma_method()
775                    x, y, y_err = corr.plottable()
776                    if hide_sigma:
777                        hide_from = np.argmax((hide_sigma * np.array(y_err)) > np.abs(y)) - 1
778                    else:
779                        hide_from = None
780                    plt.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor'])
781            else:
782                raise Exception("'comp' must be a correlator or a list of correlators.")
783
784        if plateau:
785            if isinstance(plateau, Obs):
786                if auto_gamma:
787                    plateau.gamma_method()
788                ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau))
789                ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-')
790            else:
791                raise Exception("'plateau' must be an Obs")
792
793        if references:
794            if isinstance(references, list):
795                for ref in references:
796                    ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--')
797            else:
798                raise Exception("'references' must be a list of floating pint values.")
799
800        if self.prange:
801            ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',')
802            ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',')
803
804        if fit_res:
805            x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05)
806            ax1.plot(x_samples,
807                     fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples),
808                     ls='-', marker=',', lw=2)
809
810        ax1.set_xlabel(r'$x_0 / a$')
811        if ylabel:
812            ax1.set_ylabel(ylabel)
813        ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5])
814
815        handles, labels = ax1.get_legend_handles_labels()
816        if labels:
817            ax1.legend()
818        plt.draw()
819
820        if save:
821            if isinstance(save, str):
822                fig.savefig(save)
823            else:
824                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.
#   def spaghetti_plot(self, logscale=True):
View Source
826    def spaghetti_plot(self, logscale=True):
827        """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations.
828
829        Parameters
830        ----------
831        logscale : bool
832            Determines whether the scale of the y-axis is logarithmic or standard.
833        """
834        if self.N != 1:
835            raise Exception("Correlator needs to be projected first.")
836
837        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]))
838        x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content) if o is not None]
839
840        for name in mc_names:
841            data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T
842
843            fig = plt.figure()
844            ax = fig.add_subplot(111)
845            for dat in data:
846                ax.plot(x0_vals, dat, ls='-', marker='')
847
848            if logscale is True:
849                ax.set_yscale('log')
850
851            ax.set_xlabel(r'$x_0 / a$')
852            plt.title(name)
853            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.
#   def dump(self, filename, datatype='json.gz', **kwargs):
View Source
855    def dump(self, filename, datatype="json.gz", **kwargs):
856        """Dumps the Corr into a file of chosen type
857        Parameters
858        ----------
859        filename : str
860            Name of the file to be saved.
861        datatype : str
862            Format of the exported file. Supported formats include
863            "json.gz" and "pickle"
864        path : str
865            specifies a custom path for the file (default '.')
866        """
867        if datatype == "json.gz":
868            from .input.json import dump_to_json
869            if 'path' in kwargs:
870                file_name = kwargs.get('path') + '/' + filename
871            else:
872                file_name = filename
873            dump_to_json(self, file_name)
874        elif datatype == "pickle":
875            dump_object(self, filename, **kwargs)
876        else:
877            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 '.')
#   def print(self, range=[0, None]):
View Source
879    def print(self, range=[0, None]):
880        print(self.__repr__(range))
#   def sqrt(self):
View Source
1042    def sqrt(self):
1043        return self**0.5
#   def log(self):
View Source
1045    def log(self):
1046        newcontent = [None if (item is None) else np.log(item) for item in self.content]
1047        return Corr(newcontent, prange=self.prange)
#   def exp(self):
View Source
1049    def exp(self):
1050        newcontent = [None if (item is None) else np.exp(item) for item in self.content]
1051        return Corr(newcontent, prange=self.prange)
#   def sin(self):
View Source
1064    def sin(self):
1065        return self._apply_func_to_corr(np.sin)
#   def cos(self):
View Source
1067    def cos(self):
1068        return self._apply_func_to_corr(np.cos)
#   def tan(self):
View Source
1070    def tan(self):
1071        return self._apply_func_to_corr(np.tan)
#   def sinh(self):
View Source
1073    def sinh(self):
1074        return self._apply_func_to_corr(np.sinh)
#   def cosh(self):
View Source
1076    def cosh(self):
1077        return self._apply_func_to_corr(np.cosh)
#   def tanh(self):
View Source
1079    def tanh(self):
1080        return self._apply_func_to_corr(np.tanh)
#   def arcsin(self):
View Source
1082    def arcsin(self):
1083        return self._apply_func_to_corr(np.arcsin)
#   def arccos(self):
View Source
1085    def arccos(self):
1086        return self._apply_func_to_corr(np.arccos)
#   def arctan(self):
View Source
1088    def arctan(self):
1089        return self._apply_func_to_corr(np.arctan)
#   def arcsinh(self):
View Source
1091    def arcsinh(self):
1092        return self._apply_func_to_corr(np.arcsinh)
#   def arccosh(self):
View Source
1094    def arccosh(self):
1095        return self._apply_func_to_corr(np.arccosh)
#   def arctanh(self):
View Source
1097    def arctanh(self):
1098        return self._apply_func_to_corr(np.arctanh)
#   real
#   imag
#   def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
View Source
1133    def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1134        r''' Project large correlation matrix to lowest states
1135
1136        This method can be used to reduce the size of an (N x N) correlation matrix
1137        to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise
1138        is still small.
1139
1140        Parameters
1141        ----------
1142        Ntrunc: int
1143            Rank of the target matrix.
1144        tproj: int
1145            Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method.
1146            The default value is 3.
1147        t0proj: int
1148            Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly
1149            discouraged for O(a) improved theories, since the correctness of the procedure
1150            cannot be granted in this case. The default value is 2.
1151        basematrix : Corr
1152            Correlation matrix that is used to determine the eigenvectors of the
1153            lowest states based on a GEVP. basematrix is taken to be the Corr itself if
1154            is is not specified.
1155
1156        Notes
1157        -----
1158        We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving
1159        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}$
1160        and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the
1161        resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via
1162        $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large
1163        correlation matrix and to remove some noise that is added by irrelevant operators.
1164        This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated
1165        bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.
1166        '''
1167
1168        if self.N == 1:
1169            raise Exception('Method cannot be applied to one-dimensional correlators.')
1170        if basematrix is None:
1171            basematrix = self
1172        if Ntrunc >= basematrix.N:
1173            raise Exception('Cannot truncate using Ntrunc <= %d' % (basematrix.N))
1174        if basematrix.N != self.N:
1175            raise Exception('basematrix and targetmatrix have to be of the same size.')
1176
1177        evecs = []
1178        for i in range(Ntrunc):
1179            evecs.append(basematrix.GEVP(t0proj, tproj, state=i, sorted_list=None))
1180
1181        tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object)
1182        rmat = []
1183        for t in range(basematrix.T):
1184            for i in range(Ntrunc):
1185                for j in range(Ntrunc):
1186                    tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j]
1187            rmat.append(np.copy(tmpmat))
1188
1189        newcontent = [None if (self.content[t] is None) else rmat[t] for t in range(self.T)]
1190        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)$.