# pyerrors.correlators API documentation

# pyerrors.correlators

View Source

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

class Corr: View Source

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

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

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

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

Corr(data_input, padding=[0, 0], prange=None) View Source

    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.

def gamma_method(self, **kwargs): View Source

    119    def gamma_method(self, **kwargs):
    120        """Apply the gamma method to the content of the Corr."""
    121        for item in self.content:
    122            if not (item is None):
    123                if self.N == 1:
    124                    item[0].gamma_method(**kwargs)
    125                else:
    126                    for i in range(self.N):
    127                        for j in range(self.N):
    128                            item[i, j].gamma_method(**kwargs)

Apply the gamma method to the content of the Corr.

def gm(self, **kwargs): View Source

    119    def gamma_method(self, **kwargs):
    120        """Apply the gamma method to the content of the Corr."""
    121        for item in self.content:
    122            if not (item is None):
    123                if self.N == 1:
    124                    item[0].gamma_method(**kwargs)
    125                else:
    126                    for i in range(self.N):
    127                        for j in range(self.N):
    128                            item[i, j].gamma_method(**kwargs)

Apply the gamma method to the content of the Corr.

def projected(self, vector_l=None, vector_r=None, normalize=False): View Source

    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): View Source

    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): View Source

    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): View Source

    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): View Source

    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): View Source

    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): View Source

    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): View Source

    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'): View Source

    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): View Source

    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): View Source

    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): View Source

    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): View Source

    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): View Source

    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): View Source

    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): View Source

    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'): View Source

    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'): View Source

    580    def second_deriv(self, variant="symmetric"):
    581        """Return the second derivative of the correlator with respect to x0.
    582
    583        Parameters
    584        ----------
    585        variant : str
    586            decides which definition of the finite differences derivative is used.
    587            Available choice: symmetric, improved, log, default: symmetric
    588        """
    589        if self.N != 1:
    590            raise Exception("second_deriv only implemented for one-dimensional correlators.")
    591        if variant == "symmetric":
    592            newcontent = []
    593            for t in range(1, self.T - 1):
    594                if (self.content[t - 1] is None) or (self.content[t + 1] is None):
    595                    newcontent.append(None)
    596                else:
    597                    newcontent.append((self.content[t + 1] - 2 * self.content[t] + self.content[t - 1]))
    598            if (all([x is None for x in newcontent])):
    599                raise Exception("Derivative is undefined at all timeslices")
    600            return Corr(newcontent, padding=[1, 1])
    601        elif variant == "improved":
    602            newcontent = []
    603            for t in range(2, self.T - 2):
    604                if (self.content[t - 2] is None) or (self.content[t - 1] is None) or (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 2] is None):
    605                    newcontent.append(None)
    606                else:
    607                    newcontent.append((1 / 12) * (-self.content[t + 2] + 16 * self.content[t + 1] - 30 * self.content[t] + 16 * self.content[t - 1] - self.content[t - 2]))
    608            if (all([x is None for x in newcontent])):
    609                raise Exception("Derivative is undefined at all timeslices")
    610            return Corr(newcontent, padding=[2, 2])
    611        elif variant == 'log':
    612            newcontent = []
    613            for t in range(self.T):
    614                if (self.content[t] is None) or (self.content[t] <= 0):
    615                    newcontent.append(None)
    616                else:
    617                    newcontent.append(np.log(self.content[t]))
    618            if (all([x is None for x in newcontent])):
    619                raise Exception("Log is undefined at all timeslices")
    620            logcorr = Corr(newcontent)
    621            return self * (logcorr.second_deriv('symmetric') + (logcorr.deriv('symmetric'))**2)
    622        else:
    623            raise Exception("Unknown variant.")

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

###### Parameters

- variant (str): decides which definition of the finite differences derivative is used. Available choice: symmetric, improved, log, default: symmetric

