pyerrors.correlators

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

The class for a correlator (time dependent sequence of pe.Obs).

Everything, this class does, can be achieved using lists or arrays of Obs. But it is simply more convenient to have a dedicated object for correlators. One often wants to add or multiply correlators of the same length at every timeslice and it is inconvenient to iterate over all timeslices for every operation. This is especially true, when dealing with matrices.

The correlator can have two types of content: An Obs at every timeslice OR a GEVP matrix at every timeslice. Other dependency (eg. spatial) are not supported.

Corr(data_input, padding=[0, 0], prange=None)
29    def __init__(self, data_input, padding=[0, 0], prange=None):
30        """ Initialize a Corr object.
31
32        Parameters
33        ----------
34        data_input : list or array
35            list of Obs or list of arrays of Obs or array of Corrs
36        padding : list, optional
37            List with two entries where the first labels the padding
38            at the front of the correlator and the second the padding
39            at the back.
40        prange : list, optional
41            List containing the first and last timeslice of the plateau
42            region indentified for this correlator.
43        """
44
45        if isinstance(data_input, np.ndarray):
46
47            # This only works, if the array fulfills the conditions below
48            if not len(data_input.shape) == 2 and data_input.shape[0] == data_input.shape[1]:
49                raise Exception("Incompatible array shape")
50            if not all([isinstance(item, Corr) for item in data_input.flatten()]):
51                raise Exception("If the input is an array, its elements must be of type pe.Corr")
52            if not all([item.N == 1 for item in data_input.flatten()]):
53                raise Exception("Can only construct matrix correlator from single valued correlators")
54            if not len(set([item.T for item in data_input.flatten()])) == 1:
55                raise Exception("All input Correlators must be defined over the same timeslices.")
56
57            T = data_input[0, 0].T
58            N = data_input.shape[0]
59            input_as_list = []
60            for t in range(T):
61                if any([(item.content[t] is None) for item in data_input.flatten()]):
62                    if not all([(item.content[t] is None) for item in data_input.flatten()]):
63                        warnings.warn("Input ill-defined at different timeslices. Conversion leads to data loss!", RuntimeWarning)
64                    input_as_list.append(None)
65                else:
66                    array_at_timeslace = np.empty([N, N], dtype="object")
67                    for i in range(N):
68                        for j in range(N):
69                            array_at_timeslace[i, j] = data_input[i, j][t]
70                    input_as_list.append(array_at_timeslace)
71            data_input = input_as_list
72
73        if isinstance(data_input, list):
74
75            if all([isinstance(item, (Obs, CObs)) or item is None for item in data_input]):
76                _assert_equal_properties([o for o in data_input if o is not None])
77                self.content = [np.asarray([item]) if item is not None else None for item in data_input]
78                self.N = 1
79
80            elif all([isinstance(item, np.ndarray) or item is None for item in data_input]) and any([isinstance(item, np.ndarray) for item in data_input]):
81                self.content = data_input
82                noNull = [a for a in self.content if not (a is None)]  # To check if the matrices are correct for all undefined elements
83                self.N = noNull[0].shape[0]
84                if self.N > 1 and noNull[0].shape[0] != noNull[0].shape[1]:
85                    raise Exception("Smearing matrices are not NxN")
86                if (not all([item.shape == noNull[0].shape for item in noNull])):
87                    raise Exception("Items in data_input are not of identical shape." + str(noNull))
88            else:
89                raise Exception("data_input contains item of wrong type")
90        else:
91            raise Exception("Data input was not given as list or correct array")
92
93        self.tag = None
94
95        # An undefined timeslice is represented by the None object
96        self.content = [None] * padding[0] + self.content + [None] * padding[1]
97        self.T = len(self.content)
98        self.prange = prange

Initialize a Corr object.

Parameters
  • data_input (list or array): list of Obs or list of arrays of Obs or array of Corrs
  • padding (list, optional): List with two entries where the first labels the padding at the front of the correlator and the second the padding at the back.
  • prange (list, optional): List containing the first and last timeslice of the plateau region indentified for this correlator.
tag
content
T
prange
reweighted
def gamma_method(self, **kwargs):
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 gm(self, **kwargs):
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):
132    def projected(self, vector_l=None, vector_r=None, normalize=False):
133        """We need to project the Correlator with a Vector to get a single value at each timeslice.
134
135        The method can use one or two vectors.
136        If two are specified it returns v1@G@v2 (the order might be very important.)
137        By default it will return the lowest source, which usually means unsmeared-unsmeared (0,0), but it does not have to
138        """
139        if self.N == 1:
140            raise Exception("Trying to project a Corr, that already has N=1.")
141
142        if vector_l is None:
143            vector_l, vector_r = np.asarray([1.] + (self.N - 1) * [0.]), np.asarray([1.] + (self.N - 1) * [0.])
144        elif (vector_r is None):
145            vector_r = vector_l
146        if isinstance(vector_l, list) and not isinstance(vector_r, list):
147            if len(vector_l) != self.T:
148                raise Exception("Length of vector list must be equal to T")
149            vector_r = [vector_r] * self.T
150        if isinstance(vector_r, list) and not isinstance(vector_l, list):
151            if len(vector_r) != self.T:
152                raise Exception("Length of vector list must be equal to T")
153            vector_l = [vector_l] * self.T
154
155        if not isinstance(vector_l, list):
156            if not vector_l.shape == vector_r.shape == (self.N,):
157                raise Exception("Vectors are of wrong shape!")
158            if normalize:
159                vector_l, vector_r = vector_l / np.sqrt((vector_l @ vector_l)), vector_r / np.sqrt(vector_r @ vector_r)
160            newcontent = [None if _check_for_none(self, item) else np.asarray([vector_l.T @ item @ vector_r]) for item in self.content]
161
162        else:
163            # There are no checks here yet. There are so many possible scenarios, where this can go wrong.
164            if normalize:
165                for t in range(self.T):
166                    vector_l[t], vector_r[t] = vector_l[t] / np.sqrt((vector_l[t] @ vector_l[t])), vector_r[t] / np.sqrt(vector_r[t] @ vector_r[t])
167
168            newcontent = [None if (_check_for_none(self, self.content[t]) or vector_l[t] is None or vector_r[t] is None) else np.asarray([vector_l[t].T @ self.content[t] @ vector_r[t]]) for t in range(self.T)]
169        return Corr(newcontent)

