pyerrors.correlators

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

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 matrix at every timeslice. Other dependency (eg. spatial) are not supported.

The Corr class can also deal with missing measurements or paddings for fixed boundary conditions. The missing entries are represented via the None object.

Initialization

A simple correlator can be initialized with a list or a one-dimensional array of Obs or Cobs

corr11 = pe.Corr([obs1, obs2])
corr11 = pe.Corr(np.array([obs1, obs2]))

A matrix-valued correlator can either be initialized via a two-dimensional array of Corr objects

matrix_corr = pe.Corr(np.array([[corr11, corr12], [corr21, corr22]]))

or alternatively via a three-dimensional array of Obs or CObs of shape (T, N, N) where T is the temporal extent of the correlator and N is the dimension of the matrix.

Corr(data_input, padding=None, prange=None)
 49    def __init__(self, data_input, padding=None, prange=None):
 50        """ Initialize a Corr object.
 51
 52        Parameters
 53        ----------
 54        data_input : list or array
 55            list of Obs or list of arrays of Obs or array of Corrs (see class docstring for details).
 56        padding : list, optional
 57            List with two entries where the first labels the padding
 58            at the front of the correlator and the second the padding
 59            at the back.
 60        prange : list, optional
 61            List containing the first and last timeslice of the plateau
 62            region identified for this correlator.
 63        """
 64
 65        if padding is None:
 66            padding = [0, 0]
 67
 68        if isinstance(data_input, np.ndarray):
 69            if data_input.ndim == 1:
 70                data_input = list(data_input)
 71            elif data_input.ndim == 2:
 72                if not data_input.shape[0] == data_input.shape[1]:
 73                    raise ValueError("Array needs to be square.")
 74                if not all([isinstance(item, Corr) for item in data_input.flatten()]):
 75                    raise ValueError("If the input is an array, its elements must be of type pe.Corr.")
 76                if not all([item.N == 1 for item in data_input.flatten()]):
 77                    raise ValueError("Can only construct matrix correlator from single valued correlators.")
 78                if not len(set([item.T for item in data_input.flatten()])) == 1:
 79                    raise ValueError("All input Correlators must be defined over the same timeslices.")
 80
 81                T = data_input[0, 0].T
 82                N = data_input.shape[0]
 83                input_as_list = []
 84                for t in range(T):
 85                    if any([(item.content[t] is None) for item in data_input.flatten()]):
 86                        if not all([(item.content[t] is None) for item in data_input.flatten()]):
 87                            warnings.warn("Input ill-defined at different timeslices. Conversion leads to data loss.!", RuntimeWarning, stacklevel=2)
 88                        input_as_list.append(None)
 89                    else:
 90                        array_at_timeslace = np.empty([N, N], dtype="object")
 91                        for i in range(N):
 92                            for j in range(N):
 93                                array_at_timeslace[i, j] = data_input[i, j][t]
 94                        input_as_list.append(array_at_timeslace)
 95                data_input = input_as_list
 96            elif data_input.ndim == 3:
 97                if not data_input.shape[1] == data_input.shape[2]:
 98                    raise ValueError("Array needs to be square.")
 99                data_input = list(data_input)
100            else:
101                raise ValueError("Arrays with ndim>3 not supported.")
102
103        if isinstance(data_input, list):
104
105            if all([isinstance(item, (Obs, CObs)) or item is None for item in data_input]):
106                _assert_equal_properties([o for o in data_input if o is not None])
107                self.content = [np.asarray([item]) if item is not None else None for item in data_input]
108                self.N = 1
109            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]):
110                self.content = data_input
111                noNull = [a for a in self.content if a is not None]  # To check if the matrices are correct for all undefined elements
112                self.N = noNull[0].shape[0]
113                if self.N > 1 and noNull[0].shape[0] != noNull[0].shape[1]:
114                    raise ValueError("Smearing matrices are not NxN.")
115                if (not all([item.shape == noNull[0].shape for item in noNull])):
116                    raise ValueError("Items in data_input are not of identical shape." + str(noNull))
117            else:
118                raise TypeError("'data_input' contains item of wrong type.")
119        else:
120            raise TypeError("Data input was not given as list or correct array.")
121
122        self.tag = None
123
124        # An undefined timeslice is represented by the None object
125        self.content = [None] * padding[0] + self.content + [None] * padding[1]
126        self.T = len(self.content)
127        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 (see class docstring for details).
  • 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 identified for this correlator.
tag
content
T
prange
reweighted
138    @property
139    def reweighted(self):
140        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]])
141        if np.all(bool_array == 1):
142            return True
143        elif np.all(bool_array == 0):
144            return False
145        else:
146            raise Exception("Reweighting status of correlator corrupted.")
def gamma_method(self, **kwargs):
148    def gamma_method(self, **kwargs):
149        """Apply the gamma method to the content of the Corr."""
150        for item in self.content:
151            if item is not None:
152                if self.N == 1:
153                    item[0].gamma_method(**kwargs)
154                else:
155                    for i in range(self.N):
156                        for j in range(self.N):
157                            item[i, j].gamma_method(**kwargs)

Apply the gamma method to the content of the Corr.