def m_eff(self, variant='log', guess=1.0): View Source

    625    def m_eff(self, variant='log', guess=1.0):
    626        """Returns the effective mass of the correlator as correlator object
    627
    628        Parameters
    629        ----------
    630        variant : str
    631            log : uses the standard effective mass log(C(t) / C(t+1))
    632            cosh, periodic : Use periodicitiy of the correlator by solving C(t) / C(t+1) = cosh(m * (t - T/2)) / cosh(m * (t + 1 - T/2)) for m.
    633            sinh : Use anti-periodicitiy of the correlator by solving C(t) / C(t+1) = sinh(m * (t - T/2)) / sinh(m * (t + 1 - T/2)) for m.
    634            See, e.g., arXiv:1205.5380
    635            arccosh : Uses the explicit form of the symmetrized correlator (not recommended)
    636            logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2
    637        guess : float
    638            guess for the root finder, only relevant for the root variant
    639        """
    640        if self.N != 1:
    641            raise Exception('Correlator must be projected before getting m_eff')
    642        if variant == 'log':
    643            newcontent = []
    644            for t in range(self.T - 1):
    645                if ((self.content[t] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0):
    646                    newcontent.append(None)
    647                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
    648                    newcontent.append(None)
    649                else:
    650                    newcontent.append(self.content[t] / self.content[t + 1])
    651            if (all([x is None for x in newcontent])):
    652                raise Exception('m_eff is undefined at all timeslices')
    653
    654            return np.log(Corr(newcontent, padding=[0, 1]))
    655
    656        elif variant == 'logsym':
    657            newcontent = []
    658            for t in range(1, self.T - 1):
    659                if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0):
    660                    newcontent.append(None)
    661                elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0:
    662                    newcontent.append(None)
    663                else:
    664                    newcontent.append(self.content[t - 1] / self.content[t + 1])
    665            if (all([x is None for x in newcontent])):
    666                raise Exception('m_eff is undefined at all timeslices')
    667
    668            return np.log(Corr(newcontent, padding=[1, 1])) / 2
    669
    670        elif variant in ['periodic', 'cosh', 'sinh']:
    671            if variant in ['periodic', 'cosh']:
    672                func = anp.cosh
    673            else:
    674                func = anp.sinh
    675
    676            def root_function(x, d):
    677                return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d
    678
    679            newcontent = []
    680            for t in range(self.T - 1):
    681                if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0):
    682                    newcontent.append(None)
    683                # Fill the two timeslices in the middle of the lattice with their predecessors
    684                elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]:
    685                    newcontent.append(newcontent[-1])
    686                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
    687                    newcontent.append(None)
    688                else:
    689                    newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess)))
    690            if (all([x is None for x in newcontent])):
    691                raise Exception('m_eff is undefined at all timeslices')
    692
    693            return Corr(newcontent, padding=[0, 1])
    694
    695        elif variant == 'arccosh':
    696            newcontent = []
    697            for t in range(1, self.T - 1):
    698                if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t - 1] is None) or (self.content[t][0].value == 0):
    699                    newcontent.append(None)
    700                else:
    701                    newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t]))
    702            if (all([x is None for x in newcontent])):
    703                raise Exception("m_eff is undefined at all timeslices")
    704            return np.arccosh(Corr(newcontent, padding=[1, 1]))
    705
    706        else:
    707            raise Exception('Unknown variant.')

Returns the effective mass of the correlator as correlator object

###### Parameters

- variant (str): log : uses the standard effective mass log(C(t) / C(t+1)) cosh, periodic : Use periodicitiy of the correlator by solving C(t) / C(t+1) = cosh(m * (t - T/2)) / cosh(m * (t + 1 - T/2)) for m. sinh : Use anti-periodicitiy of the correlator by solving C(t) / C(t+1) = sinh(m * (t - T/2)) / sinh(m * (t + 1 - T/2)) for m. See, e.g., arXiv:1205.5380 arccosh : Uses the explicit form of the symmetrized correlator (not recommended) logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2
- guess (float): guess for the root finder, only relevant for the root variant

def fit(self, function, fitrange=None, silent=False, **kwargs): View Source

    709    def fit(self, function, fitrange=None, silent=False, **kwargs):
    710        r'''Fits function to the data
    711
    712        Parameters
    713        ----------
    714        function : obj
    715            function to fit to the data. See fits.least_squares for details.
    716        fitrange : list
    717            Two element list containing the timeslices on which the fit is supposed to start and stop.
    718            Caution: This range is inclusive as opposed to standard python indexing.
    719            `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6.
    720            If not specified, self.prange or all timeslices are used.
    721        silent : bool
    722            Decides whether output is printed to the standard output.
    723        '''
    724        if self.N != 1:
    725            raise Exception("Correlator must be projected before fitting")
    726
    727        if fitrange is None:
    728            if self.prange:
    729                fitrange = self.prange
    730            else:
    731                fitrange = [0, self.T - 1]
    732        else:
    733            if not isinstance(fitrange, list):
    734                raise Exception("fitrange has to be a list with two elements")
    735            if len(fitrange) != 2:
    736                raise Exception("fitrange has to have exactly two elements [fit_start, fit_stop]")
    737
    738        xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None])
    739        ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if not self.content[x] is None])
    740        result = least_squares(xs, ys, function, silent=silent, **kwargs)
    741        return result

Fits function to the data

###### Parameters