We need to project the Correlator with a Vector to get a single value at each timeslice.

The method can use one or two vectors. If two are specified it returns v1@G@v2 (the order might be very important.) By default it will return the lowest source, which usually means unsmeared-unsmeared (0,0), but it does not have to

def item(self, i, j):
171    def item(self, i, j):
172        """Picks the element [i,j] from every matrix and returns a correlator containing one Obs per timeslice.
173
174        Parameters
175        ----------
176        i : int
177            First index to be picked.
178        j : int
179            Second index to be picked.
180        """
181        if self.N == 1:
182            raise Exception("Trying to pick item from projected Corr")
183        newcontent = [None if (item is None) else item[i, j] for item in self.content]
184        return Corr(newcontent)

Picks the element [i,j] from every matrix and returns a correlator containing one Obs per timeslice.

Parameters
  • i (int): First index to be picked.
  • j (int): Second index to be picked.
def plottable(self):
186    def plottable(self):
187        """Outputs the correlator in a plotable format.
188
189        Outputs three lists containing the timeslice index, the value on each
190        timeslice and the error on each timeslice.
191        """
192        if self.N != 1:
193            raise Exception("Can only make Corr[N=1] plottable")
194        x_list = [x for x in range(self.T) if not self.content[x] is None]
195        y_list = [y[0].value for y in self.content if y is not None]
196        y_err_list = [y[0].dvalue for y in self.content if y is not None]
197
198        return x_list, y_list, y_err_list

Outputs the correlator in a plotable format.

Outputs three lists containing the timeslice index, the value on each timeslice and the error on each timeslice.

def symmetric(self):
200    def symmetric(self):
201        """ Symmetrize the correlator around x0=0."""
202        if self.N != 1:
203            raise Exception('symmetric cannot be safely applied to multi-dimensional correlators.')
204        if self.T % 2 != 0:
205            raise Exception("Can not symmetrize odd T")
206
207        if self.content[0] is not None:
208            if np.argmax(np.abs([o[0].value if o is not None else 0 for o in self.content])) != 0:
209                warnings.warn("Correlator does not seem to be symmetric around x0=0.", RuntimeWarning)
210
211        newcontent = [self.content[0]]
212        for t in range(1, self.T):
213            if (self.content[t] is None) or (self.content[self.T - t] is None):
214                newcontent.append(None)
215            else:
216                newcontent.append(0.5 * (self.content[t] + self.content[self.T - t]))
217        if (all([x is None for x in newcontent])):
218            raise Exception("Corr could not be symmetrized: No redundant values")
219        return Corr(newcontent, prange=self.prange)

Symmetrize the correlator around x0=0.

def anti_symmetric(self):
221    def anti_symmetric(self):
222        """Anti-symmetrize the correlator around x0=0."""
223        if self.N != 1:
224            raise Exception('anti_symmetric cannot be safely applied to multi-dimensional correlators.')
225        if self.T % 2 != 0:
226            raise Exception("Can not symmetrize odd T")
227
228        test = 1 * self
229        test.gamma_method()
230        if not all([o.is_zero_within_error(3) for o in test.content[0]]):
231            warnings.warn("Correlator does not seem to be anti-symmetric around x0=0.", RuntimeWarning)
232
233        newcontent = [self.content[0]]
234        for t in range(1, self.T):
235            if (self.content[t] is None) or (self.content[self.T - t] is None):
236                newcontent.append(None)
237            else:
238                newcontent.append(0.5 * (self.content[t] - self.content[self.T - t]))
239        if (all([x is None for x in newcontent])):
240            raise Exception("Corr could not be symmetrized: No redundant values")
241        return Corr(newcontent, prange=self.prange)

Anti-symmetrize the correlator around x0=0.

def is_matrix_symmetric(self):
243    def is_matrix_symmetric(self):
244        """Checks whether a correlator matrices is symmetric on every timeslice."""
245        if self.N == 1:
246            raise Exception("Only works for correlator matrices.")
247        for t in range(self.T):
248            if self[t] is None:
249                continue
250            for i in range(self.N):
251                for j in range(i + 1, self.N):
252                    if self[t][i, j] is self[t][j, i]:
253                        continue
254                    if hash(self[t][i, j]) != hash(self[t][j, i]):
255                        return False
256        return True

Checks whether a correlator matrices is symmetric on every timeslice.

def matrix_symmetric(self):
258    def matrix_symmetric(self):
259        """Symmetrizes the correlator matrices on every timeslice."""
260        if self.N == 1:
261            raise Exception("Trying to symmetrize a correlator matrix, that already has N=1.")
262        if self.is_matrix_symmetric():
263            return 1.0 * self
264        else:
265            transposed = [None if _check_for_none(self, G) else G.T for G in self.content]
266            return 0.5 * (Corr(transposed) + self)

Symmetrizes the correlator matrices on every timeslice.