def gm(self, **kwargs):
148    def gamma_method(self, **kwargs):
149        """Apply the gamma method to the content of the Corr."""
150        for item in self.content:
151            if item is not None:
152                if self.N == 1:
153                    item[0].gamma_method(**kwargs)
154                else:
155                    for i in range(self.N):
156                        for j in range(self.N):
157                            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):
161    def projected(self, vector_l=None, vector_r=None, normalize=False):
162        """We need to project the Correlator with a Vector to get a single value at each timeslice.
163
164        The method can use one or two vectors.
165        If two are specified it returns v1@G@v2 (the order might be very important.)
166        By default it will return the lowest source, which usually means unsmeared-unsmeared (0,0), but it does not have to
167        """
168        if self.N == 1:
169            raise ValueError("Trying to project a Corr, that already has N=1.")
170
171        if vector_l is None:
172            vector_l, vector_r = np.asarray([1.] + (self.N - 1) * [0.]), np.asarray([1.] + (self.N - 1) * [0.])
173        elif (vector_r is None):
174            vector_r = vector_l
175        if isinstance(vector_l, list) and not isinstance(vector_r, list):
176            if len(vector_l) != self.T:
177                raise ValueError("Length of vector list must be equal to T")
178            vector_r = [vector_r] * self.T
179        if isinstance(vector_r, list) and not isinstance(vector_l, list):
180            if len(vector_r) != self.T:
181                raise ValueError("Length of vector list must be equal to T")
182            vector_l = [vector_l] * self.T
183
184        if not isinstance(vector_l, list):
185            if not vector_l.shape == vector_r.shape == (self.N,):
186                raise ValueError("Vectors are of wrong shape!")
187            if normalize:
188                vector_l, vector_r = vector_l / np.sqrt(vector_l @ vector_l), vector_r / np.sqrt(vector_r @ vector_r)
189            newcontent = [None if _check_for_none(self, item) else np.asarray([vector_l.T @ item @ vector_r]) for item in self.content]
190
191        else:
192            # There are no checks here yet. There are so many possible scenarios, where this can go wrong.
193            if normalize:
194                for t in range(self.T):
195                    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])
196
197            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)]
198        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):
200    def item(self, i, j):
201        """Picks the element [i,j] from every matrix and returns a correlator containing one Obs per timeslice.
202
203        Parameters
204        ----------
205        i : int
206            First index to be picked.
207        j : int
208            Second index to be picked.
209        """
210        if self.N == 1:
211            raise ValueError("Trying to pick item from projected Corr")
212        newcontent = [None if (item is None) else item[i, j] for item in self.content]
213        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):
215    def plottable(self):
216        """Outputs the correlator in a plotable format.
217
218        Outputs three lists containing the timeslice index, the value on each
219        timeslice and the error on each timeslice.
220        """
221        if self.N != 1:
222            raise ValueError("Can only make Corr[N=1] plottable")
223        x_list = [x for x in range(self.T) if self.content[x] is not None]
224        y_list = [y[0].value for y in self.content if y is not None]
225        y_err_list = [y[0].dvalue for y in self.content if y is not None]
226
227        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):
229    def symmetric(self):
230        """ Symmetrize the correlator around x0=0."""
231        if self.N != 1:
232            raise ValueError('symmetric cannot be safely applied to multi-dimensional correlators.')
233        if self.T % 2 != 0:
234            raise ValueError("Can not symmetrize odd T")
235
236        if self.content[0] is not None:
237            if np.argmax(np.abs([o[0].value if o is not None else 0 for o in self.content])) != 0:
238                warnings.warn("Correlator does not seem to be symmetric around x0=0.", RuntimeWarning, stacklevel=2)
239
240        newcontent = [self.content[0]]
241        for t in range(1, self.T):
242            if (self.content[t] is None) or (self.content[self.T - t] is None):
243                newcontent.append(None)
244            else:
245                newcontent.append(0.5 * (self.content[t] + self.content[self.T - t]))
246        if (all([x is None for x in newcontent])):
247            raise ValueError("Corr could not be symmetrized: No redundant values")
248        return Corr(newcontent, prange=self.prange)

Symmetrize the correlator around x0=0.

def anti_symmetric(self):
250    def anti_symmetric(self):
251        """Anti-symmetrize the correlator around x0=0."""
252        if self.N != 1:
253            raise TypeError('anti_symmetric cannot be safely applied to multi-dimensional correlators.')
254        if self.T % 2 != 0:
255            raise ValueError("Can not symmetrize odd T")
256
257        test = 1 * self
258        test.gamma_method()
259        if not all([o.is_zero_within_error(3) for o in test.content[0]]):
260            warnings.warn("Correlator does not seem to be anti-symmetric around x0=0.", RuntimeWarning, stacklevel=2)
261
262        newcontent = [self.content[0]]
263        for t in range(1, self.T):
264            if (self.content[t] is None) or (self.content[self.T - t] is None):
265                newcontent.append(None)
266            else:
267                newcontent.append(0.5 * (self.content[t] - self.content[self.T - t]))
268        if (all([x is None for x in newcontent])):
269            raise ValueError("Corr could not be symmetrized: No redundant values")
270        return Corr(newcontent, prange=self.prange)

Anti-symmetrize the correlator around x0=0.

def is_matrix_symmetric(self):
272    def is_matrix_symmetric(self):
273        """Checks whether a correlator matrices is symmetric on every timeslice."""
274        if self.N == 1:
275            raise TypeError("Only works for correlator matrices.")
276        for t in range(self.T):
277            if self[t] is None:
278                continue
279            for i in range(self.N):
280                for j in range(i + 1, self.N):
281                    if self[t][i, j] is self[t][j, i]:
282                        continue
283                    if hash(self[t][i, j]) != hash(self[t][j, i]):
284                        return False
285        return True

Checks whether a correlator matrices is symmetric on every timeslice.

def trace(self):
287    def trace(self):
288        """Calculates the per-timeslice trace of a correlator matrix."""
289        if self.N == 1:
290            raise ValueError("Only works for correlator matrices.")
291        newcontent = []
292        for t in range(self.T):
293            if _check_for_none(self, self.content[t]):
294                newcontent.append(None)
295            else:
296                newcontent.append(np.trace(self.content[t]))
297        return Corr(newcontent)

Calculates the per-timeslice trace of a correlator matrix.