- function (obj): function to fit to the data. See fits.least_squares for details.
- fitrange (list): Two element list containing the timeslices on which the fit is supposed to start and stop. Caution: This range is inclusive as opposed to standard python indexing. `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6. If not specified, self.prange or all timeslices are used.
- silent (bool): Decides whether output is printed to the standard output.

def plateau(self, plateau_range=None, method='fit', auto_gamma=False): View Source

    743    def plateau(self, plateau_range=None, method="fit", auto_gamma=False):
    744        """ Extract a plateau value from a Corr object
    745
    746        Parameters
    747        ----------
    748        plateau_range : list
    749            list with two entries, indicating the first and the last timeslice
    750            of the plateau region.
    751        method : str
    752            method to extract the plateau.
    753                'fit' fits a constant to the plateau region
    754                'avg', 'average' or 'mean' just average over the given timeslices.
    755        auto_gamma : bool
    756            apply gamma_method with default parameters to the Corr. Defaults to None
    757        """
    758        if not plateau_range:
    759            if self.prange:
    760                plateau_range = self.prange
    761            else:
    762                raise Exception("no plateau range provided")
    763        if self.N != 1:
    764            raise Exception("Correlator must be projected before getting a plateau.")
    765        if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])):
    766            raise Exception("plateau is undefined at all timeslices in plateaurange.")
    767        if auto_gamma:
    768            self.gamma_method()
    769        if method == "fit":
    770            def const_func(a, t):
    771                return a[0]
    772            return self.fit(const_func, plateau_range)[0]
    773        elif method in ["avg", "average", "mean"]:
    774            returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None])
    775            return returnvalue
    776
    777        else:
    778            raise Exception("Unsupported plateau method: " + method)

Extract a plateau value from a Corr object

###### Parameters

- plateau_range (list): list with two entries, indicating the first and the last timeslice of the plateau region.
- method (str): method to extract the plateau. 'fit' fits a constant to the plateau region 'avg', 'average' or 'mean' just average over the given timeslices.
- auto_gamma (bool): apply gamma_method with default parameters to the Corr. Defaults to None

def set_prange(self, prange): View Source

    780    def set_prange(self, prange):
    781        """Sets the attribute prange of the Corr object."""
    782        if not len(prange) == 2:
    783            raise Exception("prange must be a list or array with two values")
    784        if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))):
    785            raise Exception("Start and end point must be integers")
    786        if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] < prange[1]):
    787            raise Exception("Start and end point must define a range in the interval 0,T")
    788
    789        self.prange = prange
    790        return

Sets the attribute prange of the Corr object.

def show( self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None): View Source

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

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

###### Parameters

- x_range (list): list of two values, determining the range of the x-axis e.g. [4, 8].
- comp (Corr or list of Corr): Correlator or list of correlators which are plotted for comparison. The tags of these correlators are used as labels if available.
- logscale (bool): Sets y-axis to logscale.
- plateau (Obs): Plateau value to be visualized in the figure.
- fit_res (Fit_result): Fit_result object to be visualized.
- ylabel (str): Label for the y-axis.
- save (str): path to file in which the figure should be saved.
- auto_gamma (bool): Apply the gamma method with standard parameters to all correlators and plateau values before plotting.
- hide_sigma (float): Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors.
- references (list): List of floating point values that are displayed as horizontal lines for reference.
- title (string): Optional title of the figure.

def spaghetti_plot(self, logscale=True): View Source

    911    def spaghetti_plot(self, logscale=True):
    912        """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations.
    913
    914        Parameters
    915        ----------
    916        logscale : bool
    917            Determines whether the scale of the y-axis is logarithmic or standard.
    918        """
    919        if self.N != 1:
    920            raise Exception("Correlator needs to be projected first.")
    921
    922        mc_names = list(set([item for sublist in [sum(map(o[0].e_content.get, o[0].mc_names), []) for o in self.content if o is not None] for item in sublist]))
    923        x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content) if o is not None]
    924
    925        for name in mc_names:
    926            data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T
    927
    928            fig = plt.figure()
    929            ax = fig.add_subplot(111)
    930            for dat in data:
    931                ax.plot(x0_vals, dat, ls='-', marker='')
    932
    933            if logscale is True:
    934                ax.set_yscale('log')
    935
    936            ax.set_xlabel(r'$x_0 / a$')
    937            plt.title(name)
    938            plt.draw()

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

###### Parameters

- logscale (bool): Determines whether the scale of the y-axis is logarithmic or standard.

def dump(self, filename, datatype='json.gz', **kwargs): View Source

    940    def dump(self, filename, datatype="json.gz", **kwargs):
    941        """Dumps the Corr into a file of chosen type
    942        Parameters
    943        ----------
    944        filename : str
    945            Name of the file to be saved.
    946        datatype : str
    947            Format of the exported file. Supported formats include
    948            "json.gz" and "pickle"
    949        path : str
    950            specifies a custom path for the file (default '.')
    951        """
    952        if datatype == "json.gz":
    953            from .input.json import dump_to_json
    954            if 'path' in kwargs:
    955                file_name = kwargs.get('path') + '/' + filename
    956            else:
    957                file_name = filename
    958            dump_to_json(self, file_name)
    959        elif datatype == "pickle":
    960            dump_object(self, filename, **kwargs)
    961        else:
    962            raise Exception("Unknown datatype " + str(datatype))

Dumps the Corr into a file of chosen type

###### Parameters

- filename (str): Name of the file to be saved.
- datatype (str): Format of the exported file. Supported formats include "json.gz" and "pickle"
- path (str): specifies a custom path for the file (default '.')

def print(self, print_range=None): View Source

    964    def print(self, print_range=None):
    965        print(self.__repr__(print_range))

def sqrt(self): View Source

    1131    def sqrt(self):
    1132        return self ** 0.5

def log(self): View Source

    1134    def log(self):
    1135        newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content]
    1136        return Corr(newcontent, prange=self.prange)

def exp(self): View Source

    1138    def exp(self):
    1139        newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content]
    1140        return Corr(newcontent, prange=self.prange)

def sin(self): View Source

    1155    def sin(self):
    1156        return self._apply_func_to_corr(np.sin)

def cos(self): View Source

    1158    def cos(self):
    1159        return self._apply_func_to_corr(np.cos)

def tan(self): View Source

    1161    def tan(self):
    1162        return self._apply_func_to_corr(np.tan)

def sinh(self): View Source

    1164    def sinh(self):
    1165        return self._apply_func_to_corr(np.sinh)

def cosh(self): View Source

    1167    def cosh(self):
    1168        return self._apply_func_to_corr(np.cosh)

def tanh(self): View Source

    1170    def tanh(self):
    1171        return self._apply_func_to_corr(np.tanh)

def arcsin(self): View Source

    1173    def arcsin(self):
    1174        return self._apply_func_to_corr(np.arcsin)

def arccos(self): View Source

    1176    def arccos(self):
    1177        return self._apply_func_to_corr(np.arccos)

def arctan(self): View Source

    1179    def arctan(self):
    1180        return self._apply_func_to_corr(np.arctan)

def arcsinh(self): View Source

    1182    def arcsinh(self):
    1183        return self._apply_func_to_corr(np.arcsinh)

def arccosh(self): View Source

    1185    def arccosh(self):
    1186        return self._apply_func_to_corr(np.arccosh)

def arctanh(self): View Source

    1188    def arctanh(self):
    1189        return self._apply_func_to_corr(np.arctanh)

def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None): View Source

    1224    def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
    1225        r''' Project large correlation matrix to lowest states
    1226
    1227        This method can be used to reduce the size of an (N x N) correlation matrix
    1228        to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise
    1229        is still small.
    1230
    1231        Parameters
    1232        ----------
    1233        Ntrunc: int
    1234            Rank of the target matrix.
    1235        tproj: int
    1236            Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method.
    1237            The default value is 3.
    1238        t0proj: int
    1239            Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly
    1240            discouraged for O(a) improved theories, since the correctness of the procedure
    1241            cannot be granted in this case. The default value is 2.
    1242        basematrix : Corr
    1243            Correlation matrix that is used to determine the eigenvectors of the
    1244            lowest states based on a GEVP. basematrix is taken to be the Corr itself if
    1245            is is not specified.
    1246
    1247        Notes
    1248        -----
    1249        We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving
    1250        the GEVP $$C(t) v_n(t, t_0) = \lambda_n(t, t_0) C(t_0) v_n(t, t_0)$$ where $t \equiv t_\mathrm{proj}$
    1251        and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the
    1252        resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via
    1253        $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large
    1254        correlation matrix and to remove some noise that is added by irrelevant operators.
    1255        This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated
    1256        bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.
    1257        '''
    1258
    1259        if self.N == 1:
    1260            raise Exception('Method cannot be applied to one-dimensional correlators.')
    1261        if basematrix is None:
    1262            basematrix = self
    1263        if Ntrunc >= basematrix.N:
    1264            raise Exception('Cannot truncate using Ntrunc <= %d' % (basematrix.N))
    1265        if basematrix.N != self.N:
    1266            raise Exception('basematrix and targetmatrix have to be of the same size.')
    1267
    1268        evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc]
    1269
    1270        tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object)
    1271        rmat = []
    1272        for t in range(basematrix.T):
    1273            for i in range(Ntrunc):
    1274                for j in range(Ntrunc):
    1275                    tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j]
    1276            rmat.append(np.copy(tmpmat))
    1277
    1278        newcontent = [None if (self.content[t] is None) else rmat[t] for t in range(self.T)]
    1279        return Corr(newcontent)

Project large correlation matrix to lowest states

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

###### Parameters

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

###### Notes

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