def GEVP(self, t0, ts=None, sort='Eigenvalue', **kwargs):
268    def GEVP(self, t0, ts=None, sort="Eigenvalue", **kwargs):
269        r'''Solve the generalized eigenvalue problem on the correlator matrix and returns the corresponding eigenvectors.
270
271        The eigenvectors are sorted according to the descending eigenvalues, the zeroth eigenvector(s) correspond to the
272        largest eigenvalue(s). The eigenvector(s) for the individual states can be accessed via slicing
273        ```python
274        C.GEVP(t0=2)[0]  # Ground state vector(s)
275        C.GEVP(t0=2)[:3]  # Vectors for the lowest three states
276        ```
277
278        Parameters
279        ----------
280        t0 : int
281            The time t0 for the right hand side of the GEVP according to $G(t)v_i=\lambda_i G(t_0)v_i$
282        ts : int
283            fixed time $G(t_s)v_i=\lambda_i G(t_0)v_i$ if sort=None.
284            If sort="Eigenvector" it gives a reference point for the sorting method.
285        sort : string
286            If this argument is set, a list of self.T vectors per state is returned. If it is set to None, only one vector is returned.
287            - "Eigenvalue": The eigenvector is chosen according to which eigenvalue it belongs individually on every timeslice.
288            - "Eigenvector": Use the method described in arXiv:2004.10472 to find the set of v(t) belonging to the state.
289              The reference state is identified by its eigenvalue at $t=t_s$.
290
291        Other Parameters
292        ----------------
293        state : int
294           Returns only the vector(s) for a specified state. The lowest state is zero.
295        '''
296
297        if self.N == 1:
298            raise Exception("GEVP methods only works on correlator matrices and not single correlators.")
299        if ts is not None:
300            if (ts <= t0):
301                raise Exception("ts has to be larger than t0.")
302
303        if "sorted_list" in kwargs:
304            warnings.warn("Argument 'sorted_list' is deprecated, use 'sort' instead.", DeprecationWarning)
305            sort = kwargs.get("sorted_list")
306
307        if self.is_matrix_symmetric():
308            symmetric_corr = self
309        else:
310            symmetric_corr = self.matrix_symmetric()
311
312        G0 = np.vectorize(lambda x: x.value)(symmetric_corr[t0])
313        np.linalg.cholesky(G0)  # Check if matrix G0 is positive-semidefinite.
314
315        if sort is None:
316            if (ts is None):
317                raise Exception("ts is required if sort=None.")
318            if (self.content[t0] is None) or (self.content[ts] is None):
319                raise Exception("Corr not defined at t0/ts.")
320            Gt = np.vectorize(lambda x: x.value)(symmetric_corr[ts])
321            reordered_vecs = _GEVP_solver(Gt, G0)
322
323        elif sort in ["Eigenvalue", "Eigenvector"]:
324            if sort == "Eigenvalue" and ts is not None:
325                warnings.warn("ts has no effect when sorting by eigenvalue is chosen.", RuntimeWarning)
326            all_vecs = [None] * (t0 + 1)
327            for t in range(t0 + 1, self.T):
328                try:
329                    Gt = np.vectorize(lambda x: x.value)(symmetric_corr[t])
330                    all_vecs.append(_GEVP_solver(Gt, G0))
331                except Exception:
332                    all_vecs.append(None)
333            if sort == "Eigenvector":
334                if ts is None:
335                    raise Exception("ts is required for the Eigenvector sorting method.")
336                all_vecs = _sort_vectors(all_vecs, ts)
337
338            reordered_vecs = [[v[s] if v is not None else None for v in all_vecs] for s in range(self.N)]
339        else:
340            raise Exception("Unkown value for 'sort'.")
341
342        if "state" in kwargs:
343            return reordered_vecs[kwargs.get("state")]
344        else:
345            return reordered_vecs

Solve the generalized eigenvalue problem on the correlator matrix and returns the corresponding eigenvectors.

The eigenvectors are sorted according to the descending eigenvalues, the zeroth eigenvector(s) correspond to the largest eigenvalue(s). The eigenvector(s) for the individual states can be accessed via slicing

C.GEVP(t0=2)[0]  # Ground state vector(s)
C.GEVP(t0=2)[:3]  # Vectors for the lowest three states
Parameters
  • t0 (int): The time t0 for the right hand side of the GEVP according to $G(t)v_i=\lambda_i G(t_0)v_i$
  • ts (int): fixed time $G(t_s)v_i=\lambda_i G(t_0)v_i$ if sort=None. If sort="Eigenvector" it gives a reference point for the sorting method.
  • sort (string): If this argument is set, a list of self.T vectors per state is returned. If it is set to None, only one vector is returned.
    • "Eigenvalue": The eigenvector is chosen according to which eigenvalue it belongs individually on every timeslice.
    • "Eigenvector": Use the method described in arXiv:2004.10472 to find the set of v(t) belonging to the state. The reference state is identified by its eigenvalue at $t=t_s$.
Other Parameters
  • state (int): Returns only the vector(s) for a specified state. The lowest state is zero.
def Eigenvalue(self, t0, ts=None, state=0, sort='Eigenvalue'):
347    def Eigenvalue(self, t0, ts=None, state=0, sort="Eigenvalue"):
348        """Determines the eigenvalue of the GEVP by solving and projecting the correlator
349
350        Parameters
351        ----------
352        state : int
353            The state one is interested in ordered by energy. The lowest state is zero.
354
355        All other parameters are identical to the ones of Corr.GEVP.
356        """
357        vec = self.GEVP(t0, ts=ts, sort=sort)[state]
358        return self.projected(vec)

Determines the eigenvalue of the GEVP by solving and projecting the correlator

Parameters
  • state (int): The state one is interested in ordered by energy. The lowest state is zero.
  • All other parameters are identical to the ones of Corr.GEVP.
def Hankel(self, N, periodic=False):
360    def Hankel(self, N, periodic=False):
361        """Constructs an NxN Hankel matrix
362
363        C(t) c(t+1) ... c(t+n-1)
364        C(t+1) c(t+2) ... c(t+n)
365        .................
366        C(t+(n-1)) c(t+n) ... c(t+2(n-1))
367
368        Parameters
369        ----------
370        N : int
371            Dimension of the Hankel matrix
372        periodic : bool, optional
373            determines whether the matrix is extended periodically
374        """
375
376        if self.N != 1:
377            raise Exception("Multi-operator Prony not implemented!")
378
379        array = np.empty([N, N], dtype="object")
380        new_content = []
381        for t in range(self.T):
382            new_content.append(array.copy())
383
384        def wrap(i):
385            while i >= self.T:
386                i -= self.T
387            return i
388
389        for t in range(self.T):
390            for i in range(N):
391                for j in range(N):
392                    if periodic:
393                        new_content[t][i, j] = self.content[wrap(t + i + j)][0]
394                    elif (t + i + j) >= self.T:
395                        new_content[t] = None
396                    else:
397                        new_content[t][i, j] = self.content[t + i + j][0]
398
399        return Corr(new_content)

Constructs an NxN Hankel matrix

C(t) c(t+1) ... c(t+n-1) C(t+1) c(t+2) ... c(t+n) ................. C(t+(n-1)) c(t+n) ... c(t+2(n-1))

Parameters
  • N (int): Dimension of the Hankel matrix
  • periodic (bool, optional): determines whether the matrix is extended periodically