def matrix_symmetric(self):
299    def matrix_symmetric(self):
300        """Symmetrizes the correlator matrices on every timeslice."""
301        if self.N == 1:
302            raise ValueError("Trying to symmetrize a correlator matrix, that already has N=1.")
303        if self.is_matrix_symmetric():
304            return 1.0 * self
305        else:
306            transposed = [None if _check_for_none(self, G) else G.T for G in self.content]
307            return 0.5 * (Corr(transposed) + self)

Symmetrizes the correlator matrices on every timeslice.

def GEVP(self, t0, ts=None, sort='Eigenvalue', vector_obs=False, **kwargs):
309    def GEVP(self, t0, ts=None, sort="Eigenvalue", vector_obs=False, **kwargs):
310        r'''Solve the generalized eigenvalue problem on the correlator matrix and returns the corresponding eigenvectors.
311
312        The eigenvectors are sorted according to the descending eigenvalues, the zeroth eigenvector(s) correspond to the
313        largest eigenvalue(s). The eigenvector(s) for the individual states can be accessed via slicing
314        ```python
315        C.GEVP(t0=2)[0]  # Ground state vector(s)
316        C.GEVP(t0=2)[:3]  # Vectors for the lowest three states
317        ```
318
319        Parameters
320        ----------
321        t0 : int
322            The time t0 for the right hand side of the GEVP according to $G(t)v_i=\lambda_i G(t_0)v_i$
323        ts : int
324            fixed time $G(t_s)v_i=\lambda_i G(t_0)v_i$ if sort=None.
325            If sort="Eigenvector" it gives a reference point for the sorting method.
326        sort : string
327            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.
328            - "Eigenvalue": The eigenvector is chosen according to which eigenvalue it belongs individually on every timeslice. (default)
329            - "Eigenvector": Use the method described in arXiv:2004.10472 to find the set of v(t) belonging to the state.
330              The reference state is identified by its eigenvalue at $t=t_s$.
331            - None: The GEVP is solved only at ts, no sorting is necessary
332        vector_obs : bool
333            If True, uncertainties are propagated in the eigenvector computation (default False).
334
335        Other Parameters
336        ----------------
337        state : int
338           Returns only the vector(s) for a specified state. The lowest state is zero.
339        method : str
340           Method used to solve the GEVP.
341           - "eigh": Use scipy.linalg.eigh to solve the GEVP. (default for vector_obs=False)
342           - "cholesky": Use manually implemented solution via the Cholesky decomposition. Automatically chosen if vector_obs==True.
343        '''
344
345        if self.N == 1:
346            raise ValueError("GEVP methods only works on correlator matrices and not single correlators.")
347        if ts is not None:
348            if (ts <= t0):
349                raise ValueError("ts has to be larger than t0.")
350
351        if "sorted_list" in kwargs:
352            warnings.warn("Argument 'sorted_list' is deprecated, use 'sort' instead.", DeprecationWarning, stacklevel=2)
353            sort = kwargs.get("sorted_list")
354
355        if self.is_matrix_symmetric():
356            symmetric_corr = self
357        else:
358            symmetric_corr = self.matrix_symmetric()
359
360        def _get_mat_at_t(t, vector_obs=vector_obs):
361            if vector_obs:
362                return symmetric_corr[t]
363            else:
364                return np.vectorize(lambda x: x.value)(symmetric_corr[t])
365        G0 = _get_mat_at_t(t0)
366
367        method = kwargs.get('method', 'eigh')
368        if vector_obs:
369            chol = linalg.cholesky(G0)
370            chol_inv = linalg.inv(chol)
371            method = 'cholesky'
372        else:
373            chol = np.linalg.cholesky(_get_mat_at_t(t0, vector_obs=False))  # Check if matrix G0 is positive-semidefinite.
374            if method == 'cholesky':
375                chol_inv = np.linalg.inv(chol)
376            else:
377                chol_inv = None
378
379        if sort is None:
380            if (ts is None):
381                raise ValueError("ts is required if sort=None.")
382            if (self.content[t0] is None) or (self.content[ts] is None):
383                raise ValueError("Corr not defined at t0/ts.")
384            Gt = _get_mat_at_t(ts)
385            reordered_vecs = _GEVP_solver(Gt, G0, method=method, chol_inv=chol_inv)
386            if kwargs.get('auto_gamma', False) and vector_obs:
387                [[o.gm() for o in ev if isinstance(o, Obs)] for ev in reordered_vecs]
388
389        elif sort in ["Eigenvalue", "Eigenvector"]:
390            if sort == "Eigenvalue" and ts is not None:
391                warnings.warn("ts has no effect when sorting by eigenvalue is chosen.", RuntimeWarning, stacklevel=2)
392            all_vecs = [None] * (t0 + 1)
393            for t in range(t0 + 1, self.T):
394                try:
395                    Gt = _get_mat_at_t(t)
396                    all_vecs.append(_GEVP_solver(Gt, G0, method=method, chol_inv=chol_inv))
397                except Exception:
398                    all_vecs.append(None)
399            if sort == "Eigenvector":
400                if ts is None:
401                    raise ValueError("ts is required for the Eigenvector sorting method.")
402                all_vecs = _sort_vectors(all_vecs, ts)
403
404            reordered_vecs = [[v[s] if v is not None else None for v in all_vecs] for s in range(self.N)]
405            if kwargs.get('auto_gamma', False) and vector_obs:
406                [[[o.gm() for o in evn] for evn in ev if evn is not None] for ev in reordered_vecs]
407        else:
408            raise ValueError("Unknown value for 'sort'. Choose 'Eigenvalue', 'Eigenvector' or None.")
409
410        if "state" in kwargs:
411            return reordered_vecs[kwargs.get("state")]
412        else:
413            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. (default)
    • "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$.
    • None: The GEVP is solved only at ts, no sorting is necessary
  • vector_obs (bool): If True, uncertainties are propagated in the eigenvector computation (default False).
