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        if isinstance(self[0], CObs):
1002            return content_string
1003
1004        if print_range[1]:
1005            print_range[1] += 1
1006        content_string += 'x0/a\tCorr(x0/a)\n------------------\n'
1007        for i, sub_corr in enumerate(self.content[print_range[0]:print_range[1]]):
1008            if sub_corr is None:
1009                content_string += str(i + print_range[0]) + '\n'
1010            else:
1011                content_string += str(i + print_range[0])
1012                for element in sub_corr:
1013                    content_string += '\t' + ' ' * int(element >= 0) + str(element)
1014                content_string += '\n'
1015        return content_string
1016
1017    def __str__(self):
1018        return self.__repr__()
1019
1020    # We define the basic operations, that can be performed with correlators.
1021    # While */+- get defined here, they only work for Corr*Obs and not Obs*Corr.
1022    # This is because Obs*Corr checks Obs.__mul__ first and does not catch an exception.
1023    # One could try and tell Obs to check if the y in __mul__ is a Corr and
1024
1025    def __add__(self, y):
1026        if isinstance(y, Corr):
1027            if ((self.N != y.N) or (self.T != y.T)):
1028                raise Exception("Addition of Corrs with different shape")
1029            newcontent = []
1030            for t in range(self.T):
1031                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1032                    newcontent.append(None)
1033                else:
1034                    newcontent.append(self.content[t] + y.content[t])
1035            return Corr(newcontent)
1036
1037        elif isinstance(y, (Obs, int, float, CObs)):
1038            newcontent = []
1039            for t in range(self.T):
1040                if _check_for_none(self, self.content[t]):
1041                    newcontent.append(None)
1042                else:
1043                    newcontent.append(self.content[t] + y)
1044            return Corr(newcontent, prange=self.prange)
1045        elif isinstance(y, np.ndarray):
1046            if y.shape == (self.T,):
1047                return Corr(list((np.array(self.content).T + y).T))
1048            else:
1049                raise ValueError("operands could not be broadcast together")
1050        else:
1051            raise TypeError("Corr + wrong type")
1052
1053    def __mul__(self, y):
1054        if isinstance(y, Corr):
1055            if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T):
1056                raise Exception("Multiplication of Corr object requires N=N or N=1 and T=T")
1057            newcontent = []
1058            for t in range(self.T):
1059                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1060                    newcontent.append(None)
1061                else:
1062                    newcontent.append(self.content[t] * y.content[t])
1063            return Corr(newcontent)
1064
1065        elif isinstance(y, (Obs, int, float, CObs)):
1066            newcontent = []
1067            for t in range(self.T):
1068                if _check_for_none(self, self.content[t]):
1069                    newcontent.append(None)
1070                else:
1071                    newcontent.append(self.content[t] * y)
1072            return Corr(newcontent, prange=self.prange)
1073        elif isinstance(y, np.ndarray):
1074            if y.shape == (self.T,):
1075                return Corr(list((np.array(self.content).T * y).T))
1076            else:
1077                raise ValueError("operands could not be broadcast together")
1078        else:
1079            raise TypeError("Corr * wrong type")
1080
1081    def __truediv__(self, y):
1082        if isinstance(y, Corr):
1083            if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T):
1084                raise Exception("Multiplication of Corr object requires N=N or N=1 and T=T")
1085            newcontent = []
1086            for t in range(self.T):
1087                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1088                    newcontent.append(None)
1089                else:
1090                    newcontent.append(self.content[t] / y.content[t])
1091            for t in range(self.T):
1092                if _check_for_none(self, newcontent[t]):
1093                    continue
1094                if np.isnan(np.sum(newcontent[t]).value):
1095                    newcontent[t] = None
1096
1097            if all([item is None for item in newcontent]):
1098                raise Exception("Division returns completely undefined correlator")
1099            return Corr(newcontent)
1100
1101        elif isinstance(y, (Obs, CObs)):
1102            if isinstance(y, Obs):
1103                if y.value == 0:
1104                    raise Exception('Division by zero will return undefined correlator')
1105            if isinstance(y, CObs):
1106                if y.is_zero():
1107                    raise Exception('Division by zero will return undefined correlator')
1108
1109            newcontent = []
1110            for t in range(self.T):
1111                if _check_for_none(self, self.content[t]):
1112                    newcontent.append(None)
1113                else:
1114                    newcontent.append(self.content[t] / y)
1115            return Corr(newcontent, prange=self.prange)
1116
1117        elif isinstance(y, (int, float)):
1118            if y == 0:
1119                raise Exception('Division by zero will return undefined correlator')
1120            newcontent = []
1121            for t in range(self.T):
1122                if _check_for_none(self, self.content[t]):
1123                    newcontent.append(None)
1124                else:
1125                    newcontent.append(self.content[t] / y)
1126            return Corr(newcontent, prange=self.prange)
1127        elif isinstance(y, np.ndarray):
1128            if y.shape == (self.T,):
1129                return Corr(list((np.array(self.content).T / y).T))
1130            else:
1131                raise ValueError("operands could not be broadcast together")
1132        else:
1133            raise TypeError('Corr / wrong type')
1134
1135    def __neg__(self):
1136        newcontent = [None if _check_for_none(self, item) else -1. * item for item in self.content]
1137        return Corr(newcontent, prange=self.prange)
1138
1139    def __sub__(self, y):
1140        return self + (-y)
1141
1142    def __pow__(self, y):
1143        if isinstance(y, (Obs, int, float, CObs)):
1144            newcontent = [None if _check_for_none(self, item) else item**y for item in self.content]
1145            return Corr(newcontent, prange=self.prange)
1146        else:
1147            raise TypeError('Type of exponent not supported')
1148
1149    def __abs__(self):
1150        newcontent = [None if _check_for_none(self, item) else np.abs(item) for item in self.content]
1151        return Corr(newcontent, prange=self.prange)
1152
1153    # The numpy functions:
1154    def sqrt(self):
1155        return self ** 0.5
1156
1157    def log(self):
1158        newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content]
1159        return Corr(newcontent, prange=self.prange)
1160
1161    def exp(self):
1162        newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content]
1163        return Corr(newcontent, prange=self.prange)
1164
1165    def _apply_func_to_corr(self, func):
1166        newcontent = [None if _check_for_none(self, item) else func(item) for item in self.content]
1167        for t in range(self.T):
1168            if _check_for_none(self, newcontent[t]):
1169                continue
1170            tmp_sum = np.sum(newcontent[t])
1171            if hasattr(tmp_sum, "value"):
1172                if np.isnan(tmp_sum.value):
1173                    newcontent[t] = None
1174        if all([item is None for item in newcontent]):
1175            raise Exception('Operation returns undefined correlator')
1176        return Corr(newcontent)
1177
1178    def sin(self):
1179        return self._apply_func_to_corr(np.sin)
1180
1181    def cos(self):
1182        return self._apply_func_to_corr(np.cos)
1183
1184    def tan(self):
1185        return self._apply_func_to_corr(np.tan)
1186
1187    def sinh(self):
1188        return self._apply_func_to_corr(np.sinh)
1189
1190    def cosh(self):
1191        return self._apply_func_to_corr(np.cosh)
1192
1193    def tanh(self):
1194        return self._apply_func_to_corr(np.tanh)
1195
1196    def arcsin(self):
1197        return self._apply_func_to_corr(np.arcsin)
1198
1199    def arccos(self):
1200        return self._apply_func_to_corr(np.arccos)
1201
1202    def arctan(self):
1203        return self._apply_func_to_corr(np.arctan)
1204
1205    def arcsinh(self):
1206        return self._apply_func_to_corr(np.arcsinh)
1207
1208    def arccosh(self):
1209        return self._apply_func_to_corr(np.arccosh)
1210
1211    def arctanh(self):
1212        return self._apply_func_to_corr(np.arctanh)
1213
1214    # Right hand side operations (require tweak in main module to work)
1215    def __radd__(self, y):
1216        return self + y
1217
1218    def __rsub__(self, y):
1219        return -self + y
1220
1221    def __rmul__(self, y):
1222        return self * y
1223
1224    def __rtruediv__(self, y):
1225        return (self / y) ** (-1)
1226
1227    @property
1228    def real(self):
1229        def return_real(obs_OR_cobs):
1230            if isinstance(obs_OR_cobs.flatten()[0], CObs):
1231                return np.vectorize(lambda x: x.real)(obs_OR_cobs)
1232            else:
1233                return obs_OR_cobs
1234
1235        return self._apply_func_to_corr(return_real)
1236
1237    @property
1238    def imag(self):
1239        def return_imag(obs_OR_cobs):
1240            if isinstance(obs_OR_cobs.flatten()[0], CObs):
1241                return np.vectorize(lambda x: x.imag)(obs_OR_cobs)
1242            else:
1243                return obs_OR_cobs * 0  # So it stays the right type
1244
1245        return self._apply_func_to_corr(return_imag)
1246
1247    def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1248        r''' Project large correlation matrix to lowest states
1249
1250        This method can be used to reduce the size of an (N x N) correlation matrix
1251        to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise
1252        is still small.
1253
1254        Parameters
1255        ----------
1256        Ntrunc: int
1257            Rank of the target matrix.
1258        tproj: int
1259            Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method.
1260            The default value is 3.
1261        t0proj: int
1262            Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly
1263            discouraged for O(a) improved theories, since the correctness of the procedure
1264            cannot be granted in this case. The default value is 2.
1265        basematrix : Corr
1266            Correlation matrix that is used to determine the eigenvectors of the
1267            lowest states based on a GEVP. basematrix is taken to be the Corr itself if
1268            is is not specified.
1269
1270        Notes
1271        -----
1272        We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving
1273        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}$
1274        and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the
1275        resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via
1276        $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large
1277        correlation matrix and to remove some noise that is added by irrelevant operators.
1278        This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated
1279        bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.
1280        '''
1281
1282        if self.N == 1:
1283            raise Exception('Method cannot be applied to one-dimensional correlators.')
1284        if basematrix is None:
1285            basematrix = self
1286        if Ntrunc >= basematrix.N:
1287            raise Exception('Cannot truncate using Ntrunc <= %d' % (basematrix.N))
1288        if basematrix.N != self.N:
1289            raise Exception('basematrix and targetmatrix have to be of the same size.')
1290
1291        evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc]
1292
1293        tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object)
1294        rmat = []
1295        for t in range(basematrix.T):
1296            for i in range(Ntrunc):
1297                for j in range(Ntrunc):
1298                    tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j]
1299            rmat.append(np.copy(tmpmat))
1300
1301        newcontent = [None if (self.content[t] is None) else rmat[t] for t in range(self.T)]
1302        return Corr(newcontent)
1303
1304
1305def _sort_vectors(vec_set, ts):
1306    """Helper function used to find a set of Eigenvectors consistent over all timeslices"""
1307    reference_sorting = np.array(vec_set[ts])
1308    N = reference_sorting.shape[0]
1309    sorted_vec_set = []
1310    for t in range(len(vec_set)):
1311        if vec_set[t] is None:
1312            sorted_vec_set.append(None)
1313        elif not t == ts:
1314            perms = [list(o) for o in permutations([i for i in range(N)], N)]
1315            best_score = 0
1316            for perm in perms:
1317                current_score = 1
1318                for k in range(N):
1319                    new_sorting = reference_sorting.copy()
1320                    new_sorting[perm[k], :] = vec_set[t][k]
1321                    current_score *= abs(np.linalg.det(new_sorting))
1322                if current_score > best_score:
1323                    best_score = current_score
1324                    best_perm = perm
1325            sorted_vec_set.append([vec_set[t][k] for k in best_perm])
1326        else:
1327            sorted_vec_set.append(vec_set[t])
1328
1329    return sorted_vec_set
1330
1331
1332def _check_for_none(corr, entry):
1333    """Checks if entry for correlator corr is None"""
1334    return len(list(filter(None, np.asarray(entry).flatten()))) < corr.N ** 2
1335
1336
1337def _GEVP_solver(Gt, G0):
1338    """Helper function for solving the GEVP and sorting the eigenvectors.
1339
1340    The helper function assumes that both provided matrices are symmetric and
1341    only processes the lower triangular part of both matrices. In case the matrices
1342    are not symmetric the upper triangular parts are effectively discarded."""
1343    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        if isinstance(self[0], CObs):
1003            return content_string
1004
1005        if print_range[1]:
1006            print_range[1] += 1
1007        content_string += 'x0/a\tCorr(x0/a)\n------------------\n'
1008        for i, sub_corr in enumerate(self.content[print_range[0]:print_range[1]]):
1009            if sub_corr is None:
1010                content_string += str(i + print_range[0]) + '\n'
1011            else:
1012                content_string += str(i + print_range[0])
1013                for element in sub_corr:
1014                    content_string += '\t' + ' ' * int(element >= 0) + str(element)
1015                content_string += '\n'
1016        return content_string
1017
1018    def __str__(self):
1019        return self.__repr__()
1020
1021    # We define the basic operations, that can be performed with correlators.
1022    # While */+- get defined here, they only work for Corr*Obs and not Obs*Corr.
1023    # This is because Obs*Corr checks Obs.__mul__ first and does not catch an exception.
1024    # One could try and tell Obs to check if the y in __mul__ is a Corr and
1025
1026    def __add__(self, y):
1027        if isinstance(y, Corr):
1028            if ((self.N != y.N) or (self.T != y.T)):
1029                raise Exception("Addition of Corrs with different shape")
1030            newcontent = []
1031            for t in range(self.T):
1032                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1033                    newcontent.append(None)
1034                else:
1035                    newcontent.append(self.content[t] + y.content[t])
1036            return Corr(newcontent)
1037
1038        elif isinstance(y, (Obs, int, float, CObs)):
1039            newcontent = []
1040            for t in range(self.T):
1041                if _check_for_none(self, self.content[t]):
1042                    newcontent.append(None)
1043                else:
1044                    newcontent.append(self.content[t] + y)
1045            return Corr(newcontent, prange=self.prange)
1046        elif isinstance(y, np.ndarray):
1047            if y.shape == (self.T,):
1048                return Corr(list((np.array(self.content).T + y).T))
1049            else:
1050                raise ValueError("operands could not be broadcast together")
1051        else:
1052            raise TypeError("Corr + wrong type")
1053
1054    def __mul__(self, y):
1055        if isinstance(y, Corr):
1056            if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T):
1057                raise Exception("Multiplication of Corr object requires N=N or N=1 and T=T")
1058            newcontent = []
1059            for t in range(self.T):
1060                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1061                    newcontent.append(None)
1062                else:
1063                    newcontent.append(self.content[t] * y.content[t])
1064            return Corr(newcontent)
1065
1066        elif isinstance(y, (Obs, int, float, CObs)):
1067            newcontent = []
1068            for t in range(self.T):
1069                if _check_for_none(self, self.content[t]):
1070                    newcontent.append(None)
1071                else:
1072                    newcontent.append(self.content[t] * y)
1073            return Corr(newcontent, prange=self.prange)
1074        elif isinstance(y, np.ndarray):
1075            if y.shape == (self.T,):
1076                return Corr(list((np.array(self.content).T * y).T))
1077            else:
1078                raise ValueError("operands could not be broadcast together")
1079        else:
1080            raise TypeError("Corr * wrong type")
1081
1082    def __truediv__(self, y):
1083        if isinstance(y, Corr):
1084            if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T):
1085                raise Exception("Multiplication of Corr object requires N=N or N=1 and T=T")
1086            newcontent = []
1087            for t in range(self.T):
1088                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1089                    newcontent.append(None)
1090                else:
1091                    newcontent.append(self.content[t] / y.content[t])
1092            for t in range(self.T):
1093                if _check_for_none(self, newcontent[t]):
1094                    continue
1095                if np.isnan(np.sum(newcontent[t]).value):
1096                    newcontent[t] = None
1097
1098            if all([item is None for item in newcontent]):
1099                raise Exception("Division returns completely undefined correlator")
1100            return Corr(newcontent)
1101
1102        elif isinstance(y, (Obs, CObs)):
1103            if isinstance(y, Obs):
1104                if y.value == 0:
1105                    raise Exception('Division by zero will return undefined correlator')
1106            if isinstance(y, CObs):
1107                if y.is_zero():
1108                    raise Exception('Division by zero will return undefined correlator')
1109
1110            newcontent = []
1111            for t in range(self.T):
1112                if _check_for_none(self, self.content[t]):
1113                    newcontent.append(None)
1114                else:
1115                    newcontent.append(self.content[t] / y)
1116            return Corr(newcontent, prange=self.prange)
1117
1118        elif isinstance(y, (int, float)):
1119            if y == 0:
1120                raise Exception('Division by zero will return undefined correlator')
1121            newcontent = []
1122            for t in range(self.T):
1123                if _check_for_none(self, self.content[t]):
1124                    newcontent.append(None)
1125                else:
1126                    newcontent.append(self.content[t] / y)
1127            return Corr(newcontent, prange=self.prange)
1128        elif isinstance(y, np.ndarray):
1129            if y.shape == (self.T,):
1130                return Corr(list((np.array(self.content).T / y).T))
1131            else:
1132                raise ValueError("operands could not be broadcast together")
1133        else:
1134            raise TypeError('Corr / wrong type')
1135
1136    def __neg__(self):
1137        newcontent = [None if _check_for_none(self, item) else -1. * item for item in self.content]
1138        return Corr(newcontent, prange=self.prange)
1139
1140    def __sub__(self, y):
1141        return self + (-y)
1142
1143    def __pow__(self, y):
1144        if isinstance(y, (Obs, int, float, CObs)):
1145            newcontent = [None if _check_for_none(self, item) else item**y for item in self.content]
1146            return Corr(newcontent, prange=self.prange)
1147        else:
1148            raise TypeError('Type of exponent not supported')
1149
1150    def __abs__(self):
1151        newcontent = [None if _check_for_none(self, item) else np.abs(item) for item in self.content]
1152        return Corr(newcontent, prange=self.prange)
1153
1154    # The numpy functions:
1155    def sqrt(self):
1156        return self ** 0.5
1157
1158    def log(self):
1159        newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content]
1160        return Corr(newcontent, prange=self.prange)
1161
1162    def exp(self):
1163        newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content]
1164        return Corr(newcontent, prange=self.prange)
1165
1166    def _apply_func_to_corr(self, func):
1167        newcontent = [None if _check_for_none(self, item) else func(item) for item in self.content]
1168        for t in range(self.T):
1169            if _check_for_none(self, newcontent[t]):
1170                continue
1171            tmp_sum = np.sum(newcontent[t])
1172            if hasattr(tmp_sum, "value"):
1173                if np.isnan(tmp_sum.value):
1174                    newcontent[t] = None
1175        if all([item is None for item in newcontent]):
1176            raise Exception('Operation returns undefined correlator')
1177        return Corr(newcontent)
1178
1179    def sin(self):
1180        return self._apply_func_to_corr(np.sin)
1181
1182    def cos(self):
1183        return self._apply_func_to_corr(np.cos)
1184
1185    def tan(self):
1186        return self._apply_func_to_corr(np.tan)
1187
1188    def sinh(self):
1189        return self._apply_func_to_corr(np.sinh)
1190
1191    def cosh(self):
1192        return self._apply_func_to_corr(np.cosh)
1193
1194    def tanh(self):
1195        return self._apply_func_to_corr(np.tanh)
1196
1197    def arcsin(self):
1198        return self._apply_func_to_corr(np.arcsin)
1199
1200    def arccos(self):
1201        return self._apply_func_to_corr(np.arccos)
1202
1203    def arctan(self):
1204        return self._apply_func_to_corr(np.arctan)
1205
1206    def arcsinh(self):
1207        return self._apply_func_to_corr(np.arcsinh)
1208
1209    def arccosh(self):
1210        return self._apply_func_to_corr(np.arccosh)
1211
1212    def arctanh(self):
1213        return self._apply_func_to_corr(np.arctanh)
1214
1215    # Right hand side operations (require tweak in main module to work)
1216    def __radd__(self, y):
1217        return self + y
1218
1219    def __rsub__(self, y):
1220        return -self + y
1221
1222    def __rmul__(self, y):
1223        return self * y
1224
1225    def __rtruediv__(self, y):
1226        return (self / y) ** (-1)
1227
1228    @property
1229    def real(self):
1230        def return_real(obs_OR_cobs):
1231            if isinstance(obs_OR_cobs.flatten()[0], CObs):
1232                return np.vectorize(lambda x: x.real)(obs_OR_cobs)
1233            else:
1234                return obs_OR_cobs
1235
1236        return self._apply_func_to_corr(return_real)
1237
1238    @property
1239    def imag(self):
1240        def return_imag(obs_OR_cobs):
1241            if isinstance(obs_OR_cobs.flatten()[0], CObs):
1242                return np.vectorize(lambda x: x.imag)(obs_OR_cobs)
1243            else:
1244                return obs_OR_cobs * 0  # So it stays the right type
1245
1246        return self._apply_func_to_corr(return_imag)
1247
1248    def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1249        r''' Project large correlation matrix to lowest states
1250
1251        This method can be used to reduce the size of an (N x N) correlation matrix
1252        to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise
1253        is still small.
1254
1255        Parameters
1256        ----------
1257        Ntrunc: int
1258            Rank of the target matrix.
1259        tproj: int
1260            Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method.
1261            The default value is 3.
1262        t0proj: int
1263            Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly
1264            discouraged for O(a) improved theories, since the correctness of the procedure
1265            cannot be granted in this case. The default value is 2.
1266        basematrix : Corr
1267            Correlation matrix that is used to determine the eigenvectors of the
1268            lowest states based on a GEVP. basematrix is taken to be the Corr itself if
1269            is is not specified.
1270
1271        Notes
1272        -----
1273        We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving
1274        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}$
1275        and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the
1276        resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via
1277        $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large
1278        correlation matrix and to remove some noise that is added by irrelevant operators.
1279        This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated
1280        bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.
1281        '''
1282
1283        if self.N == 1:
1284            raise Exception('Method cannot be applied to one-dimensional correlators.')
1285        if basematrix is None:
1286            basematrix = self
1287        if Ntrunc >= basematrix.N:
1288            raise Exception('Cannot truncate using Ntrunc <= %d' % (basematrix.N))
1289        if basematrix.N != self.N:
1290            raise Exception('basematrix and targetmatrix have to be of the same size.')
1291
1292        evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc]
1293
1294        tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object)
1295        rmat = []
1296        for t in range(basematrix.T):
1297            for i in range(Ntrunc):
1298                for j in range(Ntrunc):
1299                    tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j]
1300            rmat.append(np.copy(tmpmat))
1301
1302        newcontent = [None if (self.content[t] is None) else rmat[t] for t in range(self.T)]
1303        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):
1155    def sqrt(self):
1156        return self ** 0.5
def log(self):
1158    def log(self):
1159        newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content]
1160        return Corr(newcontent, prange=self.prange)
def exp(self):
1162    def exp(self):
1163        newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content]
1164        return Corr(newcontent, prange=self.prange)
def sin(self):
1179    def sin(self):
1180        return self._apply_func_to_corr(np.sin)
def cos(self):
1182    def cos(self):
1183        return self._apply_func_to_corr(np.cos)
def tan(self):
1185    def tan(self):
1186        return self._apply_func_to_corr(np.tan)
def sinh(self):
1188    def sinh(self):
1189        return self._apply_func_to_corr(np.sinh)
def cosh(self):
1191    def cosh(self):
1192        return self._apply_func_to_corr(np.cosh)
def tanh(self):
1194    def tanh(self):
1195        return self._apply_func_to_corr(np.tanh)
def arcsin(self):
1197    def arcsin(self):
1198        return self._apply_func_to_corr(np.arcsin)
def arccos(self):
1200    def arccos(self):
1201        return self._apply_func_to_corr(np.arccos)
def arctan(self):
1203    def arctan(self):
1204        return self._apply_func_to_corr(np.arctan)
def arcsinh(self):
1206    def arcsinh(self):
1207        return self._apply_func_to_corr(np.arcsinh)
def arccosh(self):
1209    def arccosh(self):
1210        return self._apply_func_to_corr(np.arccosh)
def arctanh(self):
1212    def arctanh(self):
1213        return self._apply_func_to_corr(np.arctanh)
real
imag
def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1248    def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1249        r''' Project large correlation matrix to lowest states
1250
1251        This method can be used to reduce the size of an (N x N) correlation matrix
1252        to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise
1253        is still small.
1254
1255        Parameters
1256        ----------
1257        Ntrunc: int
1258            Rank of the target matrix.
1259        tproj: int
1260            Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method.
1261            The default value is 3.
1262        t0proj: int
1263            Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly
1264            discouraged for O(a) improved theories, since the correctness of the procedure
1265            cannot be granted in this case. The default value is 2.
1266        basematrix : Corr
1267            Correlation matrix that is used to determine the eigenvectors of the
1268            lowest states based on a GEVP. basematrix is taken to be the Corr itself if
1269            is is not specified.
1270
1271        Notes
1272        -----
1273        We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving
1274        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}$
1275        and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the
1276        resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via
1277        $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large
1278        correlation matrix and to remove some noise that is added by irrelevant operators.
1279        This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated
1280        bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.
1281        '''
1282
1283        if self.N == 1:
1284            raise Exception('Method cannot be applied to one-dimensional correlators.')
1285        if basematrix is None:
1286            basematrix = self
1287        if Ntrunc >= basematrix.N:
1288            raise Exception('Cannot truncate using Ntrunc <= %d' % (basematrix.N))
1289        if basematrix.N != self.N:
1290            raise Exception('basematrix and targetmatrix have to be of the same size.')
1291
1292        evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc]
1293
1294        tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object)
1295        rmat = []
1296        for t in range(basematrix.T):
1297            for i in range(Ntrunc):
1298                for j in range(Ntrunc):
1299                    tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j]
1300            rmat.append(np.copy(tmpmat))
1301
1302        newcontent = [None if (self.content[t] is None) else rmat[t] for t in range(self.T)]
1303        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