def roll(self, dt):
401    def roll(self, dt):
402        """Periodically shift the correlator by dt timeslices
403
404        Parameters
405        ----------
406        dt : int
407            number of timeslices
408        """
409        return Corr(list(np.roll(np.array(self.content, dtype=object), dt)))

Periodically shift the correlator by dt timeslices

Parameters
  • dt (int): number of timeslices
def reverse(self):
411    def reverse(self):
412        """Reverse the time ordering of the Corr"""
413        return Corr(self.content[:: -1])

Reverse the time ordering of the Corr

def thin(self, spacing=2, offset=0):
415    def thin(self, spacing=2, offset=0):
416        """Thin out a correlator to suppress correlations
417
418        Parameters
419        ----------
420        spacing : int
421            Keep only every 'spacing'th entry of the correlator
422        offset : int
423            Offset the equal spacing
424        """
425        new_content = []
426        for t in range(self.T):
427            if (offset + t) % spacing != 0:
428                new_content.append(None)
429            else:
430                new_content.append(self.content[t])
431        return Corr(new_content)

Thin out a correlator to suppress correlations

Parameters
  • spacing (int): Keep only every 'spacing'th entry of the correlator
  • offset (int): Offset the equal spacing
def correlate(self, partner):
433    def correlate(self, partner):
434        """Correlate the correlator with another correlator or Obs
435
436        Parameters
437        ----------
438        partner : Obs or Corr
439            partner to correlate the correlator with.
440            Can either be an Obs which is correlated with all entries of the
441            correlator or a Corr of same length.
442        """
443        if self.N != 1:
444            raise Exception("Only one-dimensional correlators can be safely correlated.")
445        new_content = []
446        for x0, t_slice in enumerate(self.content):
447            if _check_for_none(self, t_slice):
448                new_content.append(None)
449            else:
450                if isinstance(partner, Corr):
451                    if _check_for_none(partner, partner.content[x0]):
452                        new_content.append(None)
453                    else:
454                        new_content.append(np.array([correlate(o, partner.content[x0][0]) for o in t_slice]))
455                elif isinstance(partner, Obs):  # Should this include CObs?
456                    new_content.append(np.array([correlate(o, partner) for o in t_slice]))
457                else:
458                    raise Exception("Can only correlate with an Obs or a Corr.")
459
460        return Corr(new_content)

Correlate the correlator with another correlator or Obs

Parameters
  • partner (Obs or Corr): partner to correlate the correlator with. Can either be an Obs which is correlated with all entries of the correlator or a Corr of same length.
def reweight(self, weight, **kwargs):
462    def reweight(self, weight, **kwargs):
463        """Reweight the correlator.
464
465        Parameters
466        ----------
467        weight : Obs
468            Reweighting factor. An Observable that has to be defined on a superset of the
469            configurations in obs[i].idl for all i.
470        all_configs : bool
471            if True, the reweighted observables are normalized by the average of
472            the reweighting factor on all configurations in weight.idl and not
473            on the configurations in obs[i].idl.
474        """
475        if self.N != 1:
476            raise Exception("Reweighting only implemented for one-dimensional correlators.")
477        new_content = []
478        for t_slice in self.content:
479            if _check_for_none(self, t_slice):
480                new_content.append(None)
481            else:
482                new_content.append(np.array(reweight(weight, t_slice, **kwargs)))
483        return Corr(new_content)

Reweight the correlator.

Parameters
  • weight (Obs): Reweighting factor. An Observable that has to be defined on a superset of the configurations in obs[i].idl for all i.
  • all_configs (bool): if True, the reweighted observables are normalized by the average of the reweighting factor on all configurations in weight.idl and not on the configurations in obs[i].idl.
def T_symmetry(self, partner, parity=1):
485    def T_symmetry(self, partner, parity=+1):
486        """Return the time symmetry average of the correlator and its partner
487
488        Parameters
489        ----------
490        partner : Corr
491            Time symmetry partner of the Corr
492        partity : int
493            Parity quantum number of the correlator, can be +1 or -1
494        """
495        if self.N != 1:
496            raise Exception("T_symmetry only implemented for one-dimensional correlators.")
497        if not isinstance(partner, Corr):
498            raise Exception("T partner has to be a Corr object.")
499        if parity not in [+1, -1]:
500            raise Exception("Parity has to be +1 or -1.")
501        T_partner = parity * partner.reverse()
502
503        t_slices = []
504        test = (self - T_partner)
505        test.gamma_method()
506        for x0, t_slice in enumerate(test.content):
507            if t_slice is not None:
508                if not t_slice[0].is_zero_within_error(5):
509                    t_slices.append(x0)
510        if t_slices:
511            warnings.warn("T symmetry partners do not agree within 5 sigma on time slices " + str(t_slices) + ".", RuntimeWarning)
512
513        return (self + T_partner) / 2

Return the time symmetry average of the correlator and its partner

Parameters
  • partner (Corr): Time symmetry partner of the Corr
  • partity (int): Parity quantum number of the correlator, can be +1 or -1