Other Parameters
  • state (int): Returns only the vector(s) for a specified state. The lowest state is zero.
  • method (str): Method used to solve the GEVP.
    • "eigh": Use scipy.linalg.eigh to solve the GEVP. (default for vector_obs=False)
    • "cholesky": Use manually implemented solution via the Cholesky decomposition. Automatically chosen if vector_obs==True.
def Eigenvalue(self, t0, ts=None, state=0, sort='Eigenvalue', **kwargs):
415    def Eigenvalue(self, t0, ts=None, state=0, sort="Eigenvalue", **kwargs):
416        """Determines the eigenvalue of the GEVP by solving and projecting the correlator
417
418        Parameters
419        ----------
420        state : int
421            The state one is interested in ordered by energy. The lowest state is zero.
422
423        All other parameters are identical to the ones of Corr.GEVP.
424        """
425        vec = self.GEVP(t0, ts=ts, sort=sort, **kwargs)[state]
426        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):
428    def Hankel(self, N, periodic=False):
429        """Constructs an NxN Hankel matrix
430
431        C(t) c(t+1) ... c(t+n-1)
432        C(t+1) c(t+2) ... c(t+n)
433        .................
434        C(t+(n-1)) c(t+n) ... c(t+2(n-1))
435
436        Parameters
437        ----------
438        N : int
439            Dimension of the Hankel matrix
440        periodic : bool, optional
441            determines whether the matrix is extended periodically
442        """
443
444        if self.N != 1:
445            raise NotImplementedError("Multi-operator Prony not implemented!")
446
447        array = np.empty([N, N], dtype="object")
448        new_content = []
449        for _t in range(self.T):
450            new_content.append(array.copy())
451
452        def wrap(i):
453            while i >= self.T:
454                i -= self.T
455            return i
456
457        for t in range(self.T):
458            for i in range(N):
459                for j in range(N):
460                    if periodic:
461                        new_content[t][i, j] = self.content[wrap(t + i + j)][0]
462                    elif (t + i + j) >= self.T:
463                        new_content[t] = None
464                    else:
465                        new_content[t][i, j] = self.content[t + i + j][0]
466
467        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):
469    def roll(self, dt):
470        """Periodically shift the correlator by dt timeslices
471
472        Parameters
473        ----------
474        dt : int
475            number of timeslices
476        """
477        return Corr(list(np.roll(np.array(self.content, dtype=object), dt, axis=0)))

Periodically shift the correlator by dt timeslices

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

Reverse the time ordering of the Corr

def thin(self, spacing=2, offset=0):
483    def thin(self, spacing=2, offset=0):
484        """Thin out a correlator to suppress correlations
485
486        Parameters
487        ----------
488        spacing : int
489            Keep only every 'spacing'th entry of the correlator
490        offset : int
491            Offset the equal spacing
492        """
493        new_content = []
494        for t in range(self.T):
495            if (offset + t) % spacing != 0:
496                new_content.append(None)
497            else:
498                new_content.append(self.content[t])
499        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):
501    def correlate(self, partner):
502        """Correlate the correlator with another correlator or Obs
503
504        Parameters
505        ----------
506        partner : Obs or Corr
507            partner to correlate the correlator with.
508            Can either be an Obs which is correlated with all entries of the
509            correlator or a Corr of same length.
510        """
511        if self.N != 1:
512            raise ValueError("Only one-dimensional correlators can be safely correlated.")
513        new_content = []
514        for x0, t_slice in enumerate(self.content):
515            if _check_for_none(self, t_slice):
516                new_content.append(None)
517            else:
518                if isinstance(partner, Corr):
519                    if _check_for_none(partner, partner.content[x0]):
520                        new_content.append(None)
521                    else:
522                        new_content.append(np.array([correlate(o, partner.content[x0][0]) for o in t_slice]))
523                elif isinstance(partner, Obs):  # Should this include CObs?
524                    new_content.append(np.array([correlate(o, partner) for o in t_slice]))
525                else:
526                    raise TypeError("Can only correlate with an Obs or a Corr.")
527
528        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):
530    def reweight(self, weight, **kwargs):
531        """Reweight the correlator.
532
533        Parameters
534        ----------
535        weight : Obs
536            Reweighting factor. An Observable that has to be defined on a superset of the
537            configurations in obs[i].idl for all i.
538        all_configs : bool
539            if True, the reweighted observables are normalized by the average of
540            the reweighting factor on all configurations in weight.idl and not
541            on the configurations in obs[i].idl.
542        """
543        if self.N != 1:
544            raise ValueError("Reweighting only implemented for one-dimensional correlators.")
545        new_content = []
546        for t_slice in self.content:
547            if _check_for_none(self, t_slice):
548                new_content.append(None)
549            else:
550                new_content.append(np.array(reweight(weight, t_slice, **kwargs)))
551        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):
553    def T_symmetry(self, partner, parity=+1):
554        """Return the time symmetry average of the correlator and its partner
555
556        Parameters
557        ----------
558        partner : Corr
559            Time symmetry partner of the Corr
560        parity : int
561            Parity quantum number of the correlator, can be +1 or -1
562        """
563        if self.N != 1:
564            raise ValueError("T_symmetry only implemented for one-dimensional correlators.")
565        if not isinstance(partner, Corr):
566            raise TypeError("T partner has to be a Corr object.")
567        if parity not in [+1, -1]:
568            raise ValueError("Parity has to be +1 or -1.")
569        T_partner = parity * partner.reverse()
570
571        t_slices = []
572        test = (self - T_partner)
573        test.gamma_method()
574        for x0, t_slice in enumerate(test.content):
575            if t_slice is not None:
576                if not t_slice[0].is_zero_within_error(5):
577                    t_slices.append(x0)
578        if t_slices:
579            warnings.warn("T symmetry partners do not agree within 5 sigma on time slices " + str(t_slices) + ".", RuntimeWarning, stacklevel=2)
580
581        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
  • parity (int): Parity quantum number of the correlator, can be +1 or -1
