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) or self.content[t][0].value / self.content[t + 1][0].value < 0:
 731                    newcontent.append(None)
 732                else:
 733                    newcontent.append(self.content[t] / self.content[t + 1])
 734            if (all([x is None for x in newcontent])):
 735                raise ValueError('m_eff is undefined at all timeslices')
 736
 737            return np.log(Corr(newcontent, padding=[0, 1]))
 738
 739        elif variant == 'logsym':
 740            newcontent = []
 741            for t in range(1, self.T - 1):
 742                if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0) or self.content[t - 1][0].value / self.content[t + 1][0].value < 0:
 743                    newcontent.append(None)
 744                else:
 745                    newcontent.append(self.content[t - 1] / self.content[t + 1])
 746            if (all([x is None for x in newcontent])):
 747                raise ValueError('m_eff is undefined at all timeslices')
 748
 749            return np.log(Corr(newcontent, padding=[1, 1])) / 2
 750
 751        elif variant in ['periodic', 'cosh', 'sinh']:
 752            if variant in ['periodic', 'cosh']:
 753                func = anp.cosh
 754            else:
 755                func = anp.sinh
 756
 757            def root_function(x, d):
 758                return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d
 759
 760            newcontent = []
 761            for t in range(self.T - 1):
 762                if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0):
 763                    newcontent.append(None)
 764                # Fill the two timeslices in the middle of the lattice with their predecessors
 765                elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]:
 766                    newcontent.append(newcontent[-1])
 767                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
 768                    newcontent.append(None)
 769                else:
 770                    newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess)))
 771            if (all([x is None for x in newcontent])):
 772                raise ValueError('m_eff is undefined at all timeslices')
 773
 774            return Corr(newcontent, padding=[0, 1])
 775
 776        elif variant == 'arccosh':
 777            newcontent = []
 778            for t in range(1, self.T - 1):
 779                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):
 780                    newcontent.append(None)
 781                else:
 782                    newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t]))
 783            if (all([x is None for x in newcontent])):
 784                raise ValueError("m_eff is undefined at all timeslices")
 785            return np.arccosh(Corr(newcontent, padding=[1, 1]))
 786
 787        else:
 788            raise ValueError('Unknown variant.')
 789
 790    def fit(self, function, fitrange=None, silent=False, **kwargs):
 791        r'''Fits function to the data
 792
 793        Parameters
 794        ----------
 795        function : obj
 796            function to fit to the data. See fits.least_squares for details.
 797        fitrange : list
 798            Two element list containing the timeslices on which the fit is supposed to start and stop.
 799            Caution: This range is inclusive as opposed to standard python indexing.
 800            `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6.
 801            If not specified, self.prange or all timeslices are used.
 802        silent : bool
 803            Decides whether output is printed to the standard output.
 804        '''
 805        if self.N != 1:
 806            raise ValueError("Correlator must be projected before fitting")
 807
 808        if fitrange is None:
 809            if self.prange:
 810                fitrange = self.prange
 811            else:
 812                fitrange = [0, self.T - 1]
 813        else:
 814            if not isinstance(fitrange, list):
 815                raise TypeError("fitrange has to be a list with two elements")
 816            if len(fitrange) != 2:
 817                raise ValueError("fitrange has to have exactly two elements [fit_start, fit_stop]")
 818
 819        xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None])
 820        ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None])
 821        result = least_squares(xs, ys, function, silent=silent, **kwargs)
 822        return result
 823
 824    def plateau(self, plateau_range=None, method="fit", auto_gamma=False):
 825        """ Extract a plateau value from a Corr object
 826
 827        Parameters
 828        ----------
 829        plateau_range : list
 830            list with two entries, indicating the first and the last timeslice
 831            of the plateau region.
 832        method : str
 833            method to extract the plateau.
 834                'fit' fits a constant to the plateau region
 835                'avg', 'average' or 'mean' just average over the given timeslices.
 836        auto_gamma : bool
 837            apply gamma_method with default parameters to the Corr. Defaults to None
 838        """
 839        if not plateau_range:
 840            if self.prange:
 841                plateau_range = self.prange
 842            else:
 843                raise ValueError("no plateau range provided")
 844        if self.N != 1:
 845            raise ValueError("Correlator must be projected before getting a plateau.")
 846        if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])):
 847            raise ValueError("plateau is undefined at all timeslices in plateaurange.")
 848        if auto_gamma:
 849            self.gamma_method()
 850        if method == "fit":
 851            def const_func(a, t):
 852                return a[0]
 853            return self.fit(const_func, plateau_range)[0]
 854        elif method in ["avg", "average", "mean"]:
 855            returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None])
 856            return returnvalue
 857
 858        else:
 859            raise ValueError("Unsupported plateau method: " + method)
 860
 861    def set_prange(self, prange):
 862        """Sets the attribute prange of the Corr object."""
 863        if not len(prange) == 2:
 864            raise ValueError("prange must be a list or array with two values")
 865        if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))):
 866            raise TypeError("Start and end point must be integers")
 867        if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] <= prange[1]):
 868            raise ValueError("Start and end point must define a range in the interval 0,T")
 869
 870        self.prange = prange
 871
 872    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):
 873        """Plots the correlator using the tag of the correlator as label if available.
 874
 875        Parameters
 876        ----------
 877        x_range : list
 878            list of two values, determining the range of the x-axis e.g. [4, 8].
 879        comp : Corr or list of Corr
 880            Correlator or list of correlators which are plotted for comparison.
 881            The tags of these correlators are used as labels if available.
 882        logscale : bool
 883            Sets y-axis to logscale.
 884        plateau : Obs
 885            Plateau value to be visualized in the figure.
 886        fit_res : Fit_result
 887            Fit_result object to be visualized.
 888        fit_key : str
 889            Key for the fit function in Fit_result.fit_function (for combined fits).
 890        ylabel : str
 891            Label for the y-axis.
 892        save : str
 893            path to file in which the figure should be saved.
 894        auto_gamma : bool
 895            Apply the gamma method with standard parameters to all correlators and plateau values before plotting.
 896        hide_sigma : float
 897            Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors.
 898        references : list
 899            List of floating point values that are displayed as horizontal lines for reference.
 900        title : string
 901            Optional title of the figure.
 902        """
 903        if self.N != 1:
 904            raise ValueError("Correlator must be projected before plotting")
 905
 906        if auto_gamma:
 907            self.gamma_method()
 908
 909        if x_range is None:
 910            x_range = [0, self.T - 1]
 911
 912        fig = plt.figure()
 913        ax1 = fig.add_subplot(111)
 914
 915        x, y, y_err = self.plottable()
 916        if hide_sigma:
 917            hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1
 918        else:
 919            hide_from = None
 920        ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag)
 921        if logscale:
 922            ax1.set_yscale('log')
 923        else:
 924            if y_range is None:
 925                try:
 926                    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)])
 927                    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)])
 928                    ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)])
 929                except Exception:
 930                    pass
 931            else:
 932                ax1.set_ylim(y_range)
 933        if comp:
 934            if isinstance(comp, (Corr, list)):
 935                for corr in comp if isinstance(comp, list) else [comp]:
 936                    if auto_gamma:
 937                        corr.gamma_method()
 938                    x, y, y_err = corr.plottable()
 939                    if hide_sigma:
 940                        hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1
 941                    else:
 942                        hide_from = None
 943                    ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor'])
 944            else:
 945                raise TypeError("'comp' must be a correlator or a list of correlators.")
 946
 947        if plateau:
 948            if isinstance(plateau, Obs):
 949                if auto_gamma:
 950                    plateau.gamma_method()
 951                ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau))
 952                ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-')
 953            else:
 954                raise TypeError("'plateau' must be an Obs")
 955
 956        if references:
 957            if isinstance(references, list):
 958                for ref in references:
 959                    ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--')
 960            else:
 961                raise TypeError("'references' must be a list of floating pint values.")
 962
 963        if self.prange:
 964            ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',', color="black", zorder=0)
 965            ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',', color="black", zorder=0)
 966
 967        if fit_res:
 968            x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05)
 969            if isinstance(fit_res.fit_function, dict):
 970                if fit_key:
 971                    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)
 972                else:
 973                    raise ValueError("Please provide a 'fit_key' for visualizing combined fits.")
 974            else:
 975                ax1.plot(x_samples, fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2)
 976
 977        ax1.set_xlabel(r'$x_0 / a$')
 978        if ylabel:
 979            ax1.set_ylabel(ylabel)
 980        ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5])
 981
 982        _handles, labels = ax1.get_legend_handles_labels()
 983        if labels:
 984            ax1.legend()
 985
 986        if title:
 987            plt.title(title)
 988
 989        plt.draw()
 990
 991        if save:
 992            if isinstance(save, str):
 993                fig.savefig(save, bbox_inches='tight')
 994            else:
 995                raise TypeError("'save' has to be a string.")
 996
 997    def spaghetti_plot(self, logscale=True):
 998        """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations.
 999
1000        Parameters
1001        ----------
1002        logscale : bool
1003            Determines whether the scale of the y-axis is logarithmic or standard.
1004        """
1005        if self.N != 1:
1006            raise ValueError("Correlator needs to be projected first.")
1007
1008        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]))
1009        x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content, strict=True) if o is not None]
1010
1011        for name in mc_names:
1012            data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T
1013
1014            fig = plt.figure()
1015            ax = fig.add_subplot(111)
1016            for dat in data:
1017                ax.plot(x0_vals, dat, ls='-', marker='')
1018
1019            if logscale is True:
1020                ax.set_yscale('log')
1021
1022            ax.set_xlabel(r'$x_0 / a$')
1023            plt.title(name)
1024            plt.draw()
1025
1026    def dump(self, filename, datatype="json.gz", **kwargs):
1027        """Dumps the Corr into a file of chosen type
1028        Parameters
1029        ----------
1030        filename : str
1031            Name of the file to be saved.
1032        datatype : str
1033            Format of the exported file. Supported formats include
1034            "json.gz" and "pickle"
1035        path : str
1036            specifies a custom path for the file (default '.')
1037        """
1038        if datatype == "json.gz":
1039            from .input.json import dump_to_json
1040            if 'path' in kwargs:
1041                file_name = kwargs.get('path') + '/' + filename
1042            else:
1043                file_name = filename
1044            dump_to_json(self, file_name)
1045        elif datatype == "pickle":
1046            dump_object(self, filename, **kwargs)
1047        else:
1048            raise ValueError("Unknown datatype " + str(datatype))
1049
1050    def print(self, print_range=None):
1051        print(self.__repr__(print_range))
1052
1053    def __repr__(self, print_range=None):
1054        if print_range is None:
1055            print_range = [0, None]
1056
1057        content_string = ""
1058        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
1059
1060        if self.tag is not None:
1061            content_string += "Description: " + self.tag + "\n"
1062        if self.N != 1:
1063            return content_string
1064
1065        if print_range[1]:
1066            print_range[1] += 1
1067        content_string += 'x0/a\tCorr(x0/a)\n------------------\n'
1068        for i, sub_corr in enumerate(self.content[print_range[0]:print_range[1]]):
1069            if sub_corr is None:
1070                content_string += str(i + print_range[0]) + '\n'
1071            else:
1072                content_string += str(i + print_range[0])
1073                for element in sub_corr:
1074                    content_string += f"\t{element:+2}"
1075                content_string += '\n'
1076        return content_string
1077
1078    def __str__(self):
1079        return self.__repr__()
1080
1081    # We define the basic operations, that can be performed with correlators.
1082    # While */+- get defined here, they only work for Corr*Obs and not Obs*Corr.
1083    # This is because Obs*Corr checks Obs.__mul__ first and does not catch an exception.
1084    # One could try and tell Obs to check if the y in __mul__ is a Corr and
1085
1086    __array_priority__ = 10000
1087
1088    def __eq__(self, y):
1089        if isinstance(y, Corr):
1090            comp = np.asarray(y.content, dtype=object)
1091        else:
1092            comp = np.asarray(y)
1093        return np.asarray(self.content, dtype=object) == comp
1094
1095    __hash__ = None
1096
1097    def __add__(self, y):
1098        if isinstance(y, Corr):
1099            if ((self.N != y.N) or (self.T != y.T)):
1100                raise ValueError("Addition of Corrs with different shape")
1101            newcontent = []
1102            for t in range(self.T):
1103                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1104                    newcontent.append(None)
1105                else:
1106                    newcontent.append(self.content[t] + y.content[t])
1107            return Corr(newcontent)
1108
1109        elif isinstance(y, (Obs, int, float, CObs, complex)):
1110            newcontent = []
1111            for t in range(self.T):
1112                if _check_for_none(self, self.content[t]):
1113                    newcontent.append(None)
1114                else:
1115                    newcontent.append(self.content[t] + y)
1116            return Corr(newcontent, prange=self.prange)
1117        elif isinstance(y, np.ndarray):
1118            if y.shape == (self.T,):
1119                return Corr(list((np.array(self.content).T + y).T))
1120            else:
1121                raise ValueError("operands could not be broadcast together")
1122        else:
1123            raise TypeError("Corr + wrong type")
1124
1125    def __mul__(self, y):
1126        if isinstance(y, Corr):
1127            if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T):
1128                raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T")
1129            newcontent = []
1130            for t in range(self.T):
1131                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1132                    newcontent.append(None)
1133                else:
1134                    newcontent.append(self.content[t] * y.content[t])
1135            return Corr(newcontent)
1136
1137        elif isinstance(y, (Obs, int, float, CObs, complex)):
1138            newcontent = []
1139            for t in range(self.T):
1140                if _check_for_none(self, self.content[t]):
1141                    newcontent.append(None)
1142                else:
1143                    newcontent.append(self.content[t] * y)
1144            return Corr(newcontent, prange=self.prange)
1145        elif isinstance(y, np.ndarray):
1146            if y.shape == (self.T,):
1147                return Corr(list((np.array(self.content).T * y).T))
1148            else:
1149                raise ValueError("operands could not be broadcast together")
1150        else:
1151            raise TypeError("Corr * wrong type")
1152
1153    def __matmul__(self, y):
1154        if isinstance(y, np.ndarray):
1155            if y.ndim != 2 or y.shape[0] != y.shape[1]:
1156                raise ValueError("Can only multiply correlators by square matrices.")
1157            if not self.N == y.shape[0]:
1158                raise ValueError("matmul: mismatch of matrix dimensions")
1159            newcontent = []
1160            for t in range(self.T):
1161                if _check_for_none(self, self.content[t]):
1162                    newcontent.append(None)
1163                else:
1164                    newcontent.append(self.content[t] @ y)
1165            return Corr(newcontent)
1166        elif isinstance(y, Corr):
1167            if not self.N == y.N:
1168                raise ValueError("matmul: mismatch of matrix dimensions")
1169            newcontent = []
1170            for t in range(self.T):
1171                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1172                    newcontent.append(None)
1173                else:
1174                    newcontent.append(self.content[t] @ y.content[t])
1175            return Corr(newcontent)
1176
1177        else:
1178            return NotImplemented
1179
1180    def __rmatmul__(self, y):
1181        if isinstance(y, np.ndarray):
1182            if y.ndim != 2 or y.shape[0] != y.shape[1]:
1183                raise ValueError("Can only multiply correlators by square matrices.")
1184            if not self.N == y.shape[0]:
1185                raise ValueError("matmul: mismatch of matrix dimensions")
1186            newcontent = []
1187            for t in range(self.T):
1188                if _check_for_none(self, self.content[t]):
1189                    newcontent.append(None)
1190                else:
1191                    newcontent.append(y @ self.content[t])
1192            return Corr(newcontent)
1193        else:
1194            return NotImplemented
1195
1196    def __truediv__(self, y):
1197        if isinstance(y, Corr):
1198            if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T):
1199                raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T")
1200            newcontent = []
1201            for t in range(self.T):
1202                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1203                    newcontent.append(None)
1204                else:
1205                    newcontent.append(self.content[t] / y.content[t])
1206            for t in range(self.T):
1207                if _check_for_none(self, newcontent[t]):
1208                    continue
1209                if np.isnan(np.sum(newcontent[t]).value):
1210                    newcontent[t] = None
1211
1212            if all([item is None for item in newcontent]):
1213                raise ValueError("Division returns completely undefined correlator")
1214            return Corr(newcontent)
1215
1216        elif isinstance(y, (Obs, CObs)):
1217            if isinstance(y, Obs):
1218                if y.value == 0:
1219                    raise ValueError('Division by zero will return undefined correlator')
1220            if isinstance(y, CObs):
1221                if y.is_zero():
1222                    raise ValueError('Division by zero will return undefined correlator')
1223
1224            newcontent = []
1225            for t in range(self.T):
1226                if _check_for_none(self, self.content[t]):
1227                    newcontent.append(None)
1228                else:
1229                    newcontent.append(self.content[t] / y)
1230            return Corr(newcontent, prange=self.prange)
1231
1232        elif isinstance(y, (int, float)):
1233            if y == 0:
1234                raise ValueError('Division by zero will return undefined correlator')
1235            newcontent = []
1236            for t in range(self.T):
1237                if _check_for_none(self, self.content[t]):
1238                    newcontent.append(None)
1239                else:
1240                    newcontent.append(self.content[t] / y)
1241            return Corr(newcontent, prange=self.prange)
1242        elif isinstance(y, np.ndarray):
1243            if y.shape == (self.T,):
1244                return Corr(list((np.array(self.content).T / y).T))
1245            else:
1246                raise ValueError("operands could not be broadcast together")
1247        else:
1248            raise TypeError('Corr / wrong type')
1249
1250    def __neg__(self):
1251        newcontent = [None if _check_for_none(self, item) else -1. * item for item in self.content]
1252        return Corr(newcontent, prange=self.prange)
1253
1254    def __sub__(self, y):
1255        return self + (-y)
1256
1257    def __pow__(self, y):
1258        if isinstance(y, (Obs, int, float, CObs)):
1259            newcontent = [None if _check_for_none(self, item) else item**y for item in self.content]
1260            return Corr(newcontent, prange=self.prange)
1261        else:
1262            raise TypeError('Type of exponent not supported')
1263
1264    def __abs__(self):
1265        newcontent = [None if _check_for_none(self, item) else np.abs(item) for item in self.content]
1266        return Corr(newcontent, prange=self.prange)
1267
1268    # The numpy functions:
1269    def sqrt(self):
1270        return self ** 0.5
1271
1272    def log(self):
1273        newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content]
1274        return Corr(newcontent, prange=self.prange)
1275
1276    def exp(self):
1277        newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content]
1278        return Corr(newcontent, prange=self.prange)
1279
1280    def _apply_func_to_corr(self, func):
1281        newcontent = [None if _check_for_none(self, item) else func(item) for item in self.content]
1282        for t in range(self.T):
1283            if _check_for_none(self, newcontent[t]):
1284                continue
1285            tmp_sum = np.sum(newcontent[t])
1286            if hasattr(tmp_sum, "value"):
1287                if np.isnan(tmp_sum.value):
1288                    newcontent[t] = None
1289        if all([item is None for item in newcontent]):
1290            raise ValueError('Operation returns undefined correlator')
1291        return Corr(newcontent)
1292
1293    def sin(self):
1294        return self._apply_func_to_corr(np.sin)
1295
1296    def cos(self):
1297        return self._apply_func_to_corr(np.cos)
1298
1299    def tan(self):
1300        return self._apply_func_to_corr(np.tan)
1301
1302    def sinh(self):
1303        return self._apply_func_to_corr(np.sinh)
1304
1305    def cosh(self):
1306        return self._apply_func_to_corr(np.cosh)
1307
1308    def tanh(self):
1309        return self._apply_func_to_corr(np.tanh)
1310
1311    def arcsin(self):
1312        return self._apply_func_to_corr(np.arcsin)
1313
1314    def arccos(self):
1315        return self._apply_func_to_corr(np.arccos)
1316
1317    def arctan(self):
1318        return self._apply_func_to_corr(np.arctan)
1319
1320    def arcsinh(self):
1321        return self._apply_func_to_corr(np.arcsinh)
1322
1323    def arccosh(self):
1324        return self._apply_func_to_corr(np.arccosh)
1325
1326    def arctanh(self):
1327        return self._apply_func_to_corr(np.arctanh)
1328
1329    # Right hand side operations (require tweak in main module to work)
1330    def __radd__(self, y):
1331        return self + y
1332
1333    def __rsub__(self, y):
1334        return -self + y
1335
1336    def __rmul__(self, y):
1337        return self * y
1338
1339    def __rtruediv__(self, y):
1340        return (self / y) ** (-1)
1341
1342    @property
1343    def real(self):
1344        def return_real(obs_OR_cobs):
1345            if isinstance(obs_OR_cobs.flatten()[0], CObs):
1346                return np.vectorize(lambda x: x.real)(obs_OR_cobs)
1347            else:
1348                return obs_OR_cobs
1349
1350        return self._apply_func_to_corr(return_real)
1351
1352    @property
1353    def imag(self):
1354        def return_imag(obs_OR_cobs):
1355            if isinstance(obs_OR_cobs.flatten()[0], CObs):
1356                return np.vectorize(lambda x: x.imag)(obs_OR_cobs)
1357            else:
1358                return obs_OR_cobs * 0  # So it stays the right type
1359
1360        return self._apply_func_to_corr(return_imag)
1361
1362    def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1363        r''' Project large correlation matrix to lowest states
1364
1365        This method can be used to reduce the size of an (N x N) correlation matrix
1366        to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise
1367        is still small.
1368
1369        Parameters
1370        ----------
1371        Ntrunc: int
1372            Rank of the target matrix.
1373        tproj: int
1374            Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method.
1375            The default value is 3.
1376        t0proj: int
1377            Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly
1378            discouraged for O(a) improved theories, since the correctness of the procedure
1379            cannot be granted in this case. The default value is 2.
1380        basematrix : Corr
1381            Correlation matrix that is used to determine the eigenvectors of the
1382            lowest states based on a GEVP. basematrix is taken to be the Corr itself if
1383            is is not specified.
1384
1385        Notes
1386        -----
1387        We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving
1388        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}$
1389        and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the
1390        resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via
1391        $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large
1392        correlation matrix and to remove some noise that is added by irrelevant operators.
1393        This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated
1394        bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.
1395        '''
1396
1397        if self.N == 1:
1398            raise ValueError('Method cannot be applied to one-dimensional correlators.')
1399        if basematrix is None:
1400            basematrix = self
1401        if Ntrunc >= basematrix.N:
1402            raise ValueError(f'Cannot truncate using Ntrunc >= {basematrix.N}')
1403        if basematrix.N != self.N:
1404            raise ValueError('basematrix and targetmatrix have to be of the same size.')
1405
1406        evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc]
1407
1408        tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object)
1409        rmat = []
1410        for t in range(basematrix.T):
1411            if self.content[t] is None:
1412                rmat.append(None)
1413            else:
1414                for i in range(Ntrunc):
1415                    for j in range(Ntrunc):
1416                        tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j]
1417                rmat.append(np.copy(tmpmat))
1418
1419        return Corr(rmat)
1420
1421
1422def _sort_vectors(vec_set_in, ts):
1423    """Helper function used to find a set of Eigenvectors consistent over all timeslices"""
1424
1425    if isinstance(vec_set_in[ts][0][0], Obs):
1426        vec_set = [anp.vectorize(float)(vi) if vi is not None else vi for vi in vec_set_in]
1427    else:
1428        vec_set = vec_set_in
1429    reference_sorting = np.array(vec_set[ts])
1430    N = reference_sorting.shape[0]
1431    sorted_vec_set = []
1432    for t in range(len(vec_set)):
1433        if vec_set[t] is None:
1434            sorted_vec_set.append(None)
1435        elif not t == ts:
1436            perms = [list(o) for o in permutations([i for i in range(N)], N)]
1437            best_score = 0
1438            for perm in perms:
1439                current_score = 1
1440                for k in range(N):
1441                    new_sorting = reference_sorting.copy()
1442                    new_sorting[perm[k], :] = vec_set[t][k]
1443                    current_score *= abs(np.linalg.det(new_sorting))
1444                if current_score > best_score:
1445                    best_score = current_score
1446                    best_perm = perm
1447            sorted_vec_set.append([vec_set_in[t][k] for k in best_perm])
1448        else:
1449            sorted_vec_set.append(vec_set_in[t])
1450
1451    return sorted_vec_set
1452
1453
1454def _check_for_none(corr, entry):
1455    """Checks if entry for correlator corr is None"""
1456    return len(list(filter(None, np.asarray(entry).flatten()))) < corr.N ** 2
1457
1458
1459def _GEVP_solver(Gt, G0, method='eigh', chol_inv=None):
1460    r"""Helper function for solving the GEVP and sorting the eigenvectors.
1461
1462    Solves $G(t)v_i=\lambda_i G(t_0)v_i$ and returns the eigenvectors v_i
1463
1464    The helper function assumes that both provided matrices are symmetric and
1465    only processes the lower triangular part of both matrices. In case the matrices
1466    are not symmetric the upper triangular parts are effectively discarded.
1467
1468    Parameters
1469    ----------
1470    Gt : array
1471        The correlator at time t for the left hand side of the GEVP
1472    G0 : array
1473        The correlator at time t0 for the right hand side of the GEVP
1474    Method used to solve the GEVP.
1475       - "eigh": Use scipy.linalg.eigh to solve the GEVP.
1476       - "cholesky": Use manually implemented solution via the Cholesky decomposition.
1477    chol_inv : array, optional
1478        Inverse of the Cholesky decomposition of G0. May be provided to
1479        speed up the computation in the case of method=='cholesky'
1480
1481    """
1482    if isinstance(G0[0][0], Obs):
1483        vector_obs = True
1484    else:
1485        vector_obs = False
1486
1487    if method == 'cholesky':
1488        if vector_obs:
1489            cholesky = linalg.cholesky
1490            inv = linalg.inv
1491            eigv = linalg.eigv
1492            matmul = linalg.matmul
1493        else:
1494            cholesky = np.linalg.cholesky
1495            inv = np.linalg.inv
1496
1497            def eigv(x, **kwargs):
1498                return np.linalg.eigh(x)[1]
1499
1500            def matmul(*operands):
1501                return np.linalg.multi_dot(operands)
1502        N = Gt.shape[0]
1503        output = [[] for j in range(N)]
1504        if chol_inv is None:
1505            chol = cholesky(G0)  # This will automatically report if the matrix is not pos-def
1506            chol_inv = inv(chol)
1507
1508        try:
1509            new_matrix = matmul(chol_inv, Gt, chol_inv.T)
1510            ev = eigv(new_matrix)
1511            ev = matmul(chol_inv.T, ev)
1512            output = np.flip(ev, axis=1).T
1513        except (np.linalg.LinAlgError, TypeError, ValueError):  # The above code can fail because of linalg-errors or because the entry of the corr is None
1514            for s in range(N):
1515                output[s] = None
1516        return output
1517    elif method == 'eigh':
1518        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) or self.content[t][0].value / self.content[t + 1][0].value < 0:
 732                    newcontent.append(None)
 733                else:
 734                    newcontent.append(self.content[t] / self.content[t + 1])
 735            if (all([x is None for x in newcontent])):
 736                raise ValueError('m_eff is undefined at all timeslices')
 737
 738            return np.log(Corr(newcontent, padding=[0, 1]))
 739
 740        elif variant == 'logsym':
 741            newcontent = []
 742            for t in range(1, self.T - 1):
 743                if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0) or self.content[t - 1][0].value / self.content[t + 1][0].value < 0:
 744                    newcontent.append(None)
 745                else:
 746                    newcontent.append(self.content[t - 1] / self.content[t + 1])
 747            if (all([x is None for x in newcontent])):
 748                raise ValueError('m_eff is undefined at all timeslices')
 749
 750            return np.log(Corr(newcontent, padding=[1, 1])) / 2
 751
 752        elif variant in ['periodic', 'cosh', 'sinh']:
 753            if variant in ['periodic', 'cosh']:
 754                func = anp.cosh
 755            else:
 756                func = anp.sinh
 757
 758            def root_function(x, d):
 759                return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d
 760
 761            newcontent = []
 762            for t in range(self.T - 1):
 763                if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0):
 764                    newcontent.append(None)
 765                # Fill the two timeslices in the middle of the lattice with their predecessors
 766                elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]:
 767                    newcontent.append(newcontent[-1])
 768                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
 769                    newcontent.append(None)
 770                else:
 771                    newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess)))
 772            if (all([x is None for x in newcontent])):
 773                raise ValueError('m_eff is undefined at all timeslices')
 774
 775            return Corr(newcontent, padding=[0, 1])
 776
 777        elif variant == 'arccosh':
 778            newcontent = []
 779            for t in range(1, self.T - 1):
 780                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):
 781                    newcontent.append(None)
 782                else:
 783                    newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t]))
 784            if (all([x is None for x in newcontent])):
 785                raise ValueError("m_eff is undefined at all timeslices")
 786            return np.arccosh(Corr(newcontent, padding=[1, 1]))
 787
 788        else:
 789            raise ValueError('Unknown variant.')
 790
 791    def fit(self, function, fitrange=None, silent=False, **kwargs):
 792        r'''Fits function to the data
 793
 794        Parameters
 795        ----------
 796        function : obj
 797            function to fit to the data. See fits.least_squares for details.
 798        fitrange : list
 799            Two element list containing the timeslices on which the fit is supposed to start and stop.
 800            Caution: This range is inclusive as opposed to standard python indexing.
 801            `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6.
 802            If not specified, self.prange or all timeslices are used.
 803        silent : bool
 804            Decides whether output is printed to the standard output.
 805        '''
 806        if self.N != 1:
 807            raise ValueError("Correlator must be projected before fitting")
 808
 809        if fitrange is None:
 810            if self.prange:
 811                fitrange = self.prange
 812            else:
 813                fitrange = [0, self.T - 1]
 814        else:
 815            if not isinstance(fitrange, list):
 816                raise TypeError("fitrange has to be a list with two elements")
 817            if len(fitrange) != 2:
 818                raise ValueError("fitrange has to have exactly two elements [fit_start, fit_stop]")
 819
 820        xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None])
 821        ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None])
 822        result = least_squares(xs, ys, function, silent=silent, **kwargs)
 823        return result
 824
 825    def plateau(self, plateau_range=None, method="fit", auto_gamma=False):
 826        """ Extract a plateau value from a Corr object
 827
 828        Parameters
 829        ----------
 830        plateau_range : list
 831            list with two entries, indicating the first and the last timeslice
 832            of the plateau region.
 833        method : str
 834            method to extract the plateau.
 835                'fit' fits a constant to the plateau region
 836                'avg', 'average' or 'mean' just average over the given timeslices.
 837        auto_gamma : bool
 838            apply gamma_method with default parameters to the Corr. Defaults to None
 839        """
 840        if not plateau_range:
 841            if self.prange:
 842                plateau_range = self.prange
 843            else:
 844                raise ValueError("no plateau range provided")
 845        if self.N != 1:
 846            raise ValueError("Correlator must be projected before getting a plateau.")
 847        if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])):
 848            raise ValueError("plateau is undefined at all timeslices in plateaurange.")
 849        if auto_gamma:
 850            self.gamma_method()
 851        if method == "fit":
 852            def const_func(a, t):
 853                return a[0]
 854            return self.fit(const_func, plateau_range)[0]
 855        elif method in ["avg", "average", "mean"]:
 856            returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None])
 857            return returnvalue
 858
 859        else:
 860            raise ValueError("Unsupported plateau method: " + method)
 861
 862    def set_prange(self, prange):
 863        """Sets the attribute prange of the Corr object."""
 864        if not len(prange) == 2:
 865            raise ValueError("prange must be a list or array with two values")
 866        if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))):
 867            raise TypeError("Start and end point must be integers")
 868        if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] <= prange[1]):
 869            raise ValueError("Start and end point must define a range in the interval 0,T")
 870
 871        self.prange = prange
 872
 873    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):
 874        """Plots the correlator using the tag of the correlator as label if available.
 875
 876        Parameters
 877        ----------
 878        x_range : list
 879            list of two values, determining the range of the x-axis e.g. [4, 8].
 880        comp : Corr or list of Corr
 881            Correlator or list of correlators which are plotted for comparison.
 882            The tags of these correlators are used as labels if available.
 883        logscale : bool
 884            Sets y-axis to logscale.
 885        plateau : Obs
 886            Plateau value to be visualized in the figure.
 887        fit_res : Fit_result
 888            Fit_result object to be visualized.
 889        fit_key : str
 890            Key for the fit function in Fit_result.fit_function (for combined fits).
 891        ylabel : str
 892            Label for the y-axis.
 893        save : str
 894            path to file in which the figure should be saved.
 895        auto_gamma : bool
 896            Apply the gamma method with standard parameters to all correlators and plateau values before plotting.
 897        hide_sigma : float
 898            Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors.
 899        references : list
 900            List of floating point values that are displayed as horizontal lines for reference.
 901        title : string
 902            Optional title of the figure.
 903        """
 904        if self.N != 1:
 905            raise ValueError("Correlator must be projected before plotting")
 906
 907        if auto_gamma:
 908            self.gamma_method()
 909
 910        if x_range is None:
 911            x_range = [0, self.T - 1]
 912
 913        fig = plt.figure()
 914        ax1 = fig.add_subplot(111)
 915
 916        x, y, y_err = self.plottable()
 917        if hide_sigma:
 918            hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1
 919        else:
 920            hide_from = None
 921        ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag)
 922        if logscale:
 923            ax1.set_yscale('log')
 924        else:
 925            if y_range is None:
 926                try:
 927                    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)])
 928                    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)])
 929                    ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)])
 930                except Exception:
 931                    pass
 932            else:
 933                ax1.set_ylim(y_range)
 934        if comp:
 935            if isinstance(comp, (Corr, list)):
 936                for corr in comp if isinstance(comp, list) else [comp]:
 937                    if auto_gamma:
 938                        corr.gamma_method()
 939                    x, y, y_err = corr.plottable()
 940                    if hide_sigma:
 941                        hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1
 942                    else:
 943                        hide_from = None
 944                    ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor'])
 945            else:
 946                raise TypeError("'comp' must be a correlator or a list of correlators.")
 947
 948        if plateau:
 949            if isinstance(plateau, Obs):
 950                if auto_gamma:
 951                    plateau.gamma_method()
 952                ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau))
 953                ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-')
 954            else:
 955                raise TypeError("'plateau' must be an Obs")
 956
 957        if references:
 958            if isinstance(references, list):
 959                for ref in references:
 960                    ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--')
 961            else:
 962                raise TypeError("'references' must be a list of floating pint values.")
 963
 964        if self.prange:
 965            ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',', color="black", zorder=0)
 966            ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',', color="black", zorder=0)
 967
 968        if fit_res:
 969            x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05)
 970            if isinstance(fit_res.fit_function, dict):
 971                if fit_key:
 972                    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)
 973                else:
 974                    raise ValueError("Please provide a 'fit_key' for visualizing combined fits.")
 975            else:
 976                ax1.plot(x_samples, fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2)
 977
 978        ax1.set_xlabel(r'$x_0 / a$')
 979        if ylabel:
 980            ax1.set_ylabel(ylabel)
 981        ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5])
 982
 983        _handles, labels = ax1.get_legend_handles_labels()
 984        if labels:
 985            ax1.legend()
 986
 987        if title:
 988            plt.title(title)
 989
 990        plt.draw()
 991
 992        if save:
 993            if isinstance(save, str):
 994                fig.savefig(save, bbox_inches='tight')
 995            else:
 996                raise TypeError("'save' has to be a string.")
 997
 998    def spaghetti_plot(self, logscale=True):
 999        """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations.
1000
1001        Parameters
1002        ----------
1003        logscale : bool
1004            Determines whether the scale of the y-axis is logarithmic or standard.
1005        """
1006        if self.N != 1:
1007            raise ValueError("Correlator needs to be projected first.")
1008
1009        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]))
1010        x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content, strict=True) if o is not None]
1011
1012        for name in mc_names:
1013            data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T
1014
1015            fig = plt.figure()
1016            ax = fig.add_subplot(111)
1017            for dat in data:
1018                ax.plot(x0_vals, dat, ls='-', marker='')
1019
1020            if logscale is True:
1021                ax.set_yscale('log')
1022
1023            ax.set_xlabel(r'$x_0 / a$')
1024            plt.title(name)
1025            plt.draw()
1026
1027    def dump(self, filename, datatype="json.gz", **kwargs):
1028        """Dumps the Corr into a file of chosen type
1029        Parameters
1030        ----------
1031        filename : str
1032            Name of the file to be saved.
1033        datatype : str
1034            Format of the exported file. Supported formats include
1035            "json.gz" and "pickle"
1036        path : str
1037            specifies a custom path for the file (default '.')
1038        """
1039        if datatype == "json.gz":
1040            from .input.json import dump_to_json
1041            if 'path' in kwargs:
1042                file_name = kwargs.get('path') + '/' + filename
1043            else:
1044                file_name = filename
1045            dump_to_json(self, file_name)
1046        elif datatype == "pickle":
1047            dump_object(self, filename, **kwargs)
1048        else:
1049            raise ValueError("Unknown datatype " + str(datatype))
1050
1051    def print(self, print_range=None):
1052        print(self.__repr__(print_range))
1053
1054    def __repr__(self, print_range=None):
1055        if print_range is None:
1056            print_range = [0, None]
1057
1058        content_string = ""
1059        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
1060
1061        if self.tag is not None:
1062            content_string += "Description: " + self.tag + "\n"
1063        if self.N != 1:
1064            return content_string
1065
1066        if print_range[1]:
1067            print_range[1] += 1
1068        content_string += 'x0/a\tCorr(x0/a)\n------------------\n'
1069        for i, sub_corr in enumerate(self.content[print_range[0]:print_range[1]]):
1070            if sub_corr is None:
1071                content_string += str(i + print_range[0]) + '\n'
1072            else:
1073                content_string += str(i + print_range[0])
1074                for element in sub_corr:
1075                    content_string += f"\t{element:+2}"
1076                content_string += '\n'
1077        return content_string
1078
1079    def __str__(self):
1080        return self.__repr__()
1081
1082    # We define the basic operations, that can be performed with correlators.
1083    # While */+- get defined here, they only work for Corr*Obs and not Obs*Corr.
1084    # This is because Obs*Corr checks Obs.__mul__ first and does not catch an exception.
1085    # One could try and tell Obs to check if the y in __mul__ is a Corr and
1086
1087    __array_priority__ = 10000
1088
1089    def __eq__(self, y):
1090        if isinstance(y, Corr):
1091            comp = np.asarray(y.content, dtype=object)
1092        else:
1093            comp = np.asarray(y)
1094        return np.asarray(self.content, dtype=object) == comp
1095
1096    __hash__ = None
1097
1098    def __add__(self, y):
1099        if isinstance(y, Corr):
1100            if ((self.N != y.N) or (self.T != y.T)):
1101                raise ValueError("Addition of Corrs with different shape")
1102            newcontent = []
1103            for t in range(self.T):
1104                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1105                    newcontent.append(None)
1106                else:
1107                    newcontent.append(self.content[t] + y.content[t])
1108            return Corr(newcontent)
1109
1110        elif isinstance(y, (Obs, int, float, CObs, complex)):
1111            newcontent = []
1112            for t in range(self.T):
1113                if _check_for_none(self, self.content[t]):
1114                    newcontent.append(None)
1115                else:
1116                    newcontent.append(self.content[t] + y)
1117            return Corr(newcontent, prange=self.prange)
1118        elif isinstance(y, np.ndarray):
1119            if y.shape == (self.T,):
1120                return Corr(list((np.array(self.content).T + y).T))
1121            else:
1122                raise ValueError("operands could not be broadcast together")
1123        else:
1124            raise TypeError("Corr + wrong type")
1125
1126    def __mul__(self, y):
1127        if isinstance(y, Corr):
1128            if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T):
1129                raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T")
1130            newcontent = []
1131            for t in range(self.T):
1132                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1133                    newcontent.append(None)
1134                else:
1135                    newcontent.append(self.content[t] * y.content[t])
1136            return Corr(newcontent)
1137
1138        elif isinstance(y, (Obs, int, float, CObs, complex)):
1139            newcontent = []
1140            for t in range(self.T):
1141                if _check_for_none(self, self.content[t]):
1142                    newcontent.append(None)
1143                else:
1144                    newcontent.append(self.content[t] * y)
1145            return Corr(newcontent, prange=self.prange)
1146        elif isinstance(y, np.ndarray):
1147            if y.shape == (self.T,):
1148                return Corr(list((np.array(self.content).T * y).T))
1149            else:
1150                raise ValueError("operands could not be broadcast together")
1151        else:
1152            raise TypeError("Corr * wrong type")
1153
1154    def __matmul__(self, y):
1155        if isinstance(y, np.ndarray):
1156            if y.ndim != 2 or y.shape[0] != y.shape[1]:
1157                raise ValueError("Can only multiply correlators by square matrices.")
1158            if not self.N == y.shape[0]:
1159                raise ValueError("matmul: mismatch of matrix dimensions")
1160            newcontent = []
1161            for t in range(self.T):
1162                if _check_for_none(self, self.content[t]):
1163                    newcontent.append(None)
1164                else:
1165                    newcontent.append(self.content[t] @ y)
1166            return Corr(newcontent)
1167        elif isinstance(y, Corr):
1168            if not self.N == y.N:
1169                raise ValueError("matmul: mismatch of matrix dimensions")
1170            newcontent = []
1171            for t in range(self.T):
1172                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1173                    newcontent.append(None)
1174                else:
1175                    newcontent.append(self.content[t] @ y.content[t])
1176            return Corr(newcontent)
1177
1178        else:
1179            return NotImplemented
1180
1181    def __rmatmul__(self, y):
1182        if isinstance(y, np.ndarray):
1183            if y.ndim != 2 or y.shape[0] != y.shape[1]:
1184                raise ValueError("Can only multiply correlators by square matrices.")
1185            if not self.N == y.shape[0]:
1186                raise ValueError("matmul: mismatch of matrix dimensions")
1187            newcontent = []
1188            for t in range(self.T):
1189                if _check_for_none(self, self.content[t]):
1190                    newcontent.append(None)
1191                else:
1192                    newcontent.append(y @ self.content[t])
1193            return Corr(newcontent)
1194        else:
1195            return NotImplemented
1196
1197    def __truediv__(self, y):
1198        if isinstance(y, Corr):
1199            if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T):
1200                raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T")
1201            newcontent = []
1202            for t in range(self.T):
1203                if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]):
1204                    newcontent.append(None)
1205                else:
1206                    newcontent.append(self.content[t] / y.content[t])
1207            for t in range(self.T):
1208                if _check_for_none(self, newcontent[t]):
1209                    continue
1210                if np.isnan(np.sum(newcontent[t]).value):
1211                    newcontent[t] = None
1212
1213            if all([item is None for item in newcontent]):
1214                raise ValueError("Division returns completely undefined correlator")
1215            return Corr(newcontent)
1216
1217        elif isinstance(y, (Obs, CObs)):
1218            if isinstance(y, Obs):
1219                if y.value == 0:
1220                    raise ValueError('Division by zero will return undefined correlator')
1221            if isinstance(y, CObs):
1222                if y.is_zero():
1223                    raise ValueError('Division by zero will return undefined correlator')
1224
1225            newcontent = []
1226            for t in range(self.T):
1227                if _check_for_none(self, self.content[t]):
1228                    newcontent.append(None)
1229                else:
1230                    newcontent.append(self.content[t] / y)
1231            return Corr(newcontent, prange=self.prange)
1232
1233        elif isinstance(y, (int, float)):
1234            if y == 0:
1235                raise ValueError('Division by zero will return undefined correlator')
1236            newcontent = []
1237            for t in range(self.T):
1238                if _check_for_none(self, self.content[t]):
1239                    newcontent.append(None)
1240                else:
1241                    newcontent.append(self.content[t] / y)
1242            return Corr(newcontent, prange=self.prange)
1243        elif isinstance(y, np.ndarray):
1244            if y.shape == (self.T,):
1245                return Corr(list((np.array(self.content).T / y).T))
1246            else:
1247                raise ValueError("operands could not be broadcast together")
1248        else:
1249            raise TypeError('Corr / wrong type')
1250
1251    def __neg__(self):
1252        newcontent = [None if _check_for_none(self, item) else -1. * item for item in self.content]
1253        return Corr(newcontent, prange=self.prange)
1254
1255    def __sub__(self, y):
1256        return self + (-y)
1257
1258    def __pow__(self, y):
1259        if isinstance(y, (Obs, int, float, CObs)):
1260            newcontent = [None if _check_for_none(self, item) else item**y for item in self.content]
1261            return Corr(newcontent, prange=self.prange)
1262        else:
1263            raise TypeError('Type of exponent not supported')
1264
1265    def __abs__(self):
1266        newcontent = [None if _check_for_none(self, item) else np.abs(item) for item in self.content]
1267        return Corr(newcontent, prange=self.prange)
1268
1269    # The numpy functions:
1270    def sqrt(self):
1271        return self ** 0.5
1272
1273    def log(self):
1274        newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content]
1275        return Corr(newcontent, prange=self.prange)
1276
1277    def exp(self):
1278        newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content]
1279        return Corr(newcontent, prange=self.prange)
1280
1281    def _apply_func_to_corr(self, func):
1282        newcontent = [None if _check_for_none(self, item) else func(item) for item in self.content]
1283        for t in range(self.T):
1284            if _check_for_none(self, newcontent[t]):
1285                continue
1286            tmp_sum = np.sum(newcontent[t])
1287            if hasattr(tmp_sum, "value"):
1288                if np.isnan(tmp_sum.value):
1289                    newcontent[t] = None
1290        if all([item is None for item in newcontent]):
1291            raise ValueError('Operation returns undefined correlator')
1292        return Corr(newcontent)
1293
1294    def sin(self):
1295        return self._apply_func_to_corr(np.sin)
1296
1297    def cos(self):
1298        return self._apply_func_to_corr(np.cos)
1299
1300    def tan(self):
1301        return self._apply_func_to_corr(np.tan)
1302
1303    def sinh(self):
1304        return self._apply_func_to_corr(np.sinh)
1305
1306    def cosh(self):
1307        return self._apply_func_to_corr(np.cosh)
1308
1309    def tanh(self):
1310        return self._apply_func_to_corr(np.tanh)
1311
1312    def arcsin(self):
1313        return self._apply_func_to_corr(np.arcsin)
1314
1315    def arccos(self):
1316        return self._apply_func_to_corr(np.arccos)
1317
1318    def arctan(self):
1319        return self._apply_func_to_corr(np.arctan)
1320
1321    def arcsinh(self):
1322        return self._apply_func_to_corr(np.arcsinh)
1323
1324    def arccosh(self):
1325        return self._apply_func_to_corr(np.arccosh)
1326
1327    def arctanh(self):
1328        return self._apply_func_to_corr(np.arctanh)
1329
1330    # Right hand side operations (require tweak in main module to work)
1331    def __radd__(self, y):
1332        return self + y
1333
1334    def __rsub__(self, y):
1335        return -self + y
1336
1337    def __rmul__(self, y):
1338        return self * y
1339
1340    def __rtruediv__(self, y):
1341        return (self / y) ** (-1)
1342
1343    @property
1344    def real(self):
1345        def return_real(obs_OR_cobs):
1346            if isinstance(obs_OR_cobs.flatten()[0], CObs):
1347                return np.vectorize(lambda x: x.real)(obs_OR_cobs)
1348            else:
1349                return obs_OR_cobs
1350
1351        return self._apply_func_to_corr(return_real)
1352
1353    @property
1354    def imag(self):
1355        def return_imag(obs_OR_cobs):
1356            if isinstance(obs_OR_cobs.flatten()[0], CObs):
1357                return np.vectorize(lambda x: x.imag)(obs_OR_cobs)
1358            else:
1359                return obs_OR_cobs * 0  # So it stays the right type
1360
1361        return self._apply_func_to_corr(return_imag)
1362
1363    def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1364        r''' Project large correlation matrix to lowest states
1365
1366        This method can be used to reduce the size of an (N x N) correlation matrix
1367        to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise
1368        is still small.
1369
1370        Parameters
1371        ----------
1372        Ntrunc: int
1373            Rank of the target matrix.
1374        tproj: int
1375            Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method.
1376            The default value is 3.
1377        t0proj: int
1378            Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly
1379            discouraged for O(a) improved theories, since the correctness of the procedure
1380            cannot be granted in this case. The default value is 2.
1381        basematrix : Corr
1382            Correlation matrix that is used to determine the eigenvectors of the
1383            lowest states based on a GEVP. basematrix is taken to be the Corr itself if
1384            is is not specified.
1385
1386        Notes
1387        -----
1388        We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving
1389        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}$
1390        and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the
1391        resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via
1392        $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large
1393        correlation matrix and to remove some noise that is added by irrelevant operators.
1394        This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated
1395        bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.
1396        '''
1397
1398        if self.N == 1:
1399            raise ValueError('Method cannot be applied to one-dimensional correlators.')
1400        if basematrix is None:
1401            basematrix = self
1402        if Ntrunc >= basematrix.N:
1403            raise ValueError(f'Cannot truncate using Ntrunc >= {basematrix.N}')
1404        if basematrix.N != self.N:
1405            raise ValueError('basematrix and targetmatrix have to be of the same size.')
1406
1407        evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc]
1408
1409        tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object)
1410        rmat = []
1411        for t in range(basematrix.T):
1412            if self.content[t] is None:
1413                rmat.append(None)
1414            else:
1415                for i in range(Ntrunc):
1416                    for j in range(Ntrunc):
1417                        tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j]
1418                rmat.append(np.copy(tmpmat))
1419
1420        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) or self.content[t][0].value / self.content[t + 1][0].value < 0:
732                    newcontent.append(None)
733                else:
734                    newcontent.append(self.content[t] / self.content[t + 1])
735            if (all([x is None for x in newcontent])):
736                raise ValueError('m_eff is undefined at all timeslices')
737
738            return np.log(Corr(newcontent, padding=[0, 1]))
739
740        elif variant == 'logsym':
741            newcontent = []
742            for t in range(1, self.T - 1):
743                if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0) or self.content[t - 1][0].value / self.content[t + 1][0].value < 0:
744                    newcontent.append(None)
745                else:
746                    newcontent.append(self.content[t - 1] / self.content[t + 1])
747            if (all([x is None for x in newcontent])):
748                raise ValueError('m_eff is undefined at all timeslices')
749
750            return np.log(Corr(newcontent, padding=[1, 1])) / 2
751
752        elif variant in ['periodic', 'cosh', 'sinh']:
753            if variant in ['periodic', 'cosh']:
754                func = anp.cosh
755            else:
756                func = anp.sinh
757
758            def root_function(x, d):
759                return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d
760
761            newcontent = []
762            for t in range(self.T - 1):
763                if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0):
764                    newcontent.append(None)
765                # Fill the two timeslices in the middle of the lattice with their predecessors
766                elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]:
767                    newcontent.append(newcontent[-1])
768                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
769                    newcontent.append(None)
770                else:
771                    newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess)))
772            if (all([x is None for x in newcontent])):
773                raise ValueError('m_eff is undefined at all timeslices')
774
775            return Corr(newcontent, padding=[0, 1])
776
777        elif variant == 'arccosh':
778            newcontent = []
779            for t in range(1, self.T - 1):
780                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):
781                    newcontent.append(None)
782                else:
783                    newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t]))
784            if (all([x is None for x in newcontent])):
785                raise ValueError("m_eff is undefined at all timeslices")
786            return np.arccosh(Corr(newcontent, padding=[1, 1]))
787
788        else:
789            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):
791    def fit(self, function, fitrange=None, silent=False, **kwargs):
792        r'''Fits function to the data
793
794        Parameters
795        ----------
796        function : obj
797            function to fit to the data. See fits.least_squares for details.
798        fitrange : list
799            Two element list containing the timeslices on which the fit is supposed to start and stop.
800            Caution: This range is inclusive as opposed to standard python indexing.
801            `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6.
802            If not specified, self.prange or all timeslices are used.
803        silent : bool
804            Decides whether output is printed to the standard output.
805        '''
806        if self.N != 1:
807            raise ValueError("Correlator must be projected before fitting")
808
809        if fitrange is None:
810            if self.prange:
811                fitrange = self.prange
812            else:
813                fitrange = [0, self.T - 1]
814        else:
815            if not isinstance(fitrange, list):
816                raise TypeError("fitrange has to be a list with two elements")
817            if len(fitrange) != 2:
818                raise ValueError("fitrange has to have exactly two elements [fit_start, fit_stop]")
819
820        xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None])
821        ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None])
822        result = least_squares(xs, ys, function, silent=silent, **kwargs)
823        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):
825    def plateau(self, plateau_range=None, method="fit", auto_gamma=False):
826        """ Extract a plateau value from a Corr object
827
828        Parameters
829        ----------
830        plateau_range : list
831            list with two entries, indicating the first and the last timeslice
832            of the plateau region.
833        method : str
834            method to extract the plateau.
835                'fit' fits a constant to the plateau region
836                'avg', 'average' or 'mean' just average over the given timeslices.
837        auto_gamma : bool
838            apply gamma_method with default parameters to the Corr. Defaults to None
839        """
840        if not plateau_range:
841            if self.prange:
842                plateau_range = self.prange
843            else:
844                raise ValueError("no plateau range provided")
845        if self.N != 1:
846            raise ValueError("Correlator must be projected before getting a plateau.")
847        if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])):
848            raise ValueError("plateau is undefined at all timeslices in plateaurange.")
849        if auto_gamma:
850            self.gamma_method()
851        if method == "fit":
852            def const_func(a, t):
853                return a[0]
854            return self.fit(const_func, plateau_range)[0]
855        elif method in ["avg", "average", "mean"]:
856            returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None])
857            return returnvalue
858
859        else:
860            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):
862    def set_prange(self, prange):
863        """Sets the attribute prange of the Corr object."""
864        if not len(prange) == 2:
865            raise ValueError("prange must be a list or array with two values")
866        if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))):
867            raise TypeError("Start and end point must be integers")
868        if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] <= prange[1]):
869            raise ValueError("Start and end point must define a range in the interval 0,T")
870
871        self.prange = prange

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):
873    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):
874        """Plots the correlator using the tag of the correlator as label if available.
875
876        Parameters
877        ----------
878        x_range : list
879            list of two values, determining the range of the x-axis e.g. [4, 8].
880        comp : Corr or list of Corr
881            Correlator or list of correlators which are plotted for comparison.
882            The tags of these correlators are used as labels if available.
883        logscale : bool
884            Sets y-axis to logscale.
885        plateau : Obs
886            Plateau value to be visualized in the figure.
887        fit_res : Fit_result
888            Fit_result object to be visualized.
889        fit_key : str
890            Key for the fit function in Fit_result.fit_function (for combined fits).
891        ylabel : str
892            Label for the y-axis.
893        save : str
894            path to file in which the figure should be saved.
895        auto_gamma : bool
896            Apply the gamma method with standard parameters to all correlators and plateau values before plotting.
897        hide_sigma : float
898            Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors.
899        references : list
900            List of floating point values that are displayed as horizontal lines for reference.
901        title : string
902            Optional title of the figure.
903        """
904        if self.N != 1:
905            raise ValueError("Correlator must be projected before plotting")
906
907        if auto_gamma:
908            self.gamma_method()
909
910        if x_range is None:
911            x_range = [0, self.T - 1]
912
913        fig = plt.figure()
914        ax1 = fig.add_subplot(111)
915
916        x, y, y_err = self.plottable()
917        if hide_sigma:
918            hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1
919        else:
920            hide_from = None
921        ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag)
922        if logscale:
923            ax1.set_yscale('log')
924        else:
925            if y_range is None:
926                try:
927                    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)])
928                    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)])
929                    ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)])
930                except Exception:
931                    pass
932            else:
933                ax1.set_ylim(y_range)
934        if comp:
935            if isinstance(comp, (Corr, list)):
936                for corr in comp if isinstance(comp, list) else [comp]:
937                    if auto_gamma:
938                        corr.gamma_method()
939                    x, y, y_err = corr.plottable()
940                    if hide_sigma:
941                        hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1
942                    else:
943                        hide_from = None
944                    ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor'])
945            else:
946                raise TypeError("'comp' must be a correlator or a list of correlators.")
947
948        if plateau:
949            if isinstance(plateau, Obs):
950                if auto_gamma:
951                    plateau.gamma_method()
952                ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau))
953                ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-')
954            else:
955                raise TypeError("'plateau' must be an Obs")
956
957        if references:
958            if isinstance(references, list):
959                for ref in references:
960                    ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--')
961            else:
962                raise TypeError("'references' must be a list of floating pint values.")
963
964        if self.prange:
965            ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',', color="black", zorder=0)
966            ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',', color="black", zorder=0)
967
968        if fit_res:
969            x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05)
970            if isinstance(fit_res.fit_function, dict):
971                if fit_key:
972                    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)
973                else:
974                    raise ValueError("Please provide a 'fit_key' for visualizing combined fits.")
975            else:
976                ax1.plot(x_samples, fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2)
977
978        ax1.set_xlabel(r'$x_0 / a$')
979        if ylabel:
980            ax1.set_ylabel(ylabel)
981        ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5])
982
983        _handles, labels = ax1.get_legend_handles_labels()
984        if labels:
985            ax1.legend()
986
987        if title:
988            plt.title(title)
989
990        plt.draw()
991
992        if save:
993            if isinstance(save, str):
994                fig.savefig(save, bbox_inches='tight')
995            else:
996                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):
 998    def spaghetti_plot(self, logscale=True):
 999        """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations.
1000
1001        Parameters
1002        ----------
1003        logscale : bool
1004            Determines whether the scale of the y-axis is logarithmic or standard.
1005        """
1006        if self.N != 1:
1007            raise ValueError("Correlator needs to be projected first.")
1008
1009        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]))
1010        x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content, strict=True) if o is not None]
1011
1012        for name in mc_names:
1013            data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T
1014
1015            fig = plt.figure()
1016            ax = fig.add_subplot(111)
1017            for dat in data:
1018                ax.plot(x0_vals, dat, ls='-', marker='')
1019
1020            if logscale is True:
1021                ax.set_yscale('log')
1022
1023            ax.set_xlabel(r'$x_0 / a$')
1024            plt.title(name)
1025            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):
1027    def dump(self, filename, datatype="json.gz", **kwargs):
1028        """Dumps the Corr into a file of chosen type
1029        Parameters
1030        ----------
1031        filename : str
1032            Name of the file to be saved.
1033        datatype : str
1034            Format of the exported file. Supported formats include
1035            "json.gz" and "pickle"
1036        path : str
1037            specifies a custom path for the file (default '.')
1038        """
1039        if datatype == "json.gz":
1040            from .input.json import dump_to_json
1041            if 'path' in kwargs:
1042                file_name = kwargs.get('path') + '/' + filename
1043            else:
1044                file_name = filename
1045            dump_to_json(self, file_name)
1046        elif datatype == "pickle":
1047            dump_object(self, filename, **kwargs)
1048        else:
1049            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):
1051    def print(self, print_range=None):
1052        print(self.__repr__(print_range))
def sqrt(self):
1270    def sqrt(self):
1271        return self ** 0.5
def log(self):
1273    def log(self):
1274        newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content]
1275        return Corr(newcontent, prange=self.prange)
def exp(self):
1277    def exp(self):
1278        newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content]
1279        return Corr(newcontent, prange=self.prange)
def sin(self):
1294    def sin(self):
1295        return self._apply_func_to_corr(np.sin)
def cos(self):
1297    def cos(self):
1298        return self._apply_func_to_corr(np.cos)
def tan(self):
1300    def tan(self):
1301        return self._apply_func_to_corr(np.tan)
def sinh(self):
1303    def sinh(self):
1304        return self._apply_func_to_corr(np.sinh)
def cosh(self):
1306    def cosh(self):
1307        return self._apply_func_to_corr(np.cosh)
def tanh(self):
1309    def tanh(self):
1310        return self._apply_func_to_corr(np.tanh)
def arcsin(self):
1312    def arcsin(self):
1313        return self._apply_func_to_corr(np.arcsin)
def arccos(self):
1315    def arccos(self):
1316        return self._apply_func_to_corr(np.arccos)
def arctan(self):
1318    def arctan(self):
1319        return self._apply_func_to_corr(np.arctan)
def arcsinh(self):
1321    def arcsinh(self):
1322        return self._apply_func_to_corr(np.arcsinh)
def arccosh(self):
1324    def arccosh(self):
1325        return self._apply_func_to_corr(np.arccosh)
def arctanh(self):
1327    def arctanh(self):
1328        return self._apply_func_to_corr(np.arctanh)
real
1343    @property
1344    def real(self):
1345        def return_real(obs_OR_cobs):
1346            if isinstance(obs_OR_cobs.flatten()[0], CObs):
1347                return np.vectorize(lambda x: x.real)(obs_OR_cobs)
1348            else:
1349                return obs_OR_cobs
1350
1351        return self._apply_func_to_corr(return_real)
imag
1353    @property
1354    def imag(self):
1355        def return_imag(obs_OR_cobs):
1356            if isinstance(obs_OR_cobs.flatten()[0], CObs):
1357                return np.vectorize(lambda x: x.imag)(obs_OR_cobs)
1358            else:
1359                return obs_OR_cobs * 0  # So it stays the right type
1360
1361        return self._apply_func_to_corr(return_imag)
def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1363    def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
1364        r''' Project large correlation matrix to lowest states
1365
1366        This method can be used to reduce the size of an (N x N) correlation matrix
1367        to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise
1368        is still small.
1369
1370        Parameters
1371        ----------
1372        Ntrunc: int
1373            Rank of the target matrix.
1374        tproj: int
1375            Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method.
1376            The default value is 3.
1377        t0proj: int
1378            Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly
1379            discouraged for O(a) improved theories, since the correctness of the procedure
1380            cannot be granted in this case. The default value is 2.
1381        basematrix : Corr
1382            Correlation matrix that is used to determine the eigenvectors of the
1383            lowest states based on a GEVP. basematrix is taken to be the Corr itself if
1384            is is not specified.
1385
1386        Notes
1387        -----
1388        We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving
1389        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}$
1390        and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the
1391        resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via
1392        $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large
1393        correlation matrix and to remove some noise that is added by irrelevant operators.
1394        This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated
1395        bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.
1396        '''
1397
1398        if self.N == 1:
1399            raise ValueError('Method cannot be applied to one-dimensional correlators.')
1400        if basematrix is None:
1401            basematrix = self
1402        if Ntrunc >= basematrix.N:
1403            raise ValueError(f'Cannot truncate using Ntrunc >= {basematrix.N}')
1404        if basematrix.N != self.N:
1405            raise ValueError('basematrix and targetmatrix have to be of the same size.')
1406
1407        evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc]
1408
1409        tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object)
1410        rmat = []
1411        for t in range(basematrix.T):
1412            if self.content[t] is None:
1413                rmat.append(None)
1414            else:
1415                for i in range(Ntrunc):
1416                    for j in range(Ntrunc):
1417                        tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j]
1418                rmat.append(np.copy(tmpmat))
1419
1420        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