def deriv(self, variant='symmetric'):
515    def deriv(self, variant="symmetric"):
516        """Return the first derivative of the correlator with respect to x0.
517
518        Parameters
519        ----------
520        variant : str
521            decides which definition of the finite differences derivative is used.
522            Available choice: symmetric, forward, backward, improved, log, default: symmetric
523        """
524        if self.N != 1:
525            raise Exception("deriv only implemented for one-dimensional correlators.")
526        if variant == "symmetric":
527            newcontent = []
528            for t in range(1, self.T - 1):
529                if (self.content[t - 1] is None) or (self.content[t + 1] is None):
530                    newcontent.append(None)
531                else:
532                    newcontent.append(0.5 * (self.content[t + 1] - self.content[t - 1]))
533            if (all([x is None for x in newcontent])):
534                raise Exception('Derivative is undefined at all timeslices')
535            return Corr(newcontent, padding=[1, 1])
536        elif variant == "forward":
537            newcontent = []
538            for t in range(self.T - 1):
539                if (self.content[t] is None) or (self.content[t + 1] is None):
540                    newcontent.append(None)
541                else:
542                    newcontent.append(self.content[t + 1] - self.content[t])
543            if (all([x is None for x in newcontent])):
544                raise Exception("Derivative is undefined at all timeslices")
545            return Corr(newcontent, padding=[0, 1])
546        elif variant == "backward":
547            newcontent = []
548            for t in range(1, self.T):
549                if (self.content[t - 1] is None) or (self.content[t] is None):
550                    newcontent.append(None)
551                else:
552                    newcontent.append(self.content[t] - self.content[t - 1])
553            if (all([x is None for x in newcontent])):
554                raise Exception("Derivative is undefined at all timeslices")
555            return Corr(newcontent, padding=[1, 0])
556        elif variant == "improved":
557            newcontent = []
558            for t in range(2, self.T - 2):
559                if (self.content[t - 2] is None) or (self.content[t - 1] is None) or (self.content[t + 1] is None) or (self.content[t + 2] is None):
560                    newcontent.append(None)
561                else:
562                    newcontent.append((1 / 12) * (self.content[t - 2] - 8 * self.content[t - 1] + 8 * self.content[t + 1] - self.content[t + 2]))
563            if (all([x is None for x in newcontent])):
564                raise Exception('Derivative is undefined at all timeslices')
565            return Corr(newcontent, padding=[2, 2])
566        elif variant == 'log':
567            newcontent = []
568            for t in range(self.T):
569                if (self.content[t] is None) or (self.content[t] <= 0):
570                    newcontent.append(None)
571                else:
572                    newcontent.append(np.log(self.content[t]))
573            if (all([x is None for x in newcontent])):
574                raise Exception("Log is undefined at all timeslices")
575            logcorr = Corr(newcontent)
576            return self * logcorr.deriv('symmetric')
577        else:
578            raise Exception("Unknown variant.")

Return the first derivative of the correlator with respect to x0.

Parameters
  • variant (str): decides which definition of the finite differences derivative is used. Available choice: symmetric, forward, backward, improved, log, default: symmetric
def second_deriv(self, variant='symmetric'):
580    def second_deriv(self, variant="symmetric"):
581        r"""Return the second derivative of the correlator with respect to x0.
582
583        Parameters
584        ----------
585        variant : str
586            decides which definition of the finite differences derivative is used.
587            Available choice:
588                - symmetric (default)
589                    $$\tilde{\partial}^2_0 f(x_0) = f(x_0+1)-2f(x_0)+f(x_0-1)$$
590                - big_symmetric
591                    $$\partial^2_0 f(x_0) = \frac{f(x_0+2)-2f(x_0)+f(x_0-2)}{4}$$
592                - improved
593                    $$\partial^2_0 f(x_0) = \frac{-f(x_0+2) + 16 * f(x_0+1) - 30 * f(x_0) + 16 * f(x_0-1) - f(x_0-2)}{12}$$
594                - log
595                    $$f(x) = \tilde{\partial}^2_0 log(f(x_0))+(\tilde{\partial}_0 log(f(x_0)))^2$$
596        """
597        if self.N != 1:
598            raise Exception("second_deriv only implemented for one-dimensional correlators.")
599        if variant == "symmetric":
600            newcontent = []
601            for t in range(1, self.T - 1):
602                if (self.content[t - 1] is None) or (self.content[t + 1] is None):
603                    newcontent.append(None)
604                else:
605                    newcontent.append((self.content[t + 1] - 2 * self.content[t] + self.content[t - 1]))
606            if (all([x is None for x in newcontent])):
607                raise Exception("Derivative is undefined at all timeslices")
608            return Corr(newcontent, padding=[1, 1])
609        elif variant == "big_symmetric":
610            newcontent = []
611            for t in range(2, self.T - 2):
612                if (self.content[t - 2] is None) or (self.content[t + 2] is None):
613                    newcontent.append(None)
614                else:
615                    newcontent.append((self.content[t + 2] - 2 * self.content[t] + self.content[t - 2]) / 4)
616            if (all([x is None for x in newcontent])):
617                raise Exception("Derivative is undefined at all timeslices")
618            return Corr(newcontent, padding=[2, 2])
619        elif variant == "improved":
620            newcontent = []
621            for t in range(2, self.T - 2):
622                if (self.content[t - 2] is None) or (self.content[t - 1] is None) or (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 2] is None):
623                    newcontent.append(None)
624                else:
625                    newcontent.append((1 / 12) * (-self.content[t + 2] + 16 * self.content[t + 1] - 30 * self.content[t] + 16 * self.content[t - 1] - self.content[t - 2]))
626            if (all([x is None for x in newcontent])):
627                raise Exception("Derivative is undefined at all timeslices")
628            return Corr(newcontent, padding=[2, 2])
629        elif variant == 'log':
630            newcontent = []
631            for t in range(self.T):
632                if (self.content[t] is None) or (self.content[t] <= 0):
633                    newcontent.append(None)
634                else:
635                    newcontent.append(np.log(self.content[t]))
636            if (all([x is None for x in newcontent])):
637                raise Exception("Log is undefined at all timeslices")
638            logcorr = Corr(newcontent)
639            return self * (logcorr.second_deriv('symmetric') + (logcorr.deriv('symmetric'))**2)
640        else:
641            raise Exception("Unknown variant.")

Return the second derivative of the correlator with respect to x0.

Parameters
  • variant (str): decides which definition of the finite differences derivative is used. Available choice: - symmetric (default) $$\tilde{\partial}^2_0 f(x_0) = f(x_0+1)-2f(x_0)+f(x_0-1)$$ - big_symmetric $$\partial^2_0 f(x_0) = \frac{f(x_0+2)-2f(x_0)+f(x_0-2)}{4}$$ - improved $$\partial^2_0 f(x_0) = \frac{-f(x_0+2) + 16 * f(x_0+1) - 30 * f(x_0) + 16 * f(x_0-1) - f(x_0-2)}{12}$$ - log $$f(x) = \tilde{\partial}^2_0 log(f(x_0))+(\tilde{\partial}_0 log(f(x_0)))^2$$