def deriv(self, variant='symmetric'):
583    def deriv(self, variant="symmetric"):
584        """Return the first derivative of the correlator with respect to x0.
585
586        Parameters
587        ----------
588        variant : str
589            decides which definition of the finite differences derivative is used.
590            Available choice: symmetric, forward, backward, improved, log, default: symmetric
591        """
592        if self.N != 1:
593            raise ValueError("deriv only implemented for one-dimensional correlators.")
594        if variant == "symmetric":
595            newcontent = []
596            for t in range(1, self.T - 1):
597                if (self.content[t - 1] is None) or (self.content[t + 1] is None):
598                    newcontent.append(None)
599                else:
600                    newcontent.append(0.5 * (self.content[t + 1] - self.content[t - 1]))
601            if (all([x is None for x in newcontent])):
602                raise ValueError('Derivative is undefined at all timeslices')
603            return Corr(newcontent, padding=[1, 1])
604        elif variant == "forward":
605            newcontent = []
606            for t in range(self.T - 1):
607                if (self.content[t] is None) or (self.content[t + 1] is None):
608                    newcontent.append(None)
609                else:
610                    newcontent.append(self.content[t + 1] - self.content[t])
611            if (all([x is None for x in newcontent])):
612                raise ValueError("Derivative is undefined at all timeslices")
613            return Corr(newcontent, padding=[0, 1])
614        elif variant == "backward":
615            newcontent = []
616            for t in range(1, self.T):
617                if (self.content[t - 1] is None) or (self.content[t] is None):
618                    newcontent.append(None)
619                else:
620                    newcontent.append(self.content[t] - self.content[t - 1])
621            if (all([x is None for x in newcontent])):
622                raise ValueError("Derivative is undefined at all timeslices")
623            return Corr(newcontent, padding=[1, 0])
624        elif variant == "improved":
625            newcontent = []
626            for t in range(2, self.T - 2):
627                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):
628                    newcontent.append(None)
629                else:
630                    newcontent.append((1 / 12) * (self.content[t - 2] - 8 * self.content[t - 1] + 8 * self.content[t + 1] - self.content[t + 2]))
631            if (all([x is None for x in newcontent])):
632                raise ValueError('Derivative is undefined at all timeslices')
633            return Corr(newcontent, padding=[2, 2])
634        elif variant == 'log':
635            newcontent = []
636            for t in range(self.T):
637                if (self.content[t] is None) or (self.content[t] <= 0):
638                    newcontent.append(None)
639                else:
640                    newcontent.append(np.log(self.content[t]))
641            if (all([x is None for x in newcontent])):
642                raise ValueError("Log is undefined at all timeslices")
643            logcorr = Corr(newcontent)
644            return self * logcorr.deriv('symmetric')
645        else:
646            raise ValueError("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'):
648    def second_deriv(self, variant="symmetric"):
649        r"""Return the second derivative of the correlator with respect to x0.
650
651        Parameters
652        ----------
653        variant : str
654            decides which definition of the finite differences derivative is used.
655            Available choice:
656                - symmetric (default)
657                    $$\tilde{\partial}^2_0 f(x_0) = f(x_0+1)-2f(x_0)+f(x_0-1)$$
658                - big_symmetric
659                    $$\partial^2_0 f(x_0) = \frac{f(x_0+2)-2f(x_0)+f(x_0-2)}{4}$$
660                - improved
661                    $$\partial^2_0 f(x_0) = \frac{-f(x_0+2) + 16 * f(x_0+1) - 30 * f(x_0) + 16 * f(x_0-1) - f(x_0-2)}{12}$$
662                - log
663                    $$f(x) = \tilde{\partial}^2_0 log(f(x_0))+(\tilde{\partial}_0 log(f(x_0)))^2$$
664        """
665        if self.N != 1:
666            raise ValueError("second_deriv only implemented for one-dimensional correlators.")
667        if variant == "symmetric":
668            newcontent = []
669            for t in range(1, self.T - 1):
670                if (self.content[t - 1] is None) or (self.content[t + 1] is None):
671                    newcontent.append(None)
672                else:
673                    newcontent.append(self.content[t + 1] - 2 * self.content[t] + self.content[t - 1])
674            if (all([x is None for x in newcontent])):
675                raise ValueError("Derivative is undefined at all timeslices")
676            return Corr(newcontent, padding=[1, 1])
677        elif variant == "big_symmetric":
678            newcontent = []
679            for t in range(2, self.T - 2):
680                if (self.content[t - 2] is None) or (self.content[t + 2] is None):
681                    newcontent.append(None)
682                else:
683                    newcontent.append((self.content[t + 2] - 2 * self.content[t] + self.content[t - 2]) / 4)
684            if (all([x is None for x in newcontent])):
685                raise ValueError("Derivative is undefined at all timeslices")
686            return Corr(newcontent, padding=[2, 2])
687        elif variant == "improved":
688            newcontent = []
689            for t in range(2, self.T - 2):
690                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):
691                    newcontent.append(None)
692                else:
693                    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]))
694            if (all([x is None for x in newcontent])):
695                raise ValueError("Derivative is undefined at all timeslices")
696            return Corr(newcontent, padding=[2, 2])
697        elif variant == 'log':
698            newcontent = []
699            for t in range(self.T):
700                if (self.content[t] is None) or (self.content[t] <= 0):
701                    newcontent.append(None)
702                else:
703                    newcontent.append(np.log(self.content[t]))
704            if (all([x is None for x in newcontent])):
705                raise ValueError("Log is undefined at all timeslices")
706            logcorr = Corr(newcontent)
707            return self * (logcorr.second_deriv('symmetric') + (logcorr.deriv('symmetric'))**2)
708        else:
709            raise ValueError("Unknown variant.")

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