def m_eff(self, variant='log', guess=1.0):
643    def m_eff(self, variant='log', guess=1.0):
644        """Returns the effective mass of the correlator as correlator object
645
646        Parameters
647        ----------
648        variant : str
649            log : uses the standard effective mass log(C(t) / C(t+1))
650            cosh, periodic : Use periodicitiy of the correlator by solving C(t) / C(t+1) = cosh(m * (t - T/2)) / cosh(m * (t + 1 - T/2)) for m.
651            sinh : Use anti-periodicitiy of the correlator by solving C(t) / C(t+1) = sinh(m * (t - T/2)) / sinh(m * (t + 1 - T/2)) for m.
652            See, e.g., arXiv:1205.5380
653            arccosh : Uses the explicit form of the symmetrized correlator (not recommended)
654            logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2
655        guess : float
656            guess for the root finder, only relevant for the root variant
657        """
658        if self.N != 1:
659            raise Exception('Correlator must be projected before getting m_eff')
660        if variant == 'log':
661            newcontent = []
662            for t in range(self.T - 1):
663                if ((self.content[t] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0):
664                    newcontent.append(None)
665                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
666                    newcontent.append(None)
667                else:
668                    newcontent.append(self.content[t] / self.content[t + 1])
669            if (all([x is None for x in newcontent])):
670                raise Exception('m_eff is undefined at all timeslices')
671
672            return np.log(Corr(newcontent, padding=[0, 1]))
673
674        elif variant == 'logsym':
675            newcontent = []
676            for t in range(1, self.T - 1):
677                if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0):
678                    newcontent.append(None)
679                elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0:
680                    newcontent.append(None)
681                else:
682                    newcontent.append(self.content[t - 1] / self.content[t + 1])
683            if (all([x is None for x in newcontent])):
684                raise Exception('m_eff is undefined at all timeslices')
685
686            return np.log(Corr(newcontent, padding=[1, 1])) / 2
687
688        elif variant in ['periodic', 'cosh', 'sinh']:
689            if variant in ['periodic', 'cosh']:
690                func = anp.cosh
691            else:
692                func = anp.sinh
693
694            def root_function(x, d):
695                return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d
696
697            newcontent = []
698            for t in range(self.T - 1):
699                if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0):
700                    newcontent.append(None)
701                # Fill the two timeslices in the middle of the lattice with their predecessors
702                elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]:
703                    newcontent.append(newcontent[-1])
704                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
705                    newcontent.append(None)
706                else:
707                    newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess)))
708            if (all([x is None for x in newcontent])):
709                raise Exception('m_eff is undefined at all timeslices')
710
711            return Corr(newcontent, padding=[0, 1])
712
713        elif variant == 'arccosh':
714            newcontent = []
715            for t in range(1, self.T - 1):
716                if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t - 1] is None) or (self.content[t][0].value == 0):
717                    newcontent.append(None)
718                else:
719                    newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t]))
720            if (all([x is None for x in newcontent])):
721                raise Exception("m_eff is undefined at all timeslices")
722            return np.arccosh(Corr(newcontent, padding=[1, 1]))
723
724        else:
725            raise Exception('Unknown variant.')

Returns the effective mass of the correlator as correlator object

Parameters
  • variant (str): log : uses the standard effective mass log(C(t) / C(t+1)) cosh, periodic : Use periodicitiy of the correlator by solving C(t) / C(t+1) = cosh(m * (t - T/2)) / cosh(m * (t + 1 - T/2)) for m. sinh : Use anti-periodicitiy of the correlator by solving C(t) / C(t+1) = sinh(m * (t - T/2)) / sinh(m * (t + 1 - T/2)) for m. See, e.g., arXiv:1205.5380 arccosh : Uses the explicit form of the symmetrized correlator (not recommended) logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2
  • guess (float): guess for the root finder, only relevant for the root variant
def fit(self, function, fitrange=None, silent=False, **kwargs):
727    def fit(self, function, fitrange=None, silent=False, **kwargs):
728        r'''Fits function to the data
729
730        Parameters
731        ----------
732        function : obj
733            function to fit to the data. See fits.least_squares for details.
734        fitrange : list
735            Two element list containing the timeslices on which the fit is supposed to start and stop.
736            Caution: This range is inclusive as opposed to standard python indexing.
737            `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6.
738            If not specified, self.prange or all timeslices are used.
739        silent : bool
740            Decides whether output is printed to the standard output.
741        '''
742        if self.N != 1:
743            raise Exception("Correlator must be projected before fitting")
744
745        if fitrange is None:
746            if self.prange:
747                fitrange = self.prange
748            else:
749                fitrange = [0, self.T - 1]
750        else:
751            if not isinstance(fitrange, list):
752                raise Exception("fitrange has to be a list with two elements")
753            if len(fitrange) != 2:
754                raise Exception("fitrange has to have exactly two elements [fit_start, fit_stop]")
755
756        xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None])
757        ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None])
758        result = least_squares(xs, ys, function, silent=silent, **kwargs)
759        return result

Fits function to the data

Parameters
  • function (obj): function to fit to the data. See fits.least_squares for details.
  • fitrange (list): Two element list containing the timeslices on which the fit is supposed to start and stop. Caution: This range is inclusive as opposed to standard python indexing. fitrange=[4, 6] corresponds to the three entries 4, 5 and 6. If not specified, self.prange or all timeslices are used.
  • silent (bool): Decides whether output is printed to the standard output.
def plateau(self, plateau_range=None, method='fit', auto_gamma=False):
761    def plateau(self, plateau_range=None, method="fit", auto_gamma=False):
762        """ Extract a plateau value from a Corr object
763
764        Parameters
765        ----------
766        plateau_range : list
767            list with two entries, indicating the first and the last timeslice
768            of the plateau region.
769        method : str
770            method to extract the plateau.
771                'fit' fits a constant to the plateau region
772                'avg', 'average' or 'mean' just average over the given timeslices.
773        auto_gamma : bool
774            apply gamma_method with default parameters to the Corr. Defaults to None
775        """
776        if not plateau_range:
777            if self.prange:
778                plateau_range = self.prange
779            else:
780                raise Exception("no plateau range provided")
781        if self.N != 1:
782            raise Exception("Correlator must be projected before getting a plateau.")
783        if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])):
784            raise Exception("plateau is undefined at all timeslices in plateaurange.")
785        if auto_gamma:
786            self.gamma_method()
787        if method == "fit":
788            def const_func(a, t):
789                return a[0]
790            return self.fit(const_func, plateau_range)[0]
791        elif method in ["avg", "average", "mean"]:
792            returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None])
793            return returnvalue
794
795        else:
796            raise Exception("Unsupported plateau method: " + method)