Parameters
  • variant (str): decides which definition of the finite differences derivative is used. Available choice: - symmetric (default) $$\tilde{\partial}^2_0 f(x_0) = f(x_0+1)-2f(x_0)+f(x_0-1)$$ - big_symmetric $$\partial^2_0 f(x_0) = \frac{f(x_0+2)-2f(x_0)+f(x_0-2)}{4}$$ - improved $$\partial^2_0 f(x_0) = \frac{-f(x_0+2) + 16 * f(x_0+1) - 30 * f(x_0) + 16 * f(x_0-1) - f(x_0-2)}{12}$$ - log $$f(x) = \tilde{\partial}^2_0 log(f(x_0))+(\tilde{\partial}_0 log(f(x_0)))^2$$
def m_eff(self, variant='log', guess=1.0):
711    def m_eff(self, variant='log', guess=1.0):
712        """Returns the effective mass of the correlator as correlator object
713
714        Parameters
715        ----------
716        variant : str
717            log : uses the standard effective mass log(C(t) / C(t+1))
718            cosh, periodic : Use periodicity of the correlator by solving C(t) / C(t+1) = cosh(m * (t - T/2)) / cosh(m * (t + 1 - T/2)) for m.
719            sinh : Use anti-periodicity of the correlator by solving C(t) / C(t+1) = sinh(m * (t - T/2)) / sinh(m * (t + 1 - T/2)) for m.
720            See, e.g., arXiv:1205.5380
721            arccosh : Uses the explicit form of the symmetrized correlator (not recommended)
722            logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2
723        guess : float
724            guess for the root finder, only relevant for the root variant
725        """
726        if self.N != 1:
727            raise ValueError('Correlator must be projected before getting m_eff')
728        if variant == 'log':
729            newcontent = []
730            for t in range(self.T - 1):
731                if ((self.content[t] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0):
732                    newcontent.append(None)
733                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
734                    newcontent.append(None)
735                else:
736                    newcontent.append(self.content[t] / self.content[t + 1])
737            if (all([x is None for x in newcontent])):
738                raise ValueError('m_eff is undefined at all timeslices')
739
740            return np.log(Corr(newcontent, padding=[0, 1]))
741
742        elif variant == 'logsym':
743            newcontent = []
744            for t in range(1, self.T - 1):
745                if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0):
746                    newcontent.append(None)
747                elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0:
748                    newcontent.append(None)
749                else:
750                    newcontent.append(self.content[t - 1] / self.content[t + 1])
751            if (all([x is None for x in newcontent])):
752                raise ValueError('m_eff is undefined at all timeslices')
753
754            return np.log(Corr(newcontent, padding=[1, 1])) / 2
755
756        elif variant in ['periodic', 'cosh', 'sinh']:
757            if variant in ['periodic', 'cosh']:
758                func = anp.cosh
759            else:
760                func = anp.sinh
761
762            def root_function(x, d):
763                return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d
764
765            newcontent = []
766            for t in range(self.T - 1):
767                if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0):
768                    newcontent.append(None)
769                # Fill the two timeslices in the middle of the lattice with their predecessors
770                elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]:
771                    newcontent.append(newcontent[-1])
772                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
773                    newcontent.append(None)
774                else:
775                    newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess)))
776            if (all([x is None for x in newcontent])):
777                raise ValueError('m_eff is undefined at all timeslices')
778
779            return Corr(newcontent, padding=[0, 1])
780
781        elif variant == 'arccosh':
782            newcontent = []
783            for t in range(1, self.T - 1):
784                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):
785                    newcontent.append(None)
786                else:
787                    newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t]))
788            if (all([x is None for x in newcontent])):
789                raise ValueError("m_eff is undefined at all timeslices")
790            return np.arccosh(Corr(newcontent, padding=[1, 1]))
791
792        else:
793            raise ValueError('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 periodicity 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-periodicity 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):
795    def fit(self, function, fitrange=None, silent=False, **kwargs):
796        r'''Fits function to the data
797
798        Parameters
799        ----------
800        function : obj
801            function to fit to the data. See fits.least_squares for details.
802        fitrange : list
803            Two element list containing the timeslices on which the fit is supposed to start and stop.
804            Caution: This range is inclusive as opposed to standard python indexing.
805            `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6.
806            If not specified, self.prange or all timeslices are used.
807        silent : bool
808            Decides whether output is printed to the standard output.
809        '''
810        if self.N != 1:
811            raise ValueError("Correlator must be projected before fitting")
812
813        if fitrange is None:
814            if self.prange:
815                fitrange = self.prange
816            else:
817                fitrange = [0, self.T - 1]
818        else:
819            if not isinstance(fitrange, list):
820                raise TypeError("fitrange has to be a list with two elements")
821            if len(fitrange) != 2:
822                raise ValueError("fitrange has to have exactly two elements [fit_start, fit_stop]")
823
824        xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None])
825        ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None])
826        result = least_squares(xs, ys, function, silent=silent, **kwargs)
827        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):
829    def plateau(self, plateau_range=None, method="fit", auto_gamma=False):
830        """ Extract a plateau value from a Corr object
831
832        Parameters
833        ----------
834        plateau_range : list
835            list with two entries, indicating the first and the last timeslice
836            of the plateau region.
837        method : str
838            method to extract the plateau.
839                'fit' fits a constant to the plateau region
840                'avg', 'average' or 'mean' just average over the given timeslices.
841        auto_gamma : bool
842            apply gamma_method with default parameters to the Corr. Defaults to None
843        """
844        if not plateau_range:
845            if self.prange:
846                plateau_range = self.prange
847            else:
848                raise ValueError("no plateau range provided")
849        if self.N != 1:
850            raise ValueError("Correlator must be projected before getting a plateau.")
851        if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])):
852            raise ValueError("plateau is undefined at all timeslices in plateaurange.")
853        if auto_gamma:
854            self.gamma_method()
855        if method == "fit":
856            def const_func(a, t):
857                return a[0]
858            return self.fit(const_func, plateau_range)[0]
859        elif method in ["avg", "average", "mean"]:
860            returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None])
861            return returnvalue
862
863        else:
864            raise ValueError("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):
866    def set_prange(self, prange):
867        """Sets the attribute prange of the Corr object."""
868        if not len(prange) == 2:
869            raise ValueError("prange must be a list or array with two values")
870        if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))):
871            raise TypeError("Start and end point must be integers")
872        if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] <= prange[1]):
873            raise ValueError("Start and end point must define a range in the interval 0,T")
874
875        self.prange = prange
876        return

Sets the attribute prange of the Corr object.

def show( self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, fit_key=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None):
 878    def show(self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, fit_key=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None):
 879        """Plots the correlator using the tag of the correlator as label if available.
 880
 881        Parameters
 882        ----------
 883        x_range : list
 884            list of two values, determining the range of the x-axis e.g. [4, 8].
 885        comp : Corr or list of Corr
 886            Correlator or list of correlators which are plotted for comparison.
 887            The tags of these correlators are used as labels if available.
 888        logscale : bool
 889            Sets y-axis to logscale.
 890        plateau : Obs
 891            Plateau value to be visualized in the figure.
 892        fit_res : Fit_result
 893            Fit_result object to be visualized.
 894        fit_key : str
 895            Key for the fit function in Fit_result.fit_function (for combined fits).
 896        ylabel : str
 897            Label for the y-axis.
 898        save : str
 899            path to file in which the figure should be saved.
 900        auto_gamma : bool
 901            Apply the gamma method with standard parameters to all correlators and plateau values before plotting.
 902        hide_sigma : float
 903            Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors.
 904        references : list
 905            List of floating point values that are displayed as horizontal lines for reference.
 906        title : string
 907            Optional title of the figure.
 908        """
 909        if self.N != 1:
 910            raise ValueError("Correlator must be projected before plotting")
 911
 912        if auto_gamma:
 913            self.gamma_method()
 914
 915        if x_range is None:
 916            x_range = [0, self.T - 1]
 917
 918        fig = plt.figure()
 919        ax1 = fig.add_subplot(111)
 920
 921        x, y, y_err = self.plottable()
 922        if hide_sigma:
 923            hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1
 924        else:
 925            hide_from = None
 926        ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag)
 927        if logscale:
 928            ax1.set_yscale('log')
 929        else:
 930            if y_range is None:
 931                try:
 932                    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)])
 933                    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)])
 934                    ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)])
 935                except Exception:
 936                    pass
 937            else:
 938                ax1.set_ylim(y_range)
 939        if comp:
 940            if isinstance(comp, (Corr, list)):
 941                for corr in comp if isinstance(comp, list) else [comp]:
 942                    if auto_gamma:
 943                        corr.gamma_method()
 944                    x, y, y_err = corr.plottable()
 945                    if hide_sigma:
 946                        hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1
 947                    else:
 948                        hide_from = None
 949                    ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor'])
 950            else:
 951                raise TypeError("'comp' must be a correlator or a list of correlators.")
 952
 953        if plateau:
 954            if isinstance(plateau, Obs):
 955                if auto_gamma:
 956                    plateau.gamma_method()
 957                ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau))
 958                ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-')
 959            else:
 960                raise TypeError("'plateau' must be an Obs")
 961
 962        if references:
 963            if isinstance(references, list):
 964                for ref in references:
 965                    ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--')
 966            else:
 967                raise TypeError("'references' must be a list of floating pint values.")
 968
 969        if self.prange:
 970            ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',', color="black", zorder=0)
 971            ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',', color="black", zorder=0)
 972
 973        if fit_res:
 974            x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05)
 975            if isinstance(fit_res.fit_function, dict):
 976                if fit_key:
 977                    ax1.plot(x_samples, fit_res.fit_function[fit_key]([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2)
 978                else:
 979                    raise ValueError("Please provide a 'fit_key' for visualizing combined fits.")
 980            else:
 981                ax1.plot(x_samples, fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2)
 982
 983        ax1.set_xlabel(r'$x_0 / a$')
 984        if ylabel:
 985            ax1.set_ylabel(ylabel)
 986        ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5])
 987
 988        _handles, labels = ax1.get_legend_handles_labels()
 989        if labels:
 990            ax1.legend()
 991
 992        if title:
 993            plt.title(title)
 994
 995        plt.draw()
 996
 997        if save:
 998            if isinstance(save, str):
 999                fig.savefig(save, bbox_inches='tight')
1000            else:
1001                raise TypeError("'save' has to be a string.")

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

Parameters
  • x_range (list): list of two values, determining the range of the x-axis e.g. [4, 8].
  • comp (Corr or list of Corr): Correlator or list of correlators which are plotted for comparison. The tags of these correlators are used as labels if available.
  • logscale (bool): Sets y-axis to logscale.
  • plateau (Obs): Plateau value to be visualized in the figure.
  • fit_res (Fit_result): Fit_result object to be visualized.
  • fit_key (str): Key for the fit function in Fit_result.fit_function (for combined fits).
  • ylabel (str): Label for the y-axis.
  • save (str): path to file in which the figure should be saved.
  • auto_gamma (bool): Apply the gamma method with standard parameters to all correlators and plateau values before plotting.
  • hide_sigma (float): Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors.
  • references (list): List of floating point values that are displayed as horizontal lines for reference.
  • title (string): Optional title of the figure.