Extract a plateau value from a Corr object

Parameters
  • plateau_range (list): list with two entries, indicating the first and the last timeslice of the plateau region.
  • method (str): method to extract the plateau. 'fit' fits a constant to the plateau region 'avg', 'average' or 'mean' just average over the given timeslices.
  • auto_gamma (bool): apply gamma_method with default parameters to the Corr. Defaults to None
def set_prange(self, prange):
798    def set_prange(self, prange):
799        """Sets the attribute prange of the Corr object."""
800        if not len(prange) == 2:
801            raise Exception("prange must be a list or array with two values")
802        if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))):
803            raise Exception("Start and end point must be integers")
804        if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] < prange[1]):
805            raise Exception("Start and end point must define a range in the interval 0,T")
806
807        self.prange = prange
808        return

Sets the attribute prange of the Corr object.

def show( self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, fit_key=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None):
810    def show(self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, fit_key=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None):
811        """Plots the correlator using the tag of the correlator as label if available.
812
813        Parameters
814        ----------
815        x_range : list
816            list of two values, determining the range of the x-axis e.g. [4, 8].
817        comp : Corr or list of Corr
818            Correlator or list of correlators which are plotted for comparison.
819            The tags of these correlators are used as labels if available.
820        logscale : bool
821            Sets y-axis to logscale.
822        plateau : Obs
823            Plateau value to be visualized in the figure.
824        fit_res : Fit_result
825            Fit_result object to be visualized.
826        fit_key : str
827            Key for the fit function in Fit_result.fit_function (for combined fits).
828        ylabel : str
829            Label for the y-axis.
830        save : str
831            path to file in which the figure should be saved.
832        auto_gamma : bool
833            Apply the gamma method with standard parameters to all correlators and plateau values before plotting.
834        hide_sigma : float
835            Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors.
836        references : list
837            List of floating point values that are displayed as horizontal lines for reference.
838        title : string
839            Optional title of the figure.
840        """
841        if self.N != 1:
842            raise Exception("Correlator must be projected before plotting")
843
844        if auto_gamma:
845            self.gamma_method()
846
847        if x_range is None:
848            x_range = [0, self.T - 1]
849
850        fig = plt.figure()
851        ax1 = fig.add_subplot(111)
852
853        x, y, y_err = self.plottable()
854        if hide_sigma:
855            hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1
856        else:
857            hide_from = None
858        ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag)
859        if logscale:
860            ax1.set_yscale('log')
861        else:
862            if y_range is None:
863                try:
864                    y_min = min([(x[0].value - x[0].dvalue) for x in self.content[x_range[0]: x_range[1] + 1] if (x is not None) and x[0].dvalue < 2 * np.abs(x[0].value)])
865                    y_max = max([(x[0].value + x[0].dvalue) for x in self.content[x_range[0]: x_range[1] + 1] if (x is not None) and x[0].dvalue < 2 * np.abs(x[0].value)])
866                    ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)])
867                except Exception:
868                    pass
869            else:
870                ax1.set_ylim(y_range)
871        if comp:
872            if isinstance(comp, (Corr, list)):
873                for corr in comp if isinstance(comp, list) else [comp]:
874                    if auto_gamma:
875                        corr.gamma_method()
876                    x, y, y_err = corr.plottable()
877                    if hide_sigma:
878                        hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1
879                    else:
880                        hide_from = None
881                    ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor'])
882            else:
883                raise Exception("'comp' must be a correlator or a list of correlators.")
884
885        if plateau:
886            if isinstance(plateau, Obs):
887                if auto_gamma:
888                    plateau.gamma_method()
889                ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau))
890                ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-')
891            else:
892                raise Exception("'plateau' must be an Obs")
893
894        if references:
895            if isinstance(references, list):
896                for ref in references:
897                    ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--')
898            else:
899                raise Exception("'references' must be a list of floating pint values.")
900
901        if self.prange:
902            ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',', color="black", zorder=0)
903            ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',', color="black", zorder=0)
904
905        if fit_res:
906            x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05)
907            if isinstance(fit_res.fit_function, dict):
908                if fit_key:
909                    ax1.plot(x_samples, fit_res.fit_function[fit_key]([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2)
910                else:
911                    raise ValueError("Please provide a 'fit_key' for visualizing combined fits.")
912            else:
913                ax1.plot(x_samples, fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2)
914
915        ax1.set_xlabel(r'$x_0 / a$')
916        if ylabel:
917            ax1.set_ylabel(ylabel)
918        ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5])
919
920        handles, labels = ax1.get_legend_handles_labels()
921        if labels:
922            ax1.legend()
923
924        if title:
925            plt.title(title)
926
927        plt.draw()
928
929        if save:
930            if isinstance(save, str):
931                fig.savefig(save, bbox_inches='tight')
932            else:
933                raise Exception("'save' has to be a string.")

Plots the correlator using the tag of the correlator as label if available.

Parameters
  • x_range (list): list of two values, determining the range of the x-axis e.g. [4, 8].
  • comp (Corr or list of Corr): Correlator or list of correlators which are plotted for comparison. The tags of these correlators are used as labels if available.
  • logscale (bool): Sets y-axis to logscale.
  • plateau (Obs): Plateau value to be visualized in the figure.
  • fit_res (Fit_result): Fit_result object to be visualized.
  • fit_key (str): Key for the fit function in Fit_result.fit_function (for combined fits).
  • ylabel (str): Label for the y-axis.
  • save (str): path to file in which the figure should be saved.
  • auto_gamma (bool): Apply the gamma method with standard parameters to all correlators and plateau values before plotting.
  • hide_sigma (float): Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors.
  • references (list): List of floating point values that are displayed as horizontal lines for reference.
  • title (string): Optional title of the figure.
def spaghetti_plot(self, logscale=True):
935    def spaghetti_plot(self, logscale=True):
936        """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations.
937
938        Parameters
939        ----------
940        logscale : bool
941            Determines whether the scale of the y-axis is logarithmic or standard.
942        """
943        if self.N != 1:
944            raise Exception("Correlator needs to be projected first.")
945
946        mc_names = list(set([item for sublist in [sum(map(o[0].e_content.get, o[0].mc_names), []) for o in self.content if o is not None] for item in sublist]))
947        x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content) if o is not None]
948
949        for name in mc_names:
950            data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T
951
952            fig = plt.figure()
953            ax = fig.add_subplot(111)
954            for dat in data:
955                ax.plot(x0_vals, dat, ls='-', marker='')
956
957            if logscale is True:
958                ax.set_yscale('log')
959
960            ax.set_xlabel(r'$x_0 / a$')
961            plt.title(name)
962            plt.draw()