def spaghetti_plot(self, logscale=True):
1003    def spaghetti_plot(self, logscale=True):
1004        """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations.
1005
1006        Parameters
1007        ----------
1008        logscale : bool
1009            Determines whether the scale of the y-axis is logarithmic or standard.
1010        """
1011        if self.N != 1:
1012            raise ValueError("Correlator needs to be projected first.")
1013
1014        mc_names = list(set([item for sublist in [list(itertools.chain.from_iterable(map(o[0].e_content.get, o[0].mc_names))) for o in self.content if o is not None] for item in sublist]))
1015        x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content, strict=True) if o is not None]
1016
1017        for name in mc_names:
1018            data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T
1019
1020            fig = plt.figure()
1021            ax = fig.add_subplot(111)
1022            for dat in data:
1023                ax.plot(x0_vals, dat, ls='-', marker='')
1024
1025            if logscale is True:
1026                ax.set_yscale('log')
1027
1028            ax.set_xlabel(r'$x_0 / a$')
1029            plt.title(name)
1030            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):
1032    def dump(self, filename, datatype="json.gz", **kwargs):
1033        """Dumps the Corr into a file of chosen type
1034        Parameters
1035        ----------
1036        filename : str
1037            Name of the file to be saved.
1038        datatype : str
1039            Format of the exported file. Supported formats include
1040            "json.gz" and "pickle"
1041        path : str
1042            specifies a custom path for the file (default '.')
1043        """
1044        if datatype == "json.gz":
1045            from .input.json import dump_to_json
1046            if 'path' in kwargs:
1047                file_name = kwargs.get('path') + '/' + filename
1048            else:
1049                file_name = filename
1050            dump_to_json(self, file_name)
1051        elif datatype == "pickle":
1052            dump_object(self, filename, **kwargs)
1053        else:
1054            raise ValueError("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):
1056    def print(self, print_range=None):
1057        print(self.__repr__(print_range))
def sqrt(self):
1275    def sqrt(self):
1276        return self ** 0.5
def log(self):
1278    def log(self):
1279        newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content]
1280        return Corr(newcontent, prange=self.prange)
def exp(self):
1282    def exp(self):
1283        newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content]
1284        return Corr(newcontent, prange=self.prange)
def sin(self):
1299    def sin(self):
1300        return self._apply_func_to_corr(np.sin)
def cos(self):
1302    def cos(self):
1303        return self._apply_func_to_corr(np.cos)
def tan(self):
1305    def tan(self):
1306        return self._apply_func_to_corr(np.tan)
def sinh(self):
1308    def sinh(self):
1309        return self._apply_func_to_corr(np.sinh)
def cosh(self):
1311    def cosh(self):
1312        return self._apply_func_to_corr(np.cosh)
def tanh(self):
1314    def tanh(self):
1315        return self._apply_func_to_corr(np.tanh)
def arcsin(self):
1317    def arcsin(self):
1318        return self._apply_func_to_corr(np.arcsin)
def arccos(self):
1320    def arccos(self):
1321        return self._apply_func_to_corr(np.arccos)
def arctan(self):
1323    def arctan(self):
1324        return self._apply_func_to_corr(np.arctan)
def arcsinh(self):
1326    def arcsinh(self):
1327        return self._apply_func_to_corr(np.arcsinh)
def arccosh(self):
1329    def arccosh(self):
1330        return self._apply_func_to_corr(np.arccosh)
def arctanh(self):
1332    def arctanh(self):
1333        return self._apply_func_to_corr(np.arctanh)
real
1348    @property
1349    def real(self):
1350        def return_real(obs_OR_cobs):
1351            if isinstance(obs_OR_cobs.flatten()[0], CObs):
1352                return np.vectorize(lambda x: x.real)(obs_OR_cobs)
1353            else:
1354                return obs_OR_cobs
1355
1356        return self._apply_func_to_corr(return_real)
imag
1358    @property
1359    def imag(self):
1360        def return_imag(obs_OR_cobs):
1361            if isinstance(obs_OR_cobs.flatten()[0], CObs):
1362                return np.vectorize(lambda x: x.imag)(obs_OR_cobs)
1363            else:
1364                return obs_OR_cobs * 0  # So it stays the right type
1365
1366        return self._apply_func_to_corr(return_imag)
def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1368    def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1369        r''' Project large correlation matrix to lowest states
1370
1371        This method can be used to reduce the size of an (N x N) correlation matrix
1372        to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise
1373        is still small.
1374
1375        Parameters
1376        ----------
1377        Ntrunc: int
1378            Rank of the target matrix.
1379        tproj: int
1380            Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method.
1381            The default value is 3.
1382        t0proj: int
1383            Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly
1384            discouraged for O(a) improved theories, since the correctness of the procedure
1385            cannot be granted in this case. The default value is 2.
1386        basematrix : Corr
1387            Correlation matrix that is used to determine the eigenvectors of the
1388            lowest states based on a GEVP. basematrix is taken to be the Corr itself if
1389            is is not specified.
1390
1391        Notes
1392        -----
1393        We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving
1394        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}$
1395        and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the
1396        resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via
1397        $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large
1398        correlation matrix and to remove some noise that is added by irrelevant operators.
1399        This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated
1400        bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.
1401        '''
1402
1403        if self.N == 1:
1404            raise ValueError('Method cannot be applied to one-dimensional correlators.')
1405        if basematrix is None:
1406            basematrix = self
1407        if Ntrunc >= basematrix.N:
1408            raise ValueError(f'Cannot truncate using Ntrunc >= {basematrix.N}')
1409        if basematrix.N != self.N:
1410            raise ValueError('basematrix and targetmatrix have to be of the same size.')
1411
1412        evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc]
1413
1414        tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object)
1415        rmat = []
1416        for t in range(basematrix.T):
1417            if self.content[t] is None:
1418                rmat.append(None)
1419            else:
1420                for i in range(Ntrunc):
1421                    for j in range(Ntrunc):
1422                        tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j]
1423                rmat.append(np.copy(tmpmat))
1424
1425        return Corr(rmat)

Project large correlation matrix to lowest states

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

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

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

N