Produces a spaghetti plot of the correlator suited to monitor exceptional configurations.

Parameters
  • logscale (bool): Determines whether the scale of the y-axis is logarithmic or standard.
def dump(self, filename, datatype='json.gz', **kwargs):
964    def dump(self, filename, datatype="json.gz", **kwargs):
965        """Dumps the Corr into a file of chosen type
966        Parameters
967        ----------
968        filename : str
969            Name of the file to be saved.
970        datatype : str
971            Format of the exported file. Supported formats include
972            "json.gz" and "pickle"
973        path : str
974            specifies a custom path for the file (default '.')
975        """
976        if datatype == "json.gz":
977            from .input.json import dump_to_json
978            if 'path' in kwargs:
979                file_name = kwargs.get('path') + '/' + filename
980            else:
981                file_name = filename
982            dump_to_json(self, file_name)
983        elif datatype == "pickle":
984            dump_object(self, filename, **kwargs)
985        else:
986            raise Exception("Unknown datatype " + str(datatype))

Dumps the Corr into a file of chosen type

Parameters
  • filename (str): Name of the file to be saved.
  • datatype (str): Format of the exported file. Supported formats include "json.gz" and "pickle"
  • path (str): specifies a custom path for the file (default '.')
def print(self, print_range=None):
988    def print(self, print_range=None):
989        print(self.__repr__(print_range))
def sqrt(self):
1153    def sqrt(self):
1154        return self ** 0.5
def log(self):
1156    def log(self):
1157        newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content]
1158        return Corr(newcontent, prange=self.prange)
def exp(self):
1160    def exp(self):
1161        newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content]
1162        return Corr(newcontent, prange=self.prange)
def sin(self):
1177    def sin(self):
1178        return self._apply_func_to_corr(np.sin)
def cos(self):
1180    def cos(self):
1181        return self._apply_func_to_corr(np.cos)
def tan(self):
1183    def tan(self):
1184        return self._apply_func_to_corr(np.tan)
def sinh(self):
1186    def sinh(self):
1187        return self._apply_func_to_corr(np.sinh)
def cosh(self):
1189    def cosh(self):
1190        return self._apply_func_to_corr(np.cosh)
def tanh(self):
1192    def tanh(self):
1193        return self._apply_func_to_corr(np.tanh)
def arcsin(self):
1195    def arcsin(self):
1196        return self._apply_func_to_corr(np.arcsin)
def arccos(self):
1198    def arccos(self):
1199        return self._apply_func_to_corr(np.arccos)
def arctan(self):
1201    def arctan(self):
1202        return self._apply_func_to_corr(np.arctan)
def arcsinh(self):
1204    def arcsinh(self):
1205        return self._apply_func_to_corr(np.arcsinh)
def arccosh(self):
1207    def arccosh(self):
1208        return self._apply_func_to_corr(np.arccosh)
def arctanh(self):
1210    def arctanh(self):
1211        return self._apply_func_to_corr(np.arctanh)
real
imag
def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1246    def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1247        r''' Project large correlation matrix to lowest states
1248
1249        This method can be used to reduce the size of an (N x N) correlation matrix
1250        to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise
1251        is still small.
1252
1253        Parameters
1254        ----------
1255        Ntrunc: int
1256            Rank of the target matrix.
1257        tproj: int
1258            Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method.
1259            The default value is 3.
1260        t0proj: int
1261            Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly
1262            discouraged for O(a) improved theories, since the correctness of the procedure
1263            cannot be granted in this case. The default value is 2.
1264        basematrix : Corr
1265            Correlation matrix that is used to determine the eigenvectors of the
1266            lowest states based on a GEVP. basematrix is taken to be the Corr itself if
1267            is is not specified.
1268
1269        Notes
1270        -----
1271        We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving
1272        the GEVP $$C(t) v_n(t, t_0) = \lambda_n(t, t_0) C(t_0) v_n(t, t_0)$$ where $t \equiv t_\mathrm{proj}$
1273        and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the
1274        resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via
1275        $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large
1276        correlation matrix and to remove some noise that is added by irrelevant operators.
1277        This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated
1278        bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.
1279        '''
1280
1281        if self.N == 1:
1282            raise Exception('Method cannot be applied to one-dimensional correlators.')
1283        if basematrix is None:
1284            basematrix = self
1285        if Ntrunc >= basematrix.N:
1286            raise Exception('Cannot truncate using Ntrunc <= %d' % (basematrix.N))
1287        if basematrix.N != self.N:
1288            raise Exception('basematrix and targetmatrix have to be of the same size.')
1289
1290        evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc]
1291
1292        tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object)
1293        rmat = []
1294        for t in range(basematrix.T):
1295            for i in range(Ntrunc):
1296                for j in range(Ntrunc):
1297                    tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j]
1298            rmat.append(np.copy(tmpmat))
1299
1300        newcontent = [None if (self.content[t] is None) else rmat[t] for t in range(self.T)]
1301        return Corr(newcontent)

Project large correlation matrix to lowest states

This method can be used to reduce the size of an (N x N) correlation matrix to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise is still small.

Parameters
  • Ntrunc (int): Rank of the target matrix.
  • tproj (int): Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method. The default value is 3.
  • t0proj (int): Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly discouraged for O(a) improved theories, since the correctness of the procedure cannot be granted in this case. The default value is 2.
  • basematrix (Corr): Correlation matrix that is used to determine the eigenvectors of the lowest states based on a GEVP. basematrix is taken to be the Corr itself if is is not specified.
Notes

We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving the GEVP $$C(t) v_n(t, t_0) = \lambda_n(t, t_0) C(t_0) v_n(t, t_0)$$ where $t \equiv t_\mathrm{proj}$ and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large correlation matrix and to remove some noise that is added by irrelevant operators. This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.

N