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

- Corr(data_input, padding=[0, 0], prange=None) + Corr(data_input, padding=None, prange=None)
-
 46    def __init__(self, data_input, padding=[0, 0], prange=None):
- 47        """ Initialize a Corr object.
- 48
- 49        Parameters
- 50        ----------
- 51        data_input : list or array
- 52            list of Obs or list of arrays of Obs or array of Corrs (see class docstring for details).
- 53        padding : list, optional
- 54            List with two entries where the first labels the padding
- 55            at the front of the correlator and the second the padding
- 56            at the back.
- 57        prange : list, optional
- 58            List containing the first and last timeslice of the plateau
- 59            region identified for this correlator.
- 60        """
- 61
- 62        if isinstance(data_input, np.ndarray):
- 63            if data_input.ndim == 1:
- 64                data_input = list(data_input)
- 65            elif data_input.ndim == 2:
- 66                if not data_input.shape[0] == data_input.shape[1]:
- 67                    raise ValueError("Array needs to be square.")
- 68                if not all([isinstance(item, Corr) for item in data_input.flatten()]):
- 69                    raise ValueError("If the input is an array, its elements must be of type pe.Corr.")
- 70                if not all([item.N == 1 for item in data_input.flatten()]):
- 71                    raise ValueError("Can only construct matrix correlator from single valued correlators.")
- 72                if not len(set([item.T for item in data_input.flatten()])) == 1:
- 73                    raise ValueError("All input Correlators must be defined over the same timeslices.")
- 74
- 75                T = data_input[0, 0].T
- 76                N = data_input.shape[0]
- 77                input_as_list = []
- 78                for t in range(T):
- 79                    if any([(item.content[t] is None) for item in data_input.flatten()]):
- 80                        if not all([(item.content[t] is None) for item in data_input.flatten()]):
- 81                            warnings.warn("Input ill-defined at different timeslices. Conversion leads to data loss.!", RuntimeWarning)
- 82                        input_as_list.append(None)
- 83                    else:
- 84                        array_at_timeslace = np.empty([N, N], dtype="object")
- 85                        for i in range(N):
- 86                            for j in range(N):
- 87                                array_at_timeslace[i, j] = data_input[i, j][t]
- 88                        input_as_list.append(array_at_timeslace)
- 89                data_input = input_as_list
- 90            elif data_input.ndim == 3:
- 91                if not data_input.shape[1] == data_input.shape[2]:
- 92                    raise ValueError("Array needs to be square.")
- 93                data_input = list(data_input)
- 94            else:
- 95                raise ValueError("Arrays with ndim>3 not supported.")
- 96
- 97        if isinstance(data_input, list):
- 98
- 99            if all([isinstance(item, (Obs, CObs)) or item is None for item in data_input]):
-100                _assert_equal_properties([o for o in data_input if o is not None])
-101                self.content = [np.asarray([item]) if item is not None else None for item in data_input]
-102                self.N = 1
-103            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]):
-104                self.content = data_input
-105                noNull = [a for a in self.content if a is not None]  # To check if the matrices are correct for all undefined elements
-106                self.N = noNull[0].shape[0]
-107                if self.N > 1 and noNull[0].shape[0] != noNull[0].shape[1]:
-108                    raise ValueError("Smearing matrices are not NxN.")
-109                if (not all([item.shape == noNull[0].shape for item in noNull])):
-110                    raise ValueError("Items in data_input are not of identical shape." + str(noNull))
-111            else:
-112                raise TypeError("'data_input' contains item of wrong type.")
-113        else:
-114            raise TypeError("Data input was not given as list or correct array.")
-115
-116        self.tag = None
-117
-118        # An undefined timeslice is represented by the None object
-119        self.content = [None] * padding[0] + self.content + [None] * padding[1]
-120        self.T = len(self.content)
-121        self.prange = prange
+            
 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
 
@@ -3373,15 +3389,15 @@ region identified for this correlator.
-
132    @property
-133    def reweighted(self):
-134        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]])
-135        if np.all(bool_array == 1):
-136            return True
-137        elif np.all(bool_array == 0):
-138            return False
-139        else:
-140            raise Exception("Reweighting status of correlator corrupted.")
+            
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.")
 
@@ -3399,16 +3415,16 @@ region identified for this correlator.
-
142    def gamma_method(self, **kwargs):
-143        """Apply the gamma method to the content of the Corr."""
-144        for item in self.content:
-145            if item is not None:
-146                if self.N == 1:
-147                    item[0].gamma_method(**kwargs)
-148                else:
-149                    for i in range(self.N):
-150                        for j in range(self.N):
-151                            item[i, j].gamma_method(**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)
 
@@ -3428,16 +3444,16 @@ region identified for this correlator.
-
142    def gamma_method(self, **kwargs):
-143        """Apply the gamma method to the content of the Corr."""
-144        for item in self.content:
-145            if item is not None:
-146                if self.N == 1:
-147                    item[0].gamma_method(**kwargs)
-148                else:
-149                    for i in range(self.N):
-150                        for j in range(self.N):
-151                            item[i, j].gamma_method(**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)
 
@@ -3457,44 +3473,44 @@ region identified for this correlator.
-
155    def projected(self, vector_l=None, vector_r=None, normalize=False):
-156        """We need to project the Correlator with a Vector to get a single value at each timeslice.
-157
-158        The method can use one or two vectors.
-159        If two are specified it returns v1@G@v2 (the order might be very important.)
-160        By default it will return the lowest source, which usually means unsmeared-unsmeared (0,0), but it does not have to
-161        """
-162        if self.N == 1:
-163            raise ValueError("Trying to project a Corr, that already has N=1.")
-164
-165        if vector_l is None:
-166            vector_l, vector_r = np.asarray([1.] + (self.N - 1) * [0.]), np.asarray([1.] + (self.N - 1) * [0.])
-167        elif (vector_r is None):
-168            vector_r = vector_l
-169        if isinstance(vector_l, list) and not isinstance(vector_r, list):
-170            if len(vector_l) != self.T:
-171                raise ValueError("Length of vector list must be equal to T")
-172            vector_r = [vector_r] * self.T
-173        if isinstance(vector_r, list) and not isinstance(vector_l, list):
-174            if len(vector_r) != self.T:
-175                raise ValueError("Length of vector list must be equal to T")
-176            vector_l = [vector_l] * self.T
-177
-178        if not isinstance(vector_l, list):
-179            if not vector_l.shape == vector_r.shape == (self.N,):
-180                raise ValueError("Vectors are of wrong shape!")
-181            if normalize:
-182                vector_l, vector_r = vector_l / np.sqrt((vector_l @ vector_l)), vector_r / np.sqrt(vector_r @ vector_r)
-183            newcontent = [None if _check_for_none(self, item) else np.asarray([vector_l.T @ item @ vector_r]) for item in self.content]
-184
-185        else:
-186            # There are no checks here yet. There are so many possible scenarios, where this can go wrong.
+            
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                for t in range(self.T):
-189                    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])
+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            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)]
-192        return Corr(newcontent)
+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)
 
@@ -3518,20 +3534,20 @@ By default it will return the lowest source, which usually means unsmeared-unsme
-
194    def item(self, i, j):
-195        """Picks the element [i,j] from every matrix and returns a correlator containing one Obs per timeslice.
-196
-197        Parameters
-198        ----------
-199        i : int
-200            First index to be picked.
-201        j : int
-202            Second index to be picked.
-203        """
-204        if self.N == 1:
-205            raise ValueError("Trying to pick item from projected Corr")
-206        newcontent = [None if (item is None) else item[i, j] for item in self.content]
-207        return Corr(newcontent)
+            
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)
 
@@ -3560,19 +3576,19 @@ Second index to be picked.
-
209    def plottable(self):
-210        """Outputs the correlator in a plotable format.
-211
-212        Outputs three lists containing the timeslice index, the value on each
-213        timeslice and the error on each timeslice.
-214        """
-215        if self.N != 1:
-216            raise ValueError("Can only make Corr[N=1] plottable")
-217        x_list = [x for x in range(self.T) if self.content[x] is not None]
-218        y_list = [y[0].value for y in self.content if y is not None]
-219        y_err_list = [y[0].dvalue for y in self.content if y is not None]
-220
-221        return x_list, y_list, y_err_list
+            
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
 
@@ -3595,26 +3611,26 @@ timeslice and the error on each timeslice.

-
223    def symmetric(self):
-224        """ Symmetrize the correlator around x0=0."""
-225        if self.N != 1:
-226            raise ValueError('symmetric cannot be safely applied to multi-dimensional correlators.')
-227        if self.T % 2 != 0:
-228            raise ValueError("Can not symmetrize odd T")
-229
-230        if self.content[0] is not None:
-231            if np.argmax(np.abs([o[0].value if o is not None else 0 for o in self.content])) != 0:
-232                warnings.warn("Correlator does not seem to be symmetric around x0=0.", RuntimeWarning)
-233
-234        newcontent = [self.content[0]]
-235        for t in range(1, self.T):
-236            if (self.content[t] is None) or (self.content[self.T - t] is None):
-237                newcontent.append(None)
-238            else:
-239                newcontent.append(0.5 * (self.content[t] + self.content[self.T - t]))
-240        if (all([x is None for x in newcontent])):
-241            raise ValueError("Corr could not be symmetrized: No redundant values")
-242        return Corr(newcontent, prange=self.prange)
+            
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)
 
@@ -3634,27 +3650,27 @@ timeslice and the error on each timeslice.

-
244    def anti_symmetric(self):
-245        """Anti-symmetrize the correlator around x0=0."""
-246        if self.N != 1:
-247            raise TypeError('anti_symmetric cannot be safely applied to multi-dimensional correlators.')
-248        if self.T % 2 != 0:
-249            raise ValueError("Can not symmetrize odd T")
-250
-251        test = 1 * self
-252        test.gamma_method()
-253        if not all([o.is_zero_within_error(3) for o in test.content[0]]):
-254            warnings.warn("Correlator does not seem to be anti-symmetric around x0=0.", RuntimeWarning)
-255
-256        newcontent = [self.content[0]]
-257        for t in range(1, self.T):
-258            if (self.content[t] is None) or (self.content[self.T - t] is None):
-259                newcontent.append(None)
-260            else:
-261                newcontent.append(0.5 * (self.content[t] - self.content[self.T - t]))
-262        if (all([x is None for x in newcontent])):
-263            raise ValueError("Corr could not be symmetrized: No redundant values")
-264        return Corr(newcontent, prange=self.prange)
+            
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)
 
@@ -3674,20 +3690,20 @@ timeslice and the error on each timeslice.

-
266    def is_matrix_symmetric(self):
-267        """Checks whether a correlator matrices is symmetric on every timeslice."""
-268        if self.N == 1:
-269            raise TypeError("Only works for correlator matrices.")
-270        for t in range(self.T):
-271            if self[t] is None:
-272                continue
-273            for i in range(self.N):
-274                for j in range(i + 1, self.N):
-275                    if self[t][i, j] is self[t][j, i]:
-276                        continue
-277                    if hash(self[t][i, j]) != hash(self[t][j, i]):
-278                        return False
-279        return True
+            
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
 
@@ -3707,17 +3723,17 @@ timeslice and the error on each timeslice.

-
281    def trace(self):
-282        """Calculates the per-timeslice trace of a correlator matrix."""
-283        if self.N == 1:
-284            raise ValueError("Only works for correlator matrices.")
-285        newcontent = []
-286        for t in range(self.T):
-287            if _check_for_none(self, self.content[t]):
-288                newcontent.append(None)
-289            else:
-290                newcontent.append(np.trace(self.content[t]))
-291        return Corr(newcontent)
+            
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)
 
@@ -3737,15 +3753,15 @@ timeslice and the error on each timeslice.

-
293    def matrix_symmetric(self):
-294        """Symmetrizes the correlator matrices on every timeslice."""
-295        if self.N == 1:
-296            raise ValueError("Trying to symmetrize a correlator matrix, that already has N=1.")
-297        if self.is_matrix_symmetric():
-298            return 1.0 * self
-299        else:
-300            transposed = [None if _check_for_none(self, G) else G.T for G in self.content]
-301            return 0.5 * (Corr(transposed) + 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)
 
@@ -3765,111 +3781,111 @@ timeslice and the error on each timeslice.

-
303    def GEVP(self, t0, ts=None, sort="Eigenvalue", vector_obs=False, **kwargs):
-304        r'''Solve the generalized eigenvalue problem on the correlator matrix and returns the corresponding eigenvectors.
-305
-306        The eigenvectors are sorted according to the descending eigenvalues, the zeroth eigenvector(s) correspond to the
-307        largest eigenvalue(s). The eigenvector(s) for the individual states can be accessed via slicing
-308        ```python
-309        C.GEVP(t0=2)[0]  # Ground state vector(s)
-310        C.GEVP(t0=2)[:3]  # Vectors for the lowest three states
-311        ```
-312
-313        Parameters
-314        ----------
-315        t0 : int
-316            The time t0 for the right hand side of the GEVP according to $G(t)v_i=\lambda_i G(t_0)v_i$
-317        ts : int
-318            fixed time $G(t_s)v_i=\lambda_i G(t_0)v_i$ if sort=None.
-319            If sort="Eigenvector" it gives a reference point for the sorting method.
-320        sort : string
-321            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.
-322            - "Eigenvalue": The eigenvector is chosen according to which eigenvalue it belongs individually on every timeslice. (default)
-323            - "Eigenvector": Use the method described in arXiv:2004.10472 to find the set of v(t) belonging to the state.
-324              The reference state is identified by its eigenvalue at $t=t_s$.
-325            - None: The GEVP is solved only at ts, no sorting is necessary
-326        vector_obs : bool
-327            If True, uncertainties are propagated in the eigenvector computation (default False).
-328
-329        Other Parameters
-330        ----------------
-331        state : int
-332           Returns only the vector(s) for a specified state. The lowest state is zero.
-333        method : str
-334           Method used to solve the GEVP.
-335           - "eigh": Use scipy.linalg.eigh to solve the GEVP. (default for vector_obs=False)
-336           - "cholesky": Use manually implemented solution via the Cholesky decomposition. Automatically chosen if vector_obs==True.
-337        '''
-338
-339        if self.N == 1:
-340            raise ValueError("GEVP methods only works on correlator matrices and not single correlators.")
-341        if ts is not None:
-342            if (ts <= t0):
-343                raise ValueError("ts has to be larger than t0.")
+            
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 "sorted_list" in kwargs:
-346            warnings.warn("Argument 'sorted_list' is deprecated, use 'sort' instead.", DeprecationWarning)
-347            sort = kwargs.get("sorted_list")
-348
-349        if self.is_matrix_symmetric():
-350            symmetric_corr = self
-351        else:
-352            symmetric_corr = self.matrix_symmetric()
-353
-354        def _get_mat_at_t(t, vector_obs=vector_obs):
-355            if vector_obs:
-356                return symmetric_corr[t]
-357            else:
-358                return np.vectorize(lambda x: x.value)(symmetric_corr[t])
-359        G0 = _get_mat_at_t(t0)
-360
-361        method = kwargs.get('method', 'eigh')
-362        if vector_obs:
-363            chol = linalg.cholesky(G0)
-364            chol_inv = linalg.inv(chol)
-365            method = 'cholesky'
-366        else:
-367            chol = np.linalg.cholesky(_get_mat_at_t(t0, vector_obs=False))  # Check if matrix G0 is positive-semidefinite.
-368            if method == 'cholesky':
-369                chol_inv = np.linalg.inv(chol)
-370            else:
-371                chol_inv = None
-372
-373        if sort is None:
-374            if (ts is None):
-375                raise ValueError("ts is required if sort=None.")
-376            if (self.content[t0] is None) or (self.content[ts] is None):
-377                raise ValueError("Corr not defined at t0/ts.")
-378            Gt = _get_mat_at_t(ts)
-379            reordered_vecs = _GEVP_solver(Gt, G0, method=method, chol_inv=chol_inv)
-380            if kwargs.get('auto_gamma', False) and vector_obs:
-381                [[o.gm() for o in ev if isinstance(o, Obs)] for ev in reordered_vecs]
-382
-383        elif sort in ["Eigenvalue", "Eigenvector"]:
-384            if sort == "Eigenvalue" and ts is not None:
-385                warnings.warn("ts has no effect when sorting by eigenvalue is chosen.", RuntimeWarning)
-386            all_vecs = [None] * (t0 + 1)
-387            for t in range(t0 + 1, self.T):
-388                try:
-389                    Gt = _get_mat_at_t(t)
-390                    all_vecs.append(_GEVP_solver(Gt, G0, method=method, chol_inv=chol_inv))
-391                except Exception:
-392                    all_vecs.append(None)
-393            if sort == "Eigenvector":
-394                if ts is None:
-395                    raise ValueError("ts is required for the Eigenvector sorting method.")
-396                all_vecs = _sort_vectors(all_vecs, ts)
-397
-398            reordered_vecs = [[v[s] if v is not None else None for v in all_vecs] for s in range(self.N)]
-399            if kwargs.get('auto_gamma', False) and vector_obs:
-400                [[[o.gm() for o in evn] for evn in ev if evn is not None] for ev in reordered_vecs]
-401        else:
-402            raise ValueError("Unknown value for 'sort'. Choose 'Eigenvalue', 'Eigenvector' or None.")
+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        if "state" in kwargs:
-405            return reordered_vecs[kwargs.get("state")]
-406        else:
-407            return reordered_vecs
+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
 
@@ -3931,18 +3947,18 @@ Method used to solve the GEVP.
-
409    def Eigenvalue(self, t0, ts=None, state=0, sort="Eigenvalue", **kwargs):
-410        """Determines the eigenvalue of the GEVP by solving and projecting the correlator
-411
-412        Parameters
-413        ----------
-414        state : int
-415            The state one is interested in ordered by energy. The lowest state is zero.
-416
-417        All other parameters are identical to the ones of Corr.GEVP.
-418        """
-419        vec = self.GEVP(t0, ts=ts, sort=sort, **kwargs)[state]
-420        return self.projected(vec)
+            
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)
 
@@ -3970,46 +3986,46 @@ The state one is interested in ordered by energy. The lowest state is zero.
-
422    def Hankel(self, N, periodic=False):
-423        """Constructs an NxN Hankel matrix
-424
-425        C(t) c(t+1) ... c(t+n-1)
-426        C(t+1) c(t+2) ... c(t+n)
-427        .................
-428        C(t+(n-1)) c(t+n) ... c(t+2(n-1))
-429
-430        Parameters
-431        ----------
-432        N : int
-433            Dimension of the Hankel matrix
-434        periodic : bool, optional
-435            determines whether the matrix is extended periodically
-436        """
-437
-438        if self.N != 1:
-439            raise NotImplementedError("Multi-operator Prony not implemented!")
-440
-441        array = np.empty([N, N], dtype="object")
-442        new_content = []
-443        for t in range(self.T):
-444            new_content.append(array.copy())
-445
-446        def wrap(i):
-447            while i >= self.T:
-448                i -= self.T
-449            return i
-450
-451        for t in range(self.T):
-452            for i in range(N):
-453                for j in range(N):
-454                    if periodic:
-455                        new_content[t][i, j] = self.content[wrap(t + i + j)][0]
-456                    elif (t + i + j) >= self.T:
-457                        new_content[t] = None
-458                    else:
-459                        new_content[t][i, j] = self.content[t + i + j][0]
-460
-461        return Corr(new_content)
+            
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)
 
@@ -4043,15 +4059,15 @@ determines whether the matrix is extended periodically
-
463    def roll(self, dt):
-464        """Periodically shift the correlator by dt timeslices
-465
-466        Parameters
-467        ----------
-468        dt : int
-469            number of timeslices
-470        """
-471        return Corr(list(np.roll(np.array(self.content, dtype=object), dt, axis=0)))
+            
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)))
 
@@ -4078,9 +4094,9 @@ number of timeslices
-
473    def reverse(self):
-474        """Reverse the time ordering of the Corr"""
-475        return Corr(self.content[:: -1])
+            
479    def reverse(self):
+480        """Reverse the time ordering of the Corr"""
+481        return Corr(self.content[:: -1])
 
@@ -4100,23 +4116,23 @@ number of timeslices
-
477    def thin(self, spacing=2, offset=0):
-478        """Thin out a correlator to suppress correlations
-479
-480        Parameters
-481        ----------
-482        spacing : int
-483            Keep only every 'spacing'th entry of the correlator
-484        offset : int
-485            Offset the equal spacing
-486        """
-487        new_content = []
-488        for t in range(self.T):
-489            if (offset + t) % spacing != 0:
-490                new_content.append(None)
-491            else:
-492                new_content.append(self.content[t])
-493        return Corr(new_content)
+            
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)
 
@@ -4145,34 +4161,34 @@ Offset the equal spacing
-
495    def correlate(self, partner):
-496        """Correlate the correlator with another correlator or Obs
-497
-498        Parameters
-499        ----------
-500        partner : Obs or Corr
-501            partner to correlate the correlator with.
-502            Can either be an Obs which is correlated with all entries of the
-503            correlator or a Corr of same length.
-504        """
-505        if self.N != 1:
-506            raise ValueError("Only one-dimensional correlators can be safely correlated.")
-507        new_content = []
-508        for x0, t_slice in enumerate(self.content):
-509            if _check_for_none(self, t_slice):
-510                new_content.append(None)
-511            else:
-512                if isinstance(partner, Corr):
-513                    if _check_for_none(partner, partner.content[x0]):
-514                        new_content.append(None)
-515                    else:
-516                        new_content.append(np.array([correlate(o, partner.content[x0][0]) for o in t_slice]))
-517                elif isinstance(partner, Obs):  # Should this include CObs?
-518                    new_content.append(np.array([correlate(o, partner) for o in t_slice]))
-519                else:
-520                    raise TypeError("Can only correlate with an Obs or a Corr.")
-521
-522        return Corr(new_content)
+            
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)
 
@@ -4201,28 +4217,28 @@ correlator or a Corr of same length.
-
524    def reweight(self, weight, **kwargs):
-525        """Reweight the correlator.
-526
-527        Parameters
-528        ----------
-529        weight : Obs
-530            Reweighting factor. An Observable that has to be defined on a superset of the
-531            configurations in obs[i].idl for all i.
-532        all_configs : bool
-533            if True, the reweighted observables are normalized by the average of
-534            the reweighting factor on all configurations in weight.idl and not
-535            on the configurations in obs[i].idl.
-536        """
-537        if self.N != 1:
-538            raise Exception("Reweighting only implemented for one-dimensional correlators.")
-539        new_content = []
-540        for t_slice in self.content:
-541            if _check_for_none(self, t_slice):
-542                new_content.append(None)
-543            else:
-544                new_content.append(np.array(reweight(weight, t_slice, **kwargs)))
-545        return Corr(new_content)
+            
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 Exception("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)
 
@@ -4254,35 +4270,35 @@ on the configurations in obs[i].idl.
-
547    def T_symmetry(self, partner, parity=+1):
-548        """Return the time symmetry average of the correlator and its partner
-549
-550        Parameters
-551        ----------
-552        partner : Corr
-553            Time symmetry partner of the Corr
-554        parity : int
-555            Parity quantum number of the correlator, can be +1 or -1
-556        """
-557        if self.N != 1:
-558            raise Exception("T_symmetry only implemented for one-dimensional correlators.")
-559        if not isinstance(partner, Corr):
-560            raise Exception("T partner has to be a Corr object.")
-561        if parity not in [+1, -1]:
-562            raise Exception("Parity has to be +1 or -1.")
-563        T_partner = parity * partner.reverse()
-564
-565        t_slices = []
-566        test = (self - T_partner)
-567        test.gamma_method()
-568        for x0, t_slice in enumerate(test.content):
-569            if t_slice is not None:
-570                if not t_slice[0].is_zero_within_error(5):
-571                    t_slices.append(x0)
-572        if t_slices:
-573            warnings.warn("T symmetry partners do not agree within 5 sigma on time slices " + str(t_slices) + ".", RuntimeWarning)
-574
-575        return (self + T_partner) / 2
+            
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 Exception("T_symmetry only implemented for one-dimensional correlators.")
+565        if not isinstance(partner, Corr):
+566            raise Exception("T partner has to be a Corr object.")
+567        if parity not in [+1, -1]:
+568            raise Exception("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
 
@@ -4311,70 +4327,70 @@ Parity quantum number of the correlator, can be +1 or -1
-
577    def deriv(self, variant="symmetric"):
-578        """Return the first derivative of the correlator with respect to x0.
-579
-580        Parameters
-581        ----------
-582        variant : str
-583            decides which definition of the finite differences derivative is used.
-584            Available choice: symmetric, forward, backward, improved, log, default: symmetric
-585        """
-586        if self.N != 1:
-587            raise ValueError("deriv only implemented for one-dimensional correlators.")
-588        if variant == "symmetric":
-589            newcontent = []
-590            for t in range(1, self.T - 1):
-591                if (self.content[t - 1] is None) or (self.content[t + 1] is None):
-592                    newcontent.append(None)
-593                else:
-594                    newcontent.append(0.5 * (self.content[t + 1] - self.content[t - 1]))
-595            if (all([x is None for x in newcontent])):
-596                raise ValueError('Derivative is undefined at all timeslices')
-597            return Corr(newcontent, padding=[1, 1])
-598        elif variant == "forward":
-599            newcontent = []
-600            for t in range(self.T - 1):
-601                if (self.content[t] is None) or (self.content[t + 1] is None):
-602                    newcontent.append(None)
-603                else:
-604                    newcontent.append(self.content[t + 1] - self.content[t])
-605            if (all([x is None for x in newcontent])):
-606                raise ValueError("Derivative is undefined at all timeslices")
-607            return Corr(newcontent, padding=[0, 1])
-608        elif variant == "backward":
-609            newcontent = []
-610            for t in range(1, self.T):
-611                if (self.content[t - 1] is None) or (self.content[t] is None):
-612                    newcontent.append(None)
-613                else:
-614                    newcontent.append(self.content[t] - self.content[t - 1])
-615            if (all([x is None for x in newcontent])):
-616                raise ValueError("Derivative is undefined at all timeslices")
-617            return Corr(newcontent, padding=[1, 0])
-618        elif variant == "improved":
-619            newcontent = []
-620            for t in range(2, self.T - 2):
-621                if (self.content[t - 2] is None) or (self.content[t - 1] is None) or (self.content[t + 1] is None) or (self.content[t + 2] is None):
-622                    newcontent.append(None)
-623                else:
-624                    newcontent.append((1 / 12) * (self.content[t - 2] - 8 * self.content[t - 1] + 8 * self.content[t + 1] - self.content[t + 2]))
-625            if (all([x is None for x in newcontent])):
-626                raise ValueError('Derivative is undefined at all timeslices')
-627            return Corr(newcontent, padding=[2, 2])
-628        elif variant == 'log':
-629            newcontent = []
-630            for t in range(self.T):
-631                if (self.content[t] is None) or (self.content[t] <= 0):
-632                    newcontent.append(None)
-633                else:
-634                    newcontent.append(np.log(self.content[t]))
-635            if (all([x is None for x in newcontent])):
-636                raise ValueError("Log is undefined at all timeslices")
-637            logcorr = Corr(newcontent)
-638            return self * logcorr.deriv('symmetric')
-639        else:
-640            raise ValueError("Unknown variant.")
+            
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.")
 
@@ -4402,68 +4418,68 @@ Available choice: symmetric, forward, backward, improved, log, default: symmetri
-
642    def second_deriv(self, variant="symmetric"):
-643        r"""Return the second derivative of the correlator with respect to x0.
-644
-645        Parameters
-646        ----------
-647        variant : str
-648            decides which definition of the finite differences derivative is used.
-649            Available choice:
-650                - symmetric (default)
-651                    $$\tilde{\partial}^2_0 f(x_0) = f(x_0+1)-2f(x_0)+f(x_0-1)$$
-652                - big_symmetric
-653                    $$\partial^2_0 f(x_0) = \frac{f(x_0+2)-2f(x_0)+f(x_0-2)}{4}$$
-654                - improved
-655                    $$\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}$$
-656                - log
-657                    $$f(x) = \tilde{\partial}^2_0 log(f(x_0))+(\tilde{\partial}_0 log(f(x_0)))^2$$
-658        """
-659        if self.N != 1:
-660            raise ValueError("second_deriv only implemented for one-dimensional correlators.")
-661        if variant == "symmetric":
-662            newcontent = []
-663            for t in range(1, self.T - 1):
-664                if (self.content[t - 1] is None) or (self.content[t + 1] is None):
-665                    newcontent.append(None)
-666                else:
-667                    newcontent.append((self.content[t + 1] - 2 * self.content[t] + self.content[t - 1]))
-668            if (all([x is None for x in newcontent])):
-669                raise ValueError("Derivative is undefined at all timeslices")
-670            return Corr(newcontent, padding=[1, 1])
-671        elif variant == "big_symmetric":
-672            newcontent = []
-673            for t in range(2, self.T - 2):
-674                if (self.content[t - 2] is None) or (self.content[t + 2] is None):
-675                    newcontent.append(None)
-676                else:
-677                    newcontent.append((self.content[t + 2] - 2 * self.content[t] + self.content[t - 2]) / 4)
-678            if (all([x is None for x in newcontent])):
-679                raise ValueError("Derivative is undefined at all timeslices")
-680            return Corr(newcontent, padding=[2, 2])
-681        elif variant == "improved":
-682            newcontent = []
-683            for t in range(2, self.T - 2):
-684                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):
-685                    newcontent.append(None)
-686                else:
-687                    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]))
-688            if (all([x is None for x in newcontent])):
-689                raise ValueError("Derivative is undefined at all timeslices")
-690            return Corr(newcontent, padding=[2, 2])
-691        elif variant == 'log':
-692            newcontent = []
-693            for t in range(self.T):
-694                if (self.content[t] is None) or (self.content[t] <= 0):
-695                    newcontent.append(None)
-696                else:
-697                    newcontent.append(np.log(self.content[t]))
-698            if (all([x is None for x in newcontent])):
-699                raise ValueError("Log is undefined at all timeslices")
-700            logcorr = Corr(newcontent)
-701            return self * (logcorr.second_deriv('symmetric') + (logcorr.deriv('symmetric'))**2)
-702        else:
-703            raise ValueError("Unknown variant.")
+            
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.")
 
@@ -4499,89 +4515,89 @@ Available choice:
-
705    def m_eff(self, variant='log', guess=1.0):
-706        """Returns the effective mass of the correlator as correlator object
-707
-708        Parameters
-709        ----------
-710        variant : str
-711            log : uses the standard effective mass log(C(t) / C(t+1))
-712            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.
-713            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.
-714            See, e.g., arXiv:1205.5380
-715            arccosh : Uses the explicit form of the symmetrized correlator (not recommended)
-716            logsym: uses the symmetric effective mass log(C(t-1) / C(t+1))/2
-717        guess : float
-718            guess for the root finder, only relevant for the root variant
-719        """
-720        if self.N != 1:
-721            raise Exception('Correlator must be projected before getting m_eff')
-722        if variant == 'log':
-723            newcontent = []
-724            for t in range(self.T - 1):
-725                if ((self.content[t] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0):
-726                    newcontent.append(None)
-727                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
-728                    newcontent.append(None)
-729                else:
-730                    newcontent.append(self.content[t] / self.content[t + 1])
-731            if (all([x is None for x in newcontent])):
-732                raise ValueError('m_eff is undefined at all timeslices')
-733
-734            return np.log(Corr(newcontent, padding=[0, 1]))
-735
-736        elif variant == 'logsym':
-737            newcontent = []
-738            for t in range(1, self.T - 1):
-739                if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0):
-740                    newcontent.append(None)
-741                elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0:
-742                    newcontent.append(None)
-743                else:
-744                    newcontent.append(self.content[t - 1] / self.content[t + 1])
-745            if (all([x is None for x in newcontent])):
-746                raise ValueError('m_eff is undefined at all timeslices')
-747
-748            return np.log(Corr(newcontent, padding=[1, 1])) / 2
-749
-750        elif variant in ['periodic', 'cosh', 'sinh']:
-751            if variant in ['periodic', 'cosh']:
-752                func = anp.cosh
-753            else:
-754                func = anp.sinh
+            
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 Exception('Correlator must be projected before getting m_eff')
+728        if variant == 'log':
+729            newcontent = []
+730            for t in range(self.T - 1):
+731                if ((self.content[t] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0):
+732                    newcontent.append(None)
+733                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
+734                    newcontent.append(None)
+735                else:
+736                    newcontent.append(self.content[t] / self.content[t + 1])
+737            if (all([x is None for x in newcontent])):
+738                raise ValueError('m_eff is undefined at all timeslices')
+739
+740            return np.log(Corr(newcontent, padding=[0, 1]))
+741
+742        elif variant == 'logsym':
+743            newcontent = []
+744            for t in range(1, self.T - 1):
+745                if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0):
+746                    newcontent.append(None)
+747                elif self.content[t - 1][0].value / self.content[t + 1][0].value < 0:
+748                    newcontent.append(None)
+749                else:
+750                    newcontent.append(self.content[t - 1] / self.content[t + 1])
+751            if (all([x is None for x in newcontent])):
+752                raise ValueError('m_eff is undefined at all timeslices')
+753
+754            return np.log(Corr(newcontent, padding=[1, 1])) / 2
 755
-756            def root_function(x, d):
-757                return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d
-758
-759            newcontent = []
-760            for t in range(self.T - 1):
-761                if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0):
-762                    newcontent.append(None)
-763                # Fill the two timeslices in the middle of the lattice with their predecessors
-764                elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]:
-765                    newcontent.append(newcontent[-1])
-766                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
-767                    newcontent.append(None)
-768                else:
-769                    newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess)))
-770            if (all([x is None for x in newcontent])):
-771                raise ValueError('m_eff is undefined at all timeslices')
-772
-773            return Corr(newcontent, padding=[0, 1])
-774
-775        elif variant == 'arccosh':
-776            newcontent = []
-777            for t in range(1, self.T - 1):
-778                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):
-779                    newcontent.append(None)
-780                else:
-781                    newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t]))
-782            if (all([x is None for x in newcontent])):
-783                raise ValueError("m_eff is undefined at all timeslices")
-784            return np.arccosh(Corr(newcontent, padding=[1, 1]))
-785
-786        else:
-787            raise ValueError('Unknown variant.')
+756        elif variant in ['periodic', 'cosh', 'sinh']:
+757            if variant in ['periodic', 'cosh']:
+758                func = anp.cosh
+759            else:
+760                func = anp.sinh
+761
+762            def root_function(x, d):
+763                return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d
+764
+765            newcontent = []
+766            for t in range(self.T - 1):
+767                if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0):
+768                    newcontent.append(None)
+769                # Fill the two timeslices in the middle of the lattice with their predecessors
+770                elif variant == 'sinh' and t in [self.T / 2, self.T / 2 - 1]:
+771                    newcontent.append(newcontent[-1])
+772                elif self.content[t][0].value / self.content[t + 1][0].value < 0:
+773                    newcontent.append(None)
+774                else:
+775                    newcontent.append(np.abs(find_root(self.content[t][0] / self.content[t + 1][0], root_function, guess=guess)))
+776            if (all([x is None for x in newcontent])):
+777                raise ValueError('m_eff is undefined at all timeslices')
+778
+779            return Corr(newcontent, padding=[0, 1])
+780
+781        elif variant == 'arccosh':
+782            newcontent = []
+783            for t in range(1, self.T - 1):
+784                if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t - 1] is None) or (self.content[t][0].value == 0):
+785                    newcontent.append(None)
+786                else:
+787                    newcontent.append((self.content[t + 1] + self.content[t - 1]) / (2 * self.content[t]))
+788            if (all([x is None for x in newcontent])):
+789                raise ValueError("m_eff is undefined at all timeslices")
+790            return np.arccosh(Corr(newcontent, padding=[1, 1]))
+791
+792        else:
+793            raise ValueError('Unknown variant.')
 
@@ -4615,39 +4631,39 @@ guess for the root finder, only relevant for the root variant
-
789    def fit(self, function, fitrange=None, silent=False, **kwargs):
-790        r'''Fits function to the data
-791
-792        Parameters
-793        ----------
-794        function : obj
-795            function to fit to the data. See fits.least_squares for details.
-796        fitrange : list
-797            Two element list containing the timeslices on which the fit is supposed to start and stop.
-798            Caution: This range is inclusive as opposed to standard python indexing.
-799            `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6.
-800            If not specified, self.prange or all timeslices are used.
-801        silent : bool
-802            Decides whether output is printed to the standard output.
-803        '''
-804        if self.N != 1:
-805            raise ValueError("Correlator must be projected before fitting")
-806
-807        if fitrange is None:
-808            if self.prange:
-809                fitrange = self.prange
-810            else:
-811                fitrange = [0, self.T - 1]
-812        else:
-813            if not isinstance(fitrange, list):
-814                raise TypeError("fitrange has to be a list with two elements")
-815            if len(fitrange) != 2:
-816                raise ValueError("fitrange has to have exactly two elements [fit_start, fit_stop]")
-817
-818        xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None])
-819        ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None])
-820        result = least_squares(xs, ys, function, silent=silent, **kwargs)
-821        return result
+            
795    def fit(self, function, fitrange=None, silent=False, **kwargs):
+796        r'''Fits function to the data
+797
+798        Parameters
+799        ----------
+800        function : obj
+801            function to fit to the data. See fits.least_squares for details.
+802        fitrange : list
+803            Two element list containing the timeslices on which the fit is supposed to start and stop.
+804            Caution: This range is inclusive as opposed to standard python indexing.
+805            `fitrange=[4, 6]` corresponds to the three entries 4, 5 and 6.
+806            If not specified, self.prange or all timeslices are used.
+807        silent : bool
+808            Decides whether output is printed to the standard output.
+809        '''
+810        if self.N != 1:
+811            raise ValueError("Correlator must be projected before fitting")
+812
+813        if fitrange is None:
+814            if self.prange:
+815                fitrange = self.prange
+816            else:
+817                fitrange = [0, self.T - 1]
+818        else:
+819            if not isinstance(fitrange, list):
+820                raise TypeError("fitrange has to be a list with two elements")
+821            if len(fitrange) != 2:
+822                raise ValueError("fitrange has to have exactly two elements [fit_start, fit_stop]")
+823
+824        xs = np.array([x for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None])
+825        ys = np.array([self.content[x][0] for x in range(fitrange[0], fitrange[1] + 1) if self.content[x] is not None])
+826        result = least_squares(xs, ys, function, silent=silent, **kwargs)
+827        return result
 
@@ -4681,42 +4697,42 @@ Decides whether output is printed to the standard output.
-
823    def plateau(self, plateau_range=None, method="fit", auto_gamma=False):
-824        """ Extract a plateau value from a Corr object
-825
-826        Parameters
-827        ----------
-828        plateau_range : list
-829            list with two entries, indicating the first and the last timeslice
-830            of the plateau region.
-831        method : str
-832            method to extract the plateau.
-833                'fit' fits a constant to the plateau region
-834                'avg', 'average' or 'mean' just average over the given timeslices.
-835        auto_gamma : bool
-836            apply gamma_method with default parameters to the Corr. Defaults to None
-837        """
-838        if not plateau_range:
-839            if self.prange:
-840                plateau_range = self.prange
-841            else:
-842                raise Exception("no plateau range provided")
-843        if self.N != 1:
-844            raise ValueError("Correlator must be projected before getting a plateau.")
-845        if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])):
-846            raise ValueError("plateau is undefined at all timeslices in plateaurange.")
-847        if auto_gamma:
-848            self.gamma_method()
-849        if method == "fit":
-850            def const_func(a, t):
-851                return a[0]
-852            return self.fit(const_func, plateau_range)[0]
-853        elif method in ["avg", "average", "mean"]:
-854            returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None])
-855            return returnvalue
-856
-857        else:
-858            raise ValueError("Unsupported plateau method: " + method)
+            
829    def plateau(self, plateau_range=None, method="fit", auto_gamma=False):
+830        """ Extract a plateau value from a Corr object
+831
+832        Parameters
+833        ----------
+834        plateau_range : list
+835            list with two entries, indicating the first and the last timeslice
+836            of the plateau region.
+837        method : str
+838            method to extract the plateau.
+839                'fit' fits a constant to the plateau region
+840                'avg', 'average' or 'mean' just average over the given timeslices.
+841        auto_gamma : bool
+842            apply gamma_method with default parameters to the Corr. Defaults to None
+843        """
+844        if not plateau_range:
+845            if self.prange:
+846                plateau_range = self.prange
+847            else:
+848                raise Exception("no plateau range provided")
+849        if self.N != 1:
+850            raise ValueError("Correlator must be projected before getting a plateau.")
+851        if (all([self.content[t] is None for t in range(plateau_range[0], plateau_range[1] + 1)])):
+852            raise ValueError("plateau is undefined at all timeslices in plateaurange.")
+853        if auto_gamma:
+854            self.gamma_method()
+855        if method == "fit":
+856            def const_func(a, t):
+857                return a[0]
+858            return self.fit(const_func, plateau_range)[0]
+859        elif method in ["avg", "average", "mean"]:
+860            returnvalue = np.mean([item[0] for item in self.content[plateau_range[0]:plateau_range[1] + 1] if item is not None])
+861            return returnvalue
+862
+863        else:
+864            raise ValueError("Unsupported plateau method: " + method)
 
@@ -4750,17 +4766,17 @@ apply gamma_method with default parameters to the Corr. Defaults to None
-
860    def set_prange(self, prange):
-861        """Sets the attribute prange of the Corr object."""
-862        if not len(prange) == 2:
-863            raise ValueError("prange must be a list or array with two values")
-864        if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))):
-865            raise TypeError("Start and end point must be integers")
-866        if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] <= prange[1]):
-867            raise ValueError("Start and end point must define a range in the interval 0,T")
-868
-869        self.prange = prange
-870        return
+            
866    def set_prange(self, prange):
+867        """Sets the attribute prange of the Corr object."""
+868        if not len(prange) == 2:
+869            raise ValueError("prange must be a list or array with two values")
+870        if not ((isinstance(prange[0], int)) and (isinstance(prange[1], int))):
+871            raise TypeError("Start and end point must be integers")
+872        if not (0 <= prange[0] <= self.T and 0 <= prange[1] <= self.T and prange[0] <= prange[1]):
+873            raise ValueError("Start and end point must define a range in the interval 0,T")
+874
+875        self.prange = prange
+876        return
 
@@ -4780,130 +4796,130 @@ apply gamma_method with default parameters to the Corr. Defaults to None
-
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.")
+            
 878    def show(self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, fit_key=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None):
+ 879        """Plots the correlator using the tag of the correlator as label if available.
+ 880
+ 881        Parameters
+ 882        ----------
+ 883        x_range : list
+ 884            list of two values, determining the range of the x-axis e.g. [4, 8].
+ 885        comp : Corr or list of Corr
+ 886            Correlator or list of correlators which are plotted for comparison.
+ 887            The tags of these correlators are used as labels if available.
+ 888        logscale : bool
+ 889            Sets y-axis to logscale.
+ 890        plateau : Obs
+ 891            Plateau value to be visualized in the figure.
+ 892        fit_res : Fit_result
+ 893            Fit_result object to be visualized.
+ 894        fit_key : str
+ 895            Key for the fit function in Fit_result.fit_function (for combined fits).
+ 896        ylabel : str
+ 897            Label for the y-axis.
+ 898        save : str
+ 899            path to file in which the figure should be saved.
+ 900        auto_gamma : bool
+ 901            Apply the gamma method with standard parameters to all correlators and plateau values before plotting.
+ 902        hide_sigma : float
+ 903            Hides data points from the first value on which is consistent with zero within 'hide_sigma' standard errors.
+ 904        references : list
+ 905            List of floating point values that are displayed as horizontal lines for reference.
+ 906        title : string
+ 907            Optional title of the figure.
+ 908        """
+ 909        if self.N != 1:
+ 910            raise ValueError("Correlator must be projected before plotting")
+ 911
+ 912        if auto_gamma:
+ 913            self.gamma_method()
+ 914
+ 915        if x_range is None:
+ 916            x_range = [0, self.T - 1]
+ 917
+ 918        fig = plt.figure()
+ 919        ax1 = fig.add_subplot(111)
+ 920
+ 921        x, y, y_err = self.plottable()
+ 922        if hide_sigma:
+ 923            hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1
+ 924        else:
+ 925            hide_from = None
+ 926        ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=self.tag)
+ 927        if logscale:
+ 928            ax1.set_yscale('log')
+ 929        else:
+ 930            if y_range is None:
+ 931                try:
+ 932                    y_min = min([(x[0].value - x[0].dvalue) for x in self.content[x_range[0]: x_range[1] + 1] if (x is not None) and x[0].dvalue < 2 * np.abs(x[0].value)])
+ 933                    y_max = max([(x[0].value + x[0].dvalue) for x in self.content[x_range[0]: x_range[1] + 1] if (x is not None) and x[0].dvalue < 2 * np.abs(x[0].value)])
+ 934                    ax1.set_ylim([y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min)])
+ 935                except Exception:
+ 936                    pass
+ 937            else:
+ 938                ax1.set_ylim(y_range)
+ 939        if comp:
+ 940            if isinstance(comp, (Corr, list)):
+ 941                for corr in comp if isinstance(comp, list) else [comp]:
+ 942                    if auto_gamma:
+ 943                        corr.gamma_method()
+ 944                    x, y, y_err = corr.plottable()
+ 945                    if hide_sigma:
+ 946                        hide_from = np.argmax((hide_sigma * np.array(y_err[1:])) > np.abs(y[1:])) - 1
+ 947                    else:
+ 948                        hide_from = None
+ 949                    ax1.errorbar(x[:hide_from], y[:hide_from], y_err[:hide_from], label=corr.tag, mfc=plt.rcParams['axes.facecolor'])
+ 950            else:
+ 951                raise TypeError("'comp' must be a correlator or a list of correlators.")
+ 952
+ 953        if plateau:
+ 954            if isinstance(plateau, Obs):
+ 955                if auto_gamma:
+ 956                    plateau.gamma_method()
+ 957                ax1.axhline(y=plateau.value, linewidth=2, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--', label=str(plateau))
+ 958                ax1.axhspan(plateau.value - plateau.dvalue, plateau.value + plateau.dvalue, alpha=0.25, color=plt.rcParams['text.color'], ls='-')
+ 959            else:
+ 960                raise TypeError("'plateau' must be an Obs")
+ 961
+ 962        if references:
+ 963            if isinstance(references, list):
+ 964                for ref in references:
+ 965                    ax1.axhline(y=ref, linewidth=1, color=plt.rcParams['text.color'], alpha=0.6, marker=',', ls='--')
+ 966            else:
+ 967                raise TypeError("'references' must be a list of floating pint values.")
+ 968
+ 969        if self.prange:
+ 970            ax1.axvline(self.prange[0], 0, 1, ls='-', marker=',', color="black", zorder=0)
+ 971            ax1.axvline(self.prange[1], 0, 1, ls='-', marker=',', color="black", zorder=0)
+ 972
+ 973        if fit_res:
+ 974            x_samples = np.arange(x_range[0], x_range[1] + 1, 0.05)
+ 975            if isinstance(fit_res.fit_function, dict):
+ 976                if fit_key:
+ 977                    ax1.plot(x_samples, fit_res.fit_function[fit_key]([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2)
+ 978                else:
+ 979                    raise ValueError("Please provide a 'fit_key' for visualizing combined fits.")
+ 980            else:
+ 981                ax1.plot(x_samples, fit_res.fit_function([o.value for o in fit_res.fit_parameters], x_samples), ls='-', marker=',', lw=2)
+ 982
+ 983        ax1.set_xlabel(r'$x_0 / a$')
+ 984        if ylabel:
+ 985            ax1.set_ylabel(ylabel)
+ 986        ax1.set_xlim([x_range[0] - 0.5, x_range[1] + 0.5])
+ 987
+ 988        _handles, labels = ax1.get_legend_handles_labels()
+ 989        if labels:
+ 990            ax1.legend()
+ 991
+ 992        if title:
+ 993            plt.title(title)
+ 994
+ 995        plt.draw()
+ 996
+ 997        if save:
+ 998            if isinstance(save, str):
+ 999                fig.savefig(save, bbox_inches='tight')
+1000            else:
+1001                raise TypeError("'save' has to be a string.")
 
@@ -4953,34 +4969,34 @@ Optional title of the figure.
-
 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 [sum(map(o[0].e_content.get, o[0].mc_names), []) for o in self.content if o is not None] for item in sublist]))
-1009        x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content) 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
+            
1003    def spaghetti_plot(self, logscale=True):
+1004        """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations.
+1005
+1006        Parameters
+1007        ----------
+1008        logscale : bool
+1009            Determines whether the scale of the y-axis is logarithmic or standard.
+1010        """
+1011        if self.N != 1:
+1012            raise ValueError("Correlator needs to be projected first.")
 1013
-1014            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()
+1014        mc_names = list(set([item for sublist in [list(itertools.chain.from_iterable(map(o[0].e_content.get, o[0].mc_names))) for o in self.content if o is not None] for item in sublist]))
+1015        x0_vals = [n for (n, o) in zip(np.arange(self.T), self.content, strict=True) if o is not None]
+1016
+1017        for name in mc_names:
+1018            data = np.array([o[0].deltas[name] + o[0].r_values[name] for o in self.content if o is not None]).T
+1019
+1020            fig = plt.figure()
+1021            ax = fig.add_subplot(111)
+1022            for dat in data:
+1023                ax.plot(x0_vals, dat, ls='-', marker='')
+1024
+1025            if logscale is True:
+1026                ax.set_yscale('log')
+1027
+1028            ax.set_xlabel(r'$x_0 / a$')
+1029            plt.title(name)
+1030            plt.draw()
 
@@ -5007,29 +5023,29 @@ Determines whether the scale of the y-axis is logarithmic or standard.
-
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))
+            
1032    def dump(self, filename, datatype="json.gz", **kwargs):
+1033        """Dumps the Corr into a file of chosen type
+1034        Parameters
+1035        ----------
+1036        filename : str
+1037            Name of the file to be saved.
+1038        datatype : str
+1039            Format of the exported file. Supported formats include
+1040            "json.gz" and "pickle"
+1041        path : str
+1042            specifies a custom path for the file (default '.')
+1043        """
+1044        if datatype == "json.gz":
+1045            from .input.json import dump_to_json
+1046            if 'path' in kwargs:
+1047                file_name = kwargs.get('path') + '/' + filename
+1048            else:
+1049                file_name = filename
+1050            dump_to_json(self, file_name)
+1051        elif datatype == "pickle":
+1052            dump_object(self, filename, **kwargs)
+1053        else:
+1054            raise ValueError("Unknown datatype " + str(datatype))
 
@@ -5061,8 +5077,8 @@ specifies a custom path for the file (default '.')
-
1050    def print(self, print_range=None):
-1051        print(self.__repr__(print_range))
+            
1056    def print(self, print_range=None):
+1057        print(self.__repr__(print_range))
 
@@ -5080,8 +5096,8 @@ specifies a custom path for the file (default '.')
-
1267    def sqrt(self):
-1268        return self ** 0.5
+            
1275    def sqrt(self):
+1276        return self ** 0.5
 
@@ -5099,9 +5115,9 @@ specifies a custom path for the file (default '.')
-
1270    def log(self):
-1271        newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content]
-1272        return Corr(newcontent, prange=self.prange)
+            
1278    def log(self):
+1279        newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content]
+1280        return Corr(newcontent, prange=self.prange)
 
@@ -5119,9 +5135,9 @@ specifies a custom path for the file (default '.')
-
1274    def exp(self):
-1275        newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content]
-1276        return Corr(newcontent, prange=self.prange)
+            
1282    def exp(self):
+1283        newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content]
+1284        return Corr(newcontent, prange=self.prange)
 
@@ -5139,8 +5155,8 @@ specifies a custom path for the file (default '.')
-
1291    def sin(self):
-1292        return self._apply_func_to_corr(np.sin)
+            
1299    def sin(self):
+1300        return self._apply_func_to_corr(np.sin)
 
@@ -5158,8 +5174,8 @@ specifies a custom path for the file (default '.')
-
1294    def cos(self):
-1295        return self._apply_func_to_corr(np.cos)
+            
1302    def cos(self):
+1303        return self._apply_func_to_corr(np.cos)
 
@@ -5177,8 +5193,8 @@ specifies a custom path for the file (default '.')
-
1297    def tan(self):
-1298        return self._apply_func_to_corr(np.tan)
+            
1305    def tan(self):
+1306        return self._apply_func_to_corr(np.tan)
 
@@ -5196,8 +5212,8 @@ specifies a custom path for the file (default '.')
-
1300    def sinh(self):
-1301        return self._apply_func_to_corr(np.sinh)
+            
1308    def sinh(self):
+1309        return self._apply_func_to_corr(np.sinh)
 
@@ -5215,8 +5231,8 @@ specifies a custom path for the file (default '.')
-
1303    def cosh(self):
-1304        return self._apply_func_to_corr(np.cosh)
+            
1311    def cosh(self):
+1312        return self._apply_func_to_corr(np.cosh)
 
@@ -5234,8 +5250,8 @@ specifies a custom path for the file (default '.')
-
1306    def tanh(self):
-1307        return self._apply_func_to_corr(np.tanh)
+            
1314    def tanh(self):
+1315        return self._apply_func_to_corr(np.tanh)
 
@@ -5253,8 +5269,8 @@ specifies a custom path for the file (default '.')
-
1309    def arcsin(self):
-1310        return self._apply_func_to_corr(np.arcsin)
+            
1317    def arcsin(self):
+1318        return self._apply_func_to_corr(np.arcsin)
 
@@ -5272,8 +5288,8 @@ specifies a custom path for the file (default '.')
-
1312    def arccos(self):
-1313        return self._apply_func_to_corr(np.arccos)
+            
1320    def arccos(self):
+1321        return self._apply_func_to_corr(np.arccos)
 
@@ -5291,8 +5307,8 @@ specifies a custom path for the file (default '.')
-
1315    def arctan(self):
-1316        return self._apply_func_to_corr(np.arctan)
+            
1323    def arctan(self):
+1324        return self._apply_func_to_corr(np.arctan)
 
@@ -5310,8 +5326,8 @@ specifies a custom path for the file (default '.')
-
1318    def arcsinh(self):
-1319        return self._apply_func_to_corr(np.arcsinh)
+            
1326    def arcsinh(self):
+1327        return self._apply_func_to_corr(np.arcsinh)
 
@@ -5329,8 +5345,8 @@ specifies a custom path for the file (default '.')
-
1321    def arccosh(self):
-1322        return self._apply_func_to_corr(np.arccosh)
+            
1329    def arccosh(self):
+1330        return self._apply_func_to_corr(np.arccosh)
 
@@ -5348,8 +5364,8 @@ specifies a custom path for the file (default '.')
-
1324    def arctanh(self):
-1325        return self._apply_func_to_corr(np.arctanh)
+            
1332    def arctanh(self):
+1333        return self._apply_func_to_corr(np.arctanh)
 
@@ -5365,15 +5381,15 @@ specifies a custom path for the file (default '.')
-
1340    @property
-1341    def real(self):
-1342        def return_real(obs_OR_cobs):
-1343            if isinstance(obs_OR_cobs.flatten()[0], CObs):
-1344                return np.vectorize(lambda x: x.real)(obs_OR_cobs)
-1345            else:
-1346                return obs_OR_cobs
-1347
-1348        return self._apply_func_to_corr(return_real)
+            
1348    @property
+1349    def real(self):
+1350        def return_real(obs_OR_cobs):
+1351            if isinstance(obs_OR_cobs.flatten()[0], CObs):
+1352                return np.vectorize(lambda x: x.real)(obs_OR_cobs)
+1353            else:
+1354                return obs_OR_cobs
+1355
+1356        return self._apply_func_to_corr(return_real)
 
@@ -5389,15 +5405,15 @@ specifies a custom path for the file (default '.')
-
1350    @property
-1351    def imag(self):
-1352        def return_imag(obs_OR_cobs):
-1353            if isinstance(obs_OR_cobs.flatten()[0], CObs):
-1354                return np.vectorize(lambda x: x.imag)(obs_OR_cobs)
-1355            else:
-1356                return obs_OR_cobs * 0  # So it stays the right type
-1357
-1358        return self._apply_func_to_corr(return_imag)
+            
1358    @property
+1359    def imag(self):
+1360        def return_imag(obs_OR_cobs):
+1361            if isinstance(obs_OR_cobs.flatten()[0], CObs):
+1362                return np.vectorize(lambda x: x.imag)(obs_OR_cobs)
+1363            else:
+1364                return obs_OR_cobs * 0  # So it stays the right type
+1365
+1366        return self._apply_func_to_corr(return_imag)
 
@@ -5415,64 +5431,64 @@ specifies a custom path for the file (default '.')
-
1360    def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
-1361        r''' Project large correlation matrix to lowest states
-1362
-1363        This method can be used to reduce the size of an (N x N) correlation matrix
-1364        to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise
-1365        is still small.
-1366
-1367        Parameters
-1368        ----------
-1369        Ntrunc: int
-1370            Rank of the target matrix.
-1371        tproj: int
-1372            Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method.
-1373            The default value is 3.
-1374        t0proj: int
-1375            Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly
-1376            discouraged for O(a) improved theories, since the correctness of the procedure
-1377            cannot be granted in this case. The default value is 2.
-1378        basematrix : Corr
-1379            Correlation matrix that is used to determine the eigenvectors of the
-1380            lowest states based on a GEVP. basematrix is taken to be the Corr itself if
-1381            is is not specified.
-1382
-1383        Notes
-1384        -----
-1385        We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving
-1386        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}$
-1387        and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the
-1388        resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via
-1389        $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large
-1390        correlation matrix and to remove some noise that is added by irrelevant operators.
-1391        This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated
-1392        bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.
-1393        '''
-1394
-1395        if self.N == 1:
-1396            raise ValueError('Method cannot be applied to one-dimensional correlators.')
-1397        if basematrix is None:
-1398            basematrix = self
-1399        if Ntrunc >= basematrix.N:
-1400            raise ValueError('Cannot truncate using Ntrunc <= %d' % (basematrix.N))
-1401        if basematrix.N != self.N:
-1402            raise ValueError('basematrix and targetmatrix have to be of the same size.')
-1403
-1404        evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc]
-1405
-1406        tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object)
-1407        rmat = []
-1408        for t in range(basematrix.T):
-1409            if self.content[t] is None:
-1410                rmat.append(None)
-1411            else:
-1412                for i in range(Ntrunc):
-1413                    for j in range(Ntrunc):
-1414                        tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j]
-1415                rmat.append(np.copy(tmpmat))
-1416
-1417        return Corr(rmat)
+            
1368    def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None):
+1369        r''' Project large correlation matrix to lowest states
+1370
+1371        This method can be used to reduce the size of an (N x N) correlation matrix
+1372        to (Ntrunc x Ntrunc) by solving a GEVP at very early times where the noise
+1373        is still small.
+1374
+1375        Parameters
+1376        ----------
+1377        Ntrunc: int
+1378            Rank of the target matrix.
+1379        tproj: int
+1380            Time where the eigenvectors are evaluated, corresponds to ts in the GEVP method.
+1381            The default value is 3.
+1382        t0proj: int
+1383            Time where the correlation matrix is inverted. Choosing t0proj=1 is strongly
+1384            discouraged for O(a) improved theories, since the correctness of the procedure
+1385            cannot be granted in this case. The default value is 2.
+1386        basematrix : Corr
+1387            Correlation matrix that is used to determine the eigenvectors of the
+1388            lowest states based on a GEVP. basematrix is taken to be the Corr itself if
+1389            is is not specified.
+1390
+1391        Notes
+1392        -----
+1393        We have the basematrix $C(t)$ and the target matrix $G(t)$. We start by solving
+1394        the GEVP $$C(t) v_n(t, t_0) = \lambda_n(t, t_0) C(t_0) v_n(t, t_0)$$ where $t \equiv t_\mathrm{proj}$
+1395        and $t_0 \equiv t_{0, \mathrm{proj}}$. The target matrix is projected onto the subspace of the
+1396        resulting eigenvectors $v_n, n=1,\dots,N_\mathrm{trunc}$ via
+1397        $$G^\prime_{i, j}(t) = (v_i, G(t) v_j)$$. This allows to reduce the size of a large
+1398        correlation matrix and to remove some noise that is added by irrelevant operators.
+1399        This may allow to use the GEVP on $G(t)$ at late times such that the theoretically motivated
+1400        bound $t_0 \leq t/2$ holds, since the condition number of $G(t)$ is decreased, compared to $C(t)$.
+1401        '''
+1402
+1403        if self.N == 1:
+1404            raise ValueError('Method cannot be applied to one-dimensional correlators.')
+1405        if basematrix is None:
+1406            basematrix = self
+1407        if Ntrunc >= basematrix.N:
+1408            raise ValueError(f'Cannot truncate using Ntrunc >= {basematrix.N}')
+1409        if basematrix.N != self.N:
+1410            raise ValueError('basematrix and targetmatrix have to be of the same size.')
+1411
+1412        evecs = basematrix.GEVP(t0proj, tproj, sort=None)[:Ntrunc]
+1413
+1414        tmpmat = np.empty((Ntrunc, Ntrunc), dtype=object)
+1415        rmat = []
+1416        for t in range(basematrix.T):
+1417            if self.content[t] is None:
+1418                rmat.append(None)
+1419            else:
+1420                for i in range(Ntrunc):
+1421                    for j in range(Ntrunc):
+1422                        tmpmat[i][j] = evecs[i].T @ self[t] @ evecs[j]
+1423                rmat.append(np.copy(tmpmat))
+1424
+1425        return Corr(rmat)
 
diff --git a/docs/pyerrors/covobs.html b/docs/pyerrors/covobs.html index deddaea7..652426a1 100644 --- a/docs/pyerrors/covobs.html +++ b/docs/pyerrors/covobs.html @@ -131,7 +131,7 @@
32 raise Exception('Have to specify position of cov-element belonging to mean!') 33 else: 34 if pos > self.N: - 35 raise Exception('pos %d too large for covariance matrix with dimension %dx%d!' % (pos, self.N, self.N)) + 35 raise Exception(f'pos {pos} too large for covariance matrix with dimension {self.N}x{self.N}!') 36 self._grad = np.zeros((self.N, 1)) 37 self._grad[pos] = 1. 38 else: @@ -171,7 +171,7 @@ 72 for i in range(self.N): 73 for j in range(i): 74 if not self._cov[i][j] == self._cov[j][i]: - 75 raise Exception('Covariance matrix is non-symmetric for (%d, %d' % (i, j)) + 75 raise Exception(f'Covariance matrix is non-symmetric for ({i}, {j})') 76 77 evals = np.linalg.eigvalsh(self._cov) 78 for ev in evals: @@ -247,7 +247,7 @@ 33 raise Exception('Have to specify position of cov-element belonging to mean!') 34 else: 35 if pos > self.N: - 36 raise Exception('pos %d too large for covariance matrix with dimension %dx%d!' % (pos, self.N, self.N)) + 36 raise Exception(f'pos {pos} too large for covariance matrix with dimension {self.N}x{self.N}!') 37 self._grad = np.zeros((self.N, 1)) 38 self._grad[pos] = 1. 39 else: @@ -287,7 +287,7 @@ 73 for i in range(self.N): 74 for j in range(i): 75 if not self._cov[i][j] == self._cov[j][i]: - 76 raise Exception('Covariance matrix is non-symmetric for (%d, %d' % (i, j)) + 76 raise Exception(f'Covariance matrix is non-symmetric for ({i}, {j})') 77 78 evals = np.linalg.eigvalsh(self._cov) 79 for ev in evals: @@ -361,7 +361,7 @@ 33 raise Exception('Have to specify position of cov-element belonging to mean!') 34 else: 35 if pos > self.N: -36 raise Exception('pos %d too large for covariance matrix with dimension %dx%d!' % (pos, self.N, self.N)) +36 raise Exception(f'pos {pos} too large for covariance matrix with dimension {self.N}x{self.N}!') 37 self._grad = np.zeros((self.N, 1)) 38 self._grad[pos] = 1. 39 else: diff --git a/docs/pyerrors/dirac.html b/docs/pyerrors/dirac.html index d74c23a4..ab301c54 100644 --- a/docs/pyerrors/dirac.html +++ b/docs/pyerrors/dirac.html @@ -105,100 +105,99 @@
 1import numpy as np
  2
- 3
- 4gammaX = np.array(
- 5    [[0, 0, 0, 1j], [0, 0, 1j, 0], [0, -1j, 0, 0], [-1j, 0, 0, 0]],
- 6    dtype=complex)
- 7gammaY = np.array(
- 8    [[0, 0, 0, -1], [0, 0, 1, 0], [0, 1, 0, 0], [-1, 0, 0, 0]],
- 9    dtype=complex)
-10gammaZ = np.array(
-11    [[0, 0, 1j, 0], [0, 0, 0, -1j], [-1j, 0, 0, 0], [0, 1j, 0, 0]],
-12    dtype=complex)
-13gammaT = np.array(
-14    [[0, 0, 1, 0], [0, 0, 0, 1], [1, 0, 0, 0], [0, 1, 0, 0]],
-15    dtype=complex)
-16gamma = np.array([gammaX, gammaY, gammaZ, gammaT])
-17gamma5 = np.array(
-18    [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1]],
-19    dtype=complex)
-20identity = np.array(
-21    [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]],
-22    dtype=complex)
+ 3gammaX = np.array(
+ 4    [[0, 0, 0, 1j], [0, 0, 1j, 0], [0, -1j, 0, 0], [-1j, 0, 0, 0]],
+ 5    dtype=complex)
+ 6gammaY = np.array(
+ 7    [[0, 0, 0, -1], [0, 0, 1, 0], [0, 1, 0, 0], [-1, 0, 0, 0]],
+ 8    dtype=complex)
+ 9gammaZ = np.array(
+10    [[0, 0, 1j, 0], [0, 0, 0, -1j], [-1j, 0, 0, 0], [0, 1j, 0, 0]],
+11    dtype=complex)
+12gammaT = np.array(
+13    [[0, 0, 1, 0], [0, 0, 0, 1], [1, 0, 0, 0], [0, 1, 0, 0]],
+14    dtype=complex)
+15gamma = np.array([gammaX, gammaY, gammaZ, gammaT])
+16gamma5 = np.array(
+17    [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1]],
+18    dtype=complex)
+19identity = np.array(
+20    [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]],
+21    dtype=complex)
+22
 23
-24
-25def epsilon_tensor(i, j, k):
-26    """Rank-3 epsilon tensor
-27
-28    Based on https://codegolf.stackexchange.com/a/160375
-29
-30    Returns
-31    -------
-32    elem : int
-33        Element (i,j,k) of the epsilon tensor of rank 3
-34    """
-35    test_set = set((i, j, k))
-36    if not (test_set <= set((1, 2, 3)) or test_set <= set((0, 1, 2))):
-37        raise ValueError("Unexpected input", i, j, k)
-38
-39    return (i - j) * (j - k) * (k - i) / 2
+24def epsilon_tensor(i, j, k):
+25    """Rank-3 epsilon tensor
+26
+27    Based on https://codegolf.stackexchange.com/a/160375
+28
+29    Returns
+30    -------
+31    elem : int
+32        Element (i,j,k) of the epsilon tensor of rank 3
+33    """
+34    test_set = set((i, j, k))
+35    if not (test_set <= set((1, 2, 3)) or test_set <= set((0, 1, 2))):
+36        raise ValueError("Unexpected input", i, j, k)
+37
+38    return (i - j) * (j - k) * (k - i) / 2
+39
 40
-41
-42def epsilon_tensor_rank4(i, j, k, o):
-43    """Rank-4 epsilon tensor
-44
-45    Extension of https://codegolf.stackexchange.com/a/160375
+41def epsilon_tensor_rank4(i, j, k, o):
+42    """Rank-4 epsilon tensor
+43
+44    Extension of https://codegolf.stackexchange.com/a/160375
+45
 46
-47
-48    Returns
-49    -------
-50    elem : int
-51        Element (i,j,k,o) of the epsilon tensor of rank 4
-52    """
-53    test_set = set((i, j, k, o))
-54    if not (test_set <= set((1, 2, 3, 4)) or test_set <= set((0, 1, 2, 3))):
-55        raise ValueError("Unexpected input", i, j, k, o)
-56
-57    return (i - j) * (j - k) * (k - i) * (i - o) * (j - o) * (o - k) / 12
+47    Returns
+48    -------
+49    elem : int
+50        Element (i,j,k,o) of the epsilon tensor of rank 4
+51    """
+52    test_set = set((i, j, k, o))
+53    if not (test_set <= set((1, 2, 3, 4)) or test_set <= set((0, 1, 2, 3))):
+54        raise ValueError("Unexpected input", i, j, k, o)
+55
+56    return (i - j) * (j - k) * (k - i) * (i - o) * (j - o) * (o - k) / 12
+57
 58
-59
-60def Grid_gamma(gamma_tag):
-61    """Returns gamma matrix in Grid labeling."""
-62    if gamma_tag == 'Identity':
-63        g = identity
-64    elif gamma_tag == 'Gamma5':
-65        g = gamma5
-66    elif gamma_tag == 'GammaX':
-67        g = gamma[0]
-68    elif gamma_tag == 'GammaY':
-69        g = gamma[1]
-70    elif gamma_tag == 'GammaZ':
-71        g = gamma[2]
-72    elif gamma_tag == 'GammaT':
-73        g = gamma[3]
-74    elif gamma_tag == 'GammaXGamma5':
-75        g = gamma[0] @ gamma5
-76    elif gamma_tag == 'GammaYGamma5':
-77        g = gamma[1] @ gamma5
-78    elif gamma_tag == 'GammaZGamma5':
-79        g = gamma[2] @ gamma5
-80    elif gamma_tag == 'GammaTGamma5':
-81        g = gamma[3] @ gamma5
-82    elif gamma_tag == 'SigmaXT':
-83        g = 0.5 * (gamma[0] @ gamma[3] - gamma[3] @ gamma[0])
-84    elif gamma_tag == 'SigmaXY':
-85        g = 0.5 * (gamma[0] @ gamma[1] - gamma[1] @ gamma[0])
-86    elif gamma_tag == 'SigmaXZ':
-87        g = 0.5 * (gamma[0] @ gamma[2] - gamma[2] @ gamma[0])
-88    elif gamma_tag == 'SigmaYT':
-89        g = 0.5 * (gamma[1] @ gamma[3] - gamma[3] @ gamma[1])
-90    elif gamma_tag == 'SigmaYZ':
-91        g = 0.5 * (gamma[1] @ gamma[2] - gamma[2] @ gamma[1])
-92    elif gamma_tag == 'SigmaZT':
-93        g = 0.5 * (gamma[2] @ gamma[3] - gamma[3] @ gamma[2])
-94    else:
-95        raise ValueError('Unkown gamma structure', gamma_tag)
-96    return g
+59def Grid_gamma(gamma_tag):
+60    """Returns gamma matrix in Grid labeling."""
+61    if gamma_tag == 'Identity':
+62        g = identity
+63    elif gamma_tag == 'Gamma5':
+64        g = gamma5
+65    elif gamma_tag == 'GammaX':
+66        g = gamma[0]
+67    elif gamma_tag == 'GammaY':
+68        g = gamma[1]
+69    elif gamma_tag == 'GammaZ':
+70        g = gamma[2]
+71    elif gamma_tag == 'GammaT':
+72        g = gamma[3]
+73    elif gamma_tag == 'GammaXGamma5':
+74        g = gamma[0] @ gamma5
+75    elif gamma_tag == 'GammaYGamma5':
+76        g = gamma[1] @ gamma5
+77    elif gamma_tag == 'GammaZGamma5':
+78        g = gamma[2] @ gamma5
+79    elif gamma_tag == 'GammaTGamma5':
+80        g = gamma[3] @ gamma5
+81    elif gamma_tag == 'SigmaXT':
+82        g = 0.5 * (gamma[0] @ gamma[3] - gamma[3] @ gamma[0])
+83    elif gamma_tag == 'SigmaXY':
+84        g = 0.5 * (gamma[0] @ gamma[1] - gamma[1] @ gamma[0])
+85    elif gamma_tag == 'SigmaXZ':
+86        g = 0.5 * (gamma[0] @ gamma[2] - gamma[2] @ gamma[0])
+87    elif gamma_tag == 'SigmaYT':
+88        g = 0.5 * (gamma[1] @ gamma[3] - gamma[3] @ gamma[1])
+89    elif gamma_tag == 'SigmaYZ':
+90        g = 0.5 * (gamma[1] @ gamma[2] - gamma[2] @ gamma[1])
+91    elif gamma_tag == 'SigmaZT':
+92        g = 0.5 * (gamma[2] @ gamma[3] - gamma[3] @ gamma[2])
+93    else:
+94        raise ValueError('Unkown gamma structure', gamma_tag)
+95    return g
 
@@ -341,21 +340,21 @@
-
26def epsilon_tensor(i, j, k):
-27    """Rank-3 epsilon tensor
-28
-29    Based on https://codegolf.stackexchange.com/a/160375
-30
-31    Returns
-32    -------
-33    elem : int
-34        Element (i,j,k) of the epsilon tensor of rank 3
-35    """
-36    test_set = set((i, j, k))
-37    if not (test_set <= set((1, 2, 3)) or test_set <= set((0, 1, 2))):
-38        raise ValueError("Unexpected input", i, j, k)
-39
-40    return (i - j) * (j - k) * (k - i) / 2
+            
25def epsilon_tensor(i, j, k):
+26    """Rank-3 epsilon tensor
+27
+28    Based on https://codegolf.stackexchange.com/a/160375
+29
+30    Returns
+31    -------
+32    elem : int
+33        Element (i,j,k) of the epsilon tensor of rank 3
+34    """
+35    test_set = set((i, j, k))
+36    if not (test_set <= set((1, 2, 3)) or test_set <= set((0, 1, 2))):
+37        raise ValueError("Unexpected input", i, j, k)
+38
+39    return (i - j) * (j - k) * (k - i) / 2
 
@@ -384,22 +383,22 @@ Element (i,j,k) of the epsilon tensor of rank 3
-
43def epsilon_tensor_rank4(i, j, k, o):
-44    """Rank-4 epsilon tensor
-45
-46    Extension of https://codegolf.stackexchange.com/a/160375
+            
42def epsilon_tensor_rank4(i, j, k, o):
+43    """Rank-4 epsilon tensor
+44
+45    Extension of https://codegolf.stackexchange.com/a/160375
+46
 47
-48
-49    Returns
-50    -------
-51    elem : int
-52        Element (i,j,k,o) of the epsilon tensor of rank 4
-53    """
-54    test_set = set((i, j, k, o))
-55    if not (test_set <= set((1, 2, 3, 4)) or test_set <= set((0, 1, 2, 3))):
-56        raise ValueError("Unexpected input", i, j, k, o)
-57
-58    return (i - j) * (j - k) * (k - i) * (i - o) * (j - o) * (o - k) / 12
+48    Returns
+49    -------
+50    elem : int
+51        Element (i,j,k,o) of the epsilon tensor of rank 4
+52    """
+53    test_set = set((i, j, k, o))
+54    if not (test_set <= set((1, 2, 3, 4)) or test_set <= set((0, 1, 2, 3))):
+55        raise ValueError("Unexpected input", i, j, k, o)
+56
+57    return (i - j) * (j - k) * (k - i) * (i - o) * (j - o) * (o - k) / 12
 
@@ -428,43 +427,43 @@ Element (i,j,k,o) of the epsilon tensor of rank 4
-
61def Grid_gamma(gamma_tag):
-62    """Returns gamma matrix in Grid labeling."""
-63    if gamma_tag == 'Identity':
-64        g = identity
-65    elif gamma_tag == 'Gamma5':
-66        g = gamma5
-67    elif gamma_tag == 'GammaX':
-68        g = gamma[0]
-69    elif gamma_tag == 'GammaY':
-70        g = gamma[1]
-71    elif gamma_tag == 'GammaZ':
-72        g = gamma[2]
-73    elif gamma_tag == 'GammaT':
-74        g = gamma[3]
-75    elif gamma_tag == 'GammaXGamma5':
-76        g = gamma[0] @ gamma5
-77    elif gamma_tag == 'GammaYGamma5':
-78        g = gamma[1] @ gamma5
-79    elif gamma_tag == 'GammaZGamma5':
-80        g = gamma[2] @ gamma5
-81    elif gamma_tag == 'GammaTGamma5':
-82        g = gamma[3] @ gamma5
-83    elif gamma_tag == 'SigmaXT':
-84        g = 0.5 * (gamma[0] @ gamma[3] - gamma[3] @ gamma[0])
-85    elif gamma_tag == 'SigmaXY':
-86        g = 0.5 * (gamma[0] @ gamma[1] - gamma[1] @ gamma[0])
-87    elif gamma_tag == 'SigmaXZ':
-88        g = 0.5 * (gamma[0] @ gamma[2] - gamma[2] @ gamma[0])
-89    elif gamma_tag == 'SigmaYT':
-90        g = 0.5 * (gamma[1] @ gamma[3] - gamma[3] @ gamma[1])
-91    elif gamma_tag == 'SigmaYZ':
-92        g = 0.5 * (gamma[1] @ gamma[2] - gamma[2] @ gamma[1])
-93    elif gamma_tag == 'SigmaZT':
-94        g = 0.5 * (gamma[2] @ gamma[3] - gamma[3] @ gamma[2])
-95    else:
-96        raise ValueError('Unkown gamma structure', gamma_tag)
-97    return g
+            
60def Grid_gamma(gamma_tag):
+61    """Returns gamma matrix in Grid labeling."""
+62    if gamma_tag == 'Identity':
+63        g = identity
+64    elif gamma_tag == 'Gamma5':
+65        g = gamma5
+66    elif gamma_tag == 'GammaX':
+67        g = gamma[0]
+68    elif gamma_tag == 'GammaY':
+69        g = gamma[1]
+70    elif gamma_tag == 'GammaZ':
+71        g = gamma[2]
+72    elif gamma_tag == 'GammaT':
+73        g = gamma[3]
+74    elif gamma_tag == 'GammaXGamma5':
+75        g = gamma[0] @ gamma5
+76    elif gamma_tag == 'GammaYGamma5':
+77        g = gamma[1] @ gamma5
+78    elif gamma_tag == 'GammaZGamma5':
+79        g = gamma[2] @ gamma5
+80    elif gamma_tag == 'GammaTGamma5':
+81        g = gamma[3] @ gamma5
+82    elif gamma_tag == 'SigmaXT':
+83        g = 0.5 * (gamma[0] @ gamma[3] - gamma[3] @ gamma[0])
+84    elif gamma_tag == 'SigmaXY':
+85        g = 0.5 * (gamma[0] @ gamma[1] - gamma[1] @ gamma[0])
+86    elif gamma_tag == 'SigmaXZ':
+87        g = 0.5 * (gamma[0] @ gamma[2] - gamma[2] @ gamma[0])
+88    elif gamma_tag == 'SigmaYT':
+89        g = 0.5 * (gamma[1] @ gamma[3] - gamma[3] @ gamma[1])
+90    elif gamma_tag == 'SigmaYZ':
+91        g = 0.5 * (gamma[1] @ gamma[2] - gamma[2] @ gamma[1])
+92    elif gamma_tag == 'SigmaZT':
+93        g = 0.5 * (gamma[2] @ gamma[3] - gamma[3] @ gamma[2])
+94    else:
+95        raise ValueError('Unkown gamma structure', gamma_tag)
+96    return g
 
diff --git a/docs/pyerrors/fits.html b/docs/pyerrors/fits.html index 33fa2b1d..98bdb6f8 100644 --- a/docs/pyerrors/fits.html +++ b/docs/pyerrors/fits.html @@ -110,954 +110,956 @@
  1import gc
-  2from collections.abc import Sequence
-  3import warnings
-  4import numpy as np
+  2import warnings
+  3from collections.abc import Sequence
+  4
   5import autograd.numpy as anp
-  6import scipy.optimize
-  7import scipy.stats
-  8import matplotlib.pyplot as plt
-  9from matplotlib import gridspec
- 10from odrpack import odr_fit
- 11import iminuit
- 12from autograd import jacobian as auto_jacobian
- 13from autograd import hessian as auto_hessian
- 14from autograd import elementwise_grad as egrad
- 15from numdifftools import Jacobian as num_jacobian
- 16from numdifftools import Hessian as num_hessian
- 17from .obs import Obs, derived_observable, covariance, cov_Obs, invert_corr_cov_cholesky
+  6import iminuit
+  7import matplotlib.pyplot as plt
+  8import numpy as np
+  9import scipy.optimize
+ 10import scipy.stats
+ 11from autograd import elementwise_grad as egrad
+ 12from autograd import hessian as auto_hessian
+ 13from autograd import jacobian as auto_jacobian
+ 14from matplotlib import gridspec
+ 15from numdifftools import Hessian as num_hessian
+ 16from numdifftools import Jacobian as num_jacobian
+ 17from odrpack import odr_fit
  18
- 19
- 20class Fit_result(Sequence):
- 21    """Represents fit results.
- 22
- 23    Attributes
- 24    ----------
- 25    fit_parameters : list
- 26        results for the individual fit parameters,
- 27        also accessible via indices.
- 28    chisquare_by_dof : float
- 29        reduced chisquare.
- 30    p_value : float
- 31        p-value of the fit
- 32    t2_p_value : float
- 33        Hotelling t-squared p-value for correlated fits.
- 34    """
- 35
- 36    def __init__(self):
- 37        self.fit_parameters = None
- 38
- 39    def __getitem__(self, idx):
- 40        return self.fit_parameters[idx]
- 41
- 42    def __len__(self):
- 43        return len(self.fit_parameters)
- 44
- 45    def gamma_method(self, **kwargs):
- 46        """Apply the gamma method to all fit parameters"""
- 47        [o.gamma_method(**kwargs) for o in self.fit_parameters]
- 48
- 49    gm = gamma_method
+ 19from .obs import Obs, cov_Obs, covariance, derived_observable, invert_corr_cov_cholesky
+ 20
+ 21
+ 22class Fit_result(Sequence):
+ 23    """Represents fit results.
+ 24
+ 25    Attributes
+ 26    ----------
+ 27    fit_parameters : list
+ 28        results for the individual fit parameters,
+ 29        also accessible via indices.
+ 30    chisquare_by_dof : float
+ 31        reduced chisquare.
+ 32    p_value : float
+ 33        p-value of the fit
+ 34    t2_p_value : float
+ 35        Hotelling t-squared p-value for correlated fits.
+ 36    """
+ 37
+ 38    def __init__(self):
+ 39        self.fit_parameters = None
+ 40
+ 41    def __getitem__(self, idx):
+ 42        return self.fit_parameters[idx]
+ 43
+ 44    def __len__(self):
+ 45        return len(self.fit_parameters)
+ 46
+ 47    def gamma_method(self, **kwargs):
+ 48        """Apply the gamma method to all fit parameters"""
+ 49        [o.gamma_method(**kwargs) for o in self.fit_parameters]
  50
- 51    def __str__(self):
- 52        my_str = 'Goodness of fit:\n'
- 53        if hasattr(self, 'chisquare_by_dof'):
- 54            my_str += '\u03C7\u00b2/d.o.f. = ' + f'{self.chisquare_by_dof:2.6f}' + '\n'
- 55        elif hasattr(self, 'residual_variance'):
- 56            my_str += 'residual variance = ' + f'{self.residual_variance:2.6f}' + '\n'
- 57        if hasattr(self, 'chisquare_by_expected_chisquare'):
- 58            my_str += '\u03C7\u00b2/\u03C7\u00b2exp  = ' + f'{self.chisquare_by_expected_chisquare:2.6f}' + '\n'
- 59        if hasattr(self, 'p_value'):
- 60            my_str += 'p-value   = ' + f'{self.p_value:2.4f}' + '\n'
- 61        if hasattr(self, 't2_p_value'):
- 62            my_str += 't\u00B2p-value = ' + f'{self.t2_p_value:2.4f}' + '\n'
- 63        my_str += 'Fit parameters:\n'
- 64        for i_par, par in enumerate(self.fit_parameters):
- 65            my_str += str(i_par) + '\t' + ' ' * int(par >= 0) + str(par).rjust(int(par < 0.0)) + '\n'
- 66        return my_str
- 67
- 68    def __repr__(self):
- 69        m = max(map(len, list(self.__dict__.keys()))) + 1
- 70        return '\n'.join([key.rjust(m) + ': ' + repr(value) for key, value in sorted(self.__dict__.items())])
- 71
- 72
- 73def least_squares(x, y, func, priors=None, silent=False, **kwargs):
- 74    r'''Performs a non-linear fit to y = func(x).
- 75        ```
- 76
- 77    Parameters
- 78    ----------
- 79    For an uncombined fit:
- 80
- 81    x : list
- 82        list of floats.
- 83    y : list
- 84        list of Obs.
- 85    func : object
- 86        fit function, has to be of the form
- 87
- 88        ```python
- 89        import autograd.numpy as anp
- 90
- 91        def func(a, x):
- 92            return a[0] + a[1] * x + a[2] * anp.sinh(x)
- 93        ```
- 94
- 95        For multiple x values func can be of the form
+ 51    gm = gamma_method
+ 52
+ 53    def __str__(self):
+ 54        my_str = 'Goodness of fit:\n'
+ 55        if hasattr(self, 'chisquare_by_dof'):
+ 56            my_str += '\u03C7\u00b2/d.o.f. = ' + f'{self.chisquare_by_dof:2.6f}' + '\n'
+ 57        elif hasattr(self, 'residual_variance'):
+ 58            my_str += 'residual variance = ' + f'{self.residual_variance:2.6f}' + '\n'
+ 59        if hasattr(self, 'chisquare_by_expected_chisquare'):
+ 60            my_str += '\u03C7\u00b2/\u03C7\u00b2exp  = ' + f'{self.chisquare_by_expected_chisquare:2.6f}' + '\n'
+ 61        if hasattr(self, 'p_value'):
+ 62            my_str += 'p-value   = ' + f'{self.p_value:2.4f}' + '\n'
+ 63        if hasattr(self, 't2_p_value'):
+ 64            my_str += 't\u00B2p-value = ' + f'{self.t2_p_value:2.4f}' + '\n'
+ 65        my_str += 'Fit parameters:\n'
+ 66        for i_par, par in enumerate(self.fit_parameters):
+ 67            my_str += str(i_par) + '\t' + ' ' * int(par >= 0) + str(par).rjust(int(par < 0.0)) + '\n'
+ 68        return my_str
+ 69
+ 70    def __repr__(self):
+ 71        m = max(map(len, list(self.__dict__.keys()))) + 1
+ 72        return '\n'.join([key.rjust(m) + ': ' + repr(value) for key, value in sorted(self.__dict__.items())])
+ 73
+ 74
+ 75def least_squares(x, y, func, priors=None, silent=False, **kwargs):
+ 76    r'''Performs a non-linear fit to y = func(x).
+ 77        ```
+ 78
+ 79    Parameters
+ 80    ----------
+ 81    For an uncombined fit:
+ 82
+ 83    x : list
+ 84        list of floats.
+ 85    y : list
+ 86        list of Obs.
+ 87    func : object
+ 88        fit function, has to be of the form
+ 89
+ 90        ```python
+ 91        import autograd.numpy as anp
+ 92
+ 93        def func(a, x):
+ 94            return a[0] + a[1] * x + a[2] * anp.sinh(x)
+ 95        ```
  96
- 97        ```python
- 98        def func(a, x):
- 99            (x1, x2) = x
-100            return a[0] * x1 ** 2 + a[1] * x2
-101        ```
-102        It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation
-103        will not work.
-104
-105    OR For a combined fit:
+ 97        For multiple x values func can be of the form
+ 98
+ 99        ```python
+100        def func(a, x):
+101            (x1, x2) = x
+102            return a[0] * x1 ** 2 + a[1] * x2
+103        ```
+104        It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation
+105        will not work.
 106
-107    x : dict
-108        dict of lists.
-109    y : dict
-110        dict of lists of Obs.
-111    funcs : dict
-112        dict of objects
-113        fit functions have to be of the form (here a[0] is the common fit parameter)
-114        ```python
-115        import autograd.numpy as anp
-116        funcs = {"a": func_a,
-117                "b": func_b}
-118
-119        def func_a(a, x):
-120            return a[1] * anp.exp(-a[0] * x)
-121
-122        def func_b(a, x):
-123            return a[2] * anp.exp(-a[0] * x)
-124
-125        It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation
-126        will not work.
-127
-128    priors : dict or list, optional
-129        priors can either be a dictionary with integer keys and the corresponding priors as values or
-130        a list with an entry for every parameter in the fit. The entries can either be
-131        Obs (e.g. results from a previous fit) or strings containing a value and an error formatted like
-132        0.548(23), 500(40) or 0.5(0.4)
-133    silent : bool, optional
-134        If True all output to the console is omitted (default False).
-135    initial_guess : list
-136        can provide an initial guess for the input parameters. Relevant for
-137        non-linear fits with many parameters. In case of correlated fits the guess is used to perform
-138        an uncorrelated fit which then serves as guess for the correlated fit.
-139    method : str, optional
-140        can be used to choose an alternative method for the minimization of chisquare.
-141        The possible methods are the ones which can be used for scipy.optimize.minimize and
-142        migrad of iminuit. If no method is specified, Levenberg–Marquardt is used.
-143        Reliable alternatives are migrad, Powell and Nelder-Mead.
-144    tol: float, optional
-145        can be used (only for combined fits and methods other than Levenberg–Marquardt) to set the tolerance for convergence
-146        to a different value to either speed up convergence at the cost of a larger error on the fitted parameters (and possibly
-147        invalid estimates for parameter uncertainties) or smaller values to get more accurate parameter values
-148        The stopping criterion depends on the method, e.g. migrad: edm_max = 0.002 * tol * errordef (EDM criterion: edm < edm_max)
-149    correlated_fit : bool
-150        If True, use the full inverse covariance matrix in the definition of the chisquare cost function.
-151        For details about how the covariance matrix is estimated see `pyerrors.obs.covariance`.
-152        In practice the correlation matrix is Cholesky decomposed and inverted (instead of the covariance matrix).
-153        This procedure should be numerically more stable as the correlation matrix is typically better conditioned (Jacobi preconditioning).
-154    inv_chol_cov_matrix [array,list], optional
-155        array: shape = (number of y values) X (number of y values)
-156        list:   for an uncombined fit: [""]
-157                for a combined fit: list of keys belonging to the corr_matrix saved in the array, must be the same as the keys of the y dict in alphabetical order
-158        If correlated_fit=True is set as well, can provide an inverse covariance matrix (y errors, dy_f included!) of your own choosing for a correlated fit.
-159        The matrix must be a lower triangular matrix constructed from a Cholesky decomposition: The function invert_corr_cov_cholesky(corr, inverrdiag) can be
-160        used to construct it from a correlation matrix (corr) and the errors dy_f of the data points (inverrdiag = np.diag(1 / np.asarray(dy_f))). For the correct
-161        ordering the correlation matrix (corr) can be sorted via the function sort_corr(corr, kl, yd) where kl is the list of keys and yd the y dict.
-162    expected_chisquare : bool
-163        If True estimates the expected chisquare which is
-164        corrected by effects caused by correlated input data (default False).
-165    resplot : bool
-166        If True, a plot which displays fit, data and residuals is generated (default False).
-167    qqplot : bool
-168        If True, a quantile-quantile plot of the fit result is generated (default False).
-169    num_grad : bool
-170        Use numerical differentation instead of automatic differentiation to perform the error propagation (default False).
-171    n_parms : int, optional
-172        Number of fit parameters. Overrides automatic detection of parameter count.
-173        Useful when autodetection fails. Must match the length of initial_guess or priors (if provided).
-174
-175    Returns
-176    -------
-177    output : Fit_result
-178        Parameters and information on the fitted result.
-179    Examples
-180    ------
-181    >>> # Example of a correlated (correlated_fit = True, inv_chol_cov_matrix handed over) combined fit, based on a randomly generated data set
-182    >>> import numpy as np
-183    >>> from scipy.stats import norm
-184    >>> from scipy.linalg import cholesky
-185    >>> import pyerrors as pe
-186    >>> # generating the random data set
-187    >>> num_samples = 400
-188    >>> N = 3
-189    >>> x = np.arange(N)
-190    >>> x1 = norm.rvs(size=(N, num_samples)) # generate random numbers
-191    >>> x2 = norm.rvs(size=(N, num_samples)) # generate random numbers
-192    >>> r = r1 = r2 = np.zeros((N, N))
-193    >>> y = {}
-194    >>> for i in range(N):
-195    >>>    for j in range(N):
-196    >>>        r[i, j] = np.exp(-0.8 * np.fabs(i - j)) # element in correlation matrix
-197    >>> errl = np.sqrt([3.4, 2.5, 3.6]) # set y errors
-198    >>> for i in range(N):
-199    >>>    for j in range(N):
-200    >>>        r[i, j] *= errl[i] * errl[j] # element in covariance matrix
-201    >>> c = cholesky(r, lower=True)
-202    >>> y = {'a': np.dot(c, x1), 'b': np.dot(c, x2)} # generate y data with the covariance matrix defined
-203    >>> # random data set has been generated, now the dictionaries and the inverse covariance matrix to be handed over are built
-204    >>> x_dict = {}
-205    >>> y_dict = {}
-206    >>> chol_inv_dict = {}
-207    >>> data = []
-208    >>> for key in y.keys():
-209    >>>    x_dict[key] = x
-210    >>>    for i in range(N):
-211    >>>        data.append(pe.Obs([[i + 1 + o for o in y[key][i]]], ['ens'])) # generate y Obs from the y data
-212    >>>    [o.gamma_method() for o in data]
-213    >>>    corr = pe.covariance(data, correlation=True)
-214    >>>    inverrdiag = np.diag(1 / np.asarray([o.dvalue for o in data]))
-215    >>>    chol_inv = pe.obs.invert_corr_cov_cholesky(corr, inverrdiag) # gives form of the inverse covariance matrix needed for the combined correlated fit below
-216    >>> y_dict = {'a': data[:3], 'b': data[3:]}
-217    >>> # common fit parameter p[0] in combined fit
-218    >>> def fit1(p, x):
-219    >>>    return p[0] + p[1] * x
-220    >>> def fit2(p, x):
-221    >>>    return p[0] + p[2] * x
-222    >>> fitf_dict = {'a': fit1, 'b':fit2}
-223    >>> fitp_inv_cov_combined_fit = pe.least_squares(x_dict,y_dict, fitf_dict, correlated_fit = True, inv_chol_cov_matrix = [chol_inv,['a','b']])
-224    Fit with 3 parameters
-225    Method: Levenberg-Marquardt
-226    `ftol` termination condition is satisfied.
-227    chisquare/d.o.f.: 0.5388013574561786 # random
-228    fit parameters [1.11897846 0.96361162 0.92325319] # random
-229
-230    '''
-231    output = Fit_result()
-232
-233    if (isinstance(x, dict) and isinstance(y, dict) and isinstance(func, dict)):
-234        xd = {key: anp.asarray(x[key]) for key in x}
-235        yd = y
-236        funcd = func
-237        output.fit_function = func
-238    elif (isinstance(x, dict) or isinstance(y, dict) or isinstance(func, dict)):
-239        raise TypeError("All arguments have to be dictionaries in order to perform a combined fit.")
-240    else:
-241        x = np.asarray(x)
-242        xd = {"": x}
-243        yd = {"": y}
-244        funcd = {"": func}
-245        output.fit_function = func
-246
-247    if kwargs.get('num_grad') is True:
-248        jacobian = num_jacobian
-249        hessian = num_hessian
-250    else:
-251        jacobian = auto_jacobian
-252        hessian = auto_hessian
-253
-254    key_ls = sorted(list(xd.keys()))
+107    OR For a combined fit:
+108
+109    x : dict
+110        dict of lists.
+111    y : dict
+112        dict of lists of Obs.
+113    funcs : dict
+114        dict of objects
+115        fit functions have to be of the form (here a[0] is the common fit parameter)
+116        ```python
+117        import autograd.numpy as anp
+118        funcs = {"a": func_a,
+119                "b": func_b}
+120
+121        def func_a(a, x):
+122            return a[1] * anp.exp(-a[0] * x)
+123
+124        def func_b(a, x):
+125            return a[2] * anp.exp(-a[0] * x)
+126
+127        It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation
+128        will not work.
+129
+130    priors : dict or list, optional
+131        priors can either be a dictionary with integer keys and the corresponding priors as values or
+132        a list with an entry for every parameter in the fit. The entries can either be
+133        Obs (e.g. results from a previous fit) or strings containing a value and an error formatted like
+134        0.548(23), 500(40) or 0.5(0.4)
+135    silent : bool, optional
+136        If True all output to the console is omitted (default False).
+137    initial_guess : list
+138        can provide an initial guess for the input parameters. Relevant for
+139        non-linear fits with many parameters. In case of correlated fits the guess is used to perform
+140        an uncorrelated fit which then serves as guess for the correlated fit.
+141    method : str, optional
+142        can be used to choose an alternative method for the minimization of chisquare.
+143        The possible methods are the ones which can be used for scipy.optimize.minimize and
+144        migrad of iminuit. If no method is specified, Levenberg–Marquardt is used.
+145        Reliable alternatives are migrad, Powell and Nelder-Mead.
+146    tol: float, optional
+147        can be used (only for combined fits and methods other than Levenberg–Marquardt) to set the tolerance for convergence
+148        to a different value to either speed up convergence at the cost of a larger error on the fitted parameters (and possibly
+149        invalid estimates for parameter uncertainties) or smaller values to get more accurate parameter values
+150        The stopping criterion depends on the method, e.g. migrad: edm_max = 0.002 * tol * errordef (EDM criterion: edm < edm_max)
+151    correlated_fit : bool
+152        If True, use the full inverse covariance matrix in the definition of the chisquare cost function.
+153        For details about how the covariance matrix is estimated see `pyerrors.obs.covariance`.
+154        In practice the correlation matrix is Cholesky decomposed and inverted (instead of the covariance matrix).
+155        This procedure should be numerically more stable as the correlation matrix is typically better conditioned (Jacobi preconditioning).
+156    inv_chol_cov_matrix [array,list], optional
+157        array: shape = (number of y values) X (number of y values)
+158        list:   for an uncombined fit: [""]
+159                for a combined fit: list of keys belonging to the corr_matrix saved in the array, must be the same as the keys of the y dict in alphabetical order
+160        If correlated_fit=True is set as well, can provide an inverse covariance matrix (y errors, dy_f included!) of your own choosing for a correlated fit.
+161        The matrix must be a lower triangular matrix constructed from a Cholesky decomposition: The function invert_corr_cov_cholesky(corr, inverrdiag) can be
+162        used to construct it from a correlation matrix (corr) and the errors dy_f of the data points (inverrdiag = np.diag(1 / np.asarray(dy_f))). For the correct
+163        ordering the correlation matrix (corr) can be sorted via the function sort_corr(corr, kl, yd) where kl is the list of keys and yd the y dict.
+164    expected_chisquare : bool
+165        If True estimates the expected chisquare which is
+166        corrected by effects caused by correlated input data (default False).
+167    resplot : bool
+168        If True, a plot which displays fit, data and residuals is generated (default False).
+169    qqplot : bool
+170        If True, a quantile-quantile plot of the fit result is generated (default False).
+171    num_grad : bool
+172        Use numerical differentation instead of automatic differentiation to perform the error propagation (default False).
+173    n_parms : int, optional
+174        Number of fit parameters. Overrides automatic detection of parameter count.
+175        Useful when autodetection fails. Must match the length of initial_guess or priors (if provided).
+176
+177    Returns
+178    -------
+179    output : Fit_result
+180        Parameters and information on the fitted result.
+181    Examples
+182    ------
+183    >>> # Example of a correlated (correlated_fit = True, inv_chol_cov_matrix handed over) combined fit, based on a randomly generated data set
+184    >>> import numpy as np
+185    >>> from scipy.stats import norm
+186    >>> from scipy.linalg import cholesky
+187    >>> import pyerrors as pe
+188    >>> # generating the random data set
+189    >>> num_samples = 400
+190    >>> N = 3
+191    >>> x = np.arange(N)
+192    >>> x1 = norm.rvs(size=(N, num_samples)) # generate random numbers
+193    >>> x2 = norm.rvs(size=(N, num_samples)) # generate random numbers
+194    >>> r = r1 = r2 = np.zeros((N, N))
+195    >>> y = {}
+196    >>> for i in range(N):
+197    >>>    for j in range(N):
+198    >>>        r[i, j] = np.exp(-0.8 * np.fabs(i - j)) # element in correlation matrix
+199    >>> errl = np.sqrt([3.4, 2.5, 3.6]) # set y errors
+200    >>> for i in range(N):
+201    >>>    for j in range(N):
+202    >>>        r[i, j] *= errl[i] * errl[j] # element in covariance matrix
+203    >>> c = cholesky(r, lower=True)
+204    >>> y = {'a': np.dot(c, x1), 'b': np.dot(c, x2)} # generate y data with the covariance matrix defined
+205    >>> # random data set has been generated, now the dictionaries and the inverse covariance matrix to be handed over are built
+206    >>> x_dict = {}
+207    >>> y_dict = {}
+208    >>> chol_inv_dict = {}
+209    >>> data = []
+210    >>> for key in y.keys():
+211    >>>    x_dict[key] = x
+212    >>>    for i in range(N):
+213    >>>        data.append(pe.Obs([[i + 1 + o for o in y[key][i]]], ['ens'])) # generate y Obs from the y data
+214    >>>    [o.gamma_method() for o in data]
+215    >>>    corr = pe.covariance(data, correlation=True)
+216    >>>    inverrdiag = np.diag(1 / np.asarray([o.dvalue for o in data]))
+217    >>>    chol_inv = pe.obs.invert_corr_cov_cholesky(corr, inverrdiag) # gives form of the inverse covariance matrix needed for the combined correlated fit below
+218    >>> y_dict = {'a': data[:3], 'b': data[3:]}
+219    >>> # common fit parameter p[0] in combined fit
+220    >>> def fit1(p, x):
+221    >>>    return p[0] + p[1] * x
+222    >>> def fit2(p, x):
+223    >>>    return p[0] + p[2] * x
+224    >>> fitf_dict = {'a': fit1, 'b':fit2}
+225    >>> fitp_inv_cov_combined_fit = pe.least_squares(x_dict,y_dict, fitf_dict, correlated_fit = True, inv_chol_cov_matrix = [chol_inv,['a','b']])
+226    Fit with 3 parameters
+227    Method: Levenberg-Marquardt
+228    `ftol` termination condition is satisfied.
+229    chisquare/d.o.f.: 0.5388013574561786 # random
+230    fit parameters [1.11897846 0.96361162 0.92325319] # random
+231
+232    '''
+233    output = Fit_result()
+234
+235    if (isinstance(x, dict) and isinstance(y, dict) and isinstance(func, dict)):
+236        xd = {key: anp.asarray(x[key]) for key in x}
+237        yd = y
+238        funcd = func
+239        output.fit_function = func
+240    elif (isinstance(x, dict) or isinstance(y, dict) or isinstance(func, dict)):
+241        raise TypeError("All arguments have to be dictionaries in order to perform a combined fit.")
+242    else:
+243        x = np.asarray(x)
+244        xd = {"": x}
+245        yd = {"": y}
+246        funcd = {"": func}
+247        output.fit_function = func
+248
+249    if kwargs.get('num_grad') is True:
+250        jacobian = num_jacobian
+251        hessian = num_hessian
+252    else:
+253        jacobian = auto_jacobian
+254        hessian = auto_hessian
 255
-256    if sorted(list(yd.keys())) != key_ls:
-257        raise ValueError('x and y dictionaries do not contain the same keys.')
-258
-259    if sorted(list(funcd.keys())) != key_ls:
-260        raise ValueError('x and func dictionaries do not contain the same keys.')
-261
-262    x_all = np.concatenate([np.array(xd[key]).transpose() for key in key_ls]).transpose()
-263    y_all = np.concatenate([np.array(yd[key]) for key in key_ls])
-264
-265    y_f = [o.value for o in y_all]
-266    dy_f = [o.dvalue for o in y_all]
-267
-268    if len(x_all.shape) > 2:
-269        raise ValueError("Unknown format for x values")
-270
-271    if np.any(np.asarray(dy_f) <= 0.0):
-272        raise Exception("No y errors available, run the gamma method first.")
-273
-274    # number of fit parameters
-275    if 'n_parms' in kwargs:
-276        n_parms = kwargs.get('n_parms')
-277        if not isinstance(n_parms, int):
-278            raise TypeError(
-279                f"'n_parms' must be an integer, got {n_parms!r} "
-280                f"of type {type(n_parms).__name__}."
-281            )
-282        if n_parms <= 0:
-283            raise ValueError(
-284                f"'n_parms' must be a positive integer, got {n_parms}."
-285            )
-286    else:
-287        n_parms_ls = []
-288        for key in key_ls:
-289            if not callable(funcd[key]):
-290                raise TypeError('func (key=' + key + ') is not a function.')
-291            if np.asarray(xd[key]).shape[-1] != len(yd[key]):
-292                raise ValueError('x and y input (key=' + key + ') do not have the same length')
-293            for n_loc in range(100):
-294                try:
-295                    funcd[key](np.arange(n_loc), x_all.T[0])
-296                except TypeError:
-297                    continue
-298                except IndexError:
+256    key_ls = sorted(list(xd.keys()))
+257
+258    if sorted(list(yd.keys())) != key_ls:
+259        raise ValueError('x and y dictionaries do not contain the same keys.')
+260
+261    if sorted(list(funcd.keys())) != key_ls:
+262        raise ValueError('x and func dictionaries do not contain the same keys.')
+263
+264    x_all = np.concatenate([np.array(xd[key]).transpose() for key in key_ls]).transpose()
+265    y_all = np.concatenate([np.array(yd[key]) for key in key_ls])
+266
+267    y_f = [o.value for o in y_all]
+268    dy_f = [o.dvalue for o in y_all]
+269
+270    if len(x_all.shape) > 2:
+271        raise ValueError("Unknown format for x values")
+272
+273    if np.any(np.asarray(dy_f) <= 0.0):
+274        raise Exception("No y errors available, run the gamma method first.")
+275
+276    # number of fit parameters
+277    if 'n_parms' in kwargs:
+278        n_parms = kwargs.get('n_parms')
+279        if not isinstance(n_parms, int):
+280            raise TypeError(
+281                f"'n_parms' must be an integer, got {n_parms!r} "
+282                f"of type {type(n_parms).__name__}."
+283            )
+284        if n_parms <= 0:
+285            raise ValueError(
+286                f"'n_parms' must be a positive integer, got {n_parms}."
+287            )
+288    else:
+289        n_parms_ls = []
+290        for key in key_ls:
+291            if not callable(funcd[key]):
+292                raise TypeError('func (key=' + key + ') is not a function.')
+293            if np.asarray(xd[key]).shape[-1] != len(yd[key]):
+294                raise ValueError('x and y input (key=' + key + ') do not have the same length')
+295            for n_loc in range(100):
+296                try:
+297                    funcd[key](np.arange(n_loc), x_all.T[0])
+298                except TypeError:
 299                    continue
-300                else:
-301                    break
-302            else:
-303                raise RuntimeError("Fit function (key=" + key + ") is not valid.")
-304            n_parms_ls.append(n_loc)
-305
-306        n_parms = max(n_parms_ls)
+300                except IndexError:
+301                    continue
+302                else:
+303                    break
+304            else:
+305                raise RuntimeError("Fit function (key=" + key + ") is not valid.")
+306            n_parms_ls.append(n_loc)
 307
-308    if len(key_ls) > 1:
-309        for key in key_ls:
-310            if np.asarray(yd[key]).shape != funcd[key](np.arange(n_parms), xd[key]).shape:
-311                raise ValueError(f"Fit function {key} returns the wrong shape ({funcd[key](np.arange(n_parms), xd[key]).shape} instead of {np.asarray(yd[key]).shape})\nIf the fit function is just a constant you could try adding x*0 to get the correct shape.")
-312
-313    if not silent:
-314        print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1))
-315
-316    if priors is not None:
-317        if isinstance(priors, (list, np.ndarray)):
-318            if n_parms != len(priors):
-319                raise ValueError("'priors' does not have the correct length.")
-320
-321            loc_priors = []
-322            for i_n, i_prior in enumerate(priors):
-323                loc_priors.append(_construct_prior_obs(i_prior, i_n))
-324
-325            prior_mask = np.arange(len(priors))
-326            output.priors = loc_priors
-327
-328        elif isinstance(priors, dict):
-329            loc_priors = []
-330            prior_mask = []
-331            output.priors = {}
-332            for pos, prior in priors.items():
-333                if isinstance(pos, int):
-334                    prior_mask.append(pos)
-335                else:
-336                    raise TypeError("Prior position needs to be an integer.")
-337                loc_priors.append(_construct_prior_obs(prior, pos))
-338
-339                output.priors[pos] = loc_priors[-1]
-340            if max(prior_mask) >= n_parms:
-341                raise ValueError("Prior position out of range.")
-342        else:
-343            raise TypeError("Unkown type for `priors`.")
-344
-345        p_f = [o.value for o in loc_priors]
-346        dp_f = [o.dvalue for o in loc_priors]
-347        if np.any(np.asarray(dp_f) <= 0.0):
-348            raise Exception("No prior errors available, run the gamma method first.")
-349    else:
-350        p_f = dp_f = np.array([])
-351        prior_mask = []
-352        loc_priors = []
-353
-354    if 'initial_guess' in kwargs:
-355        x0 = kwargs.get('initial_guess')
-356        if len(x0) != n_parms:
-357            raise ValueError('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms))
-358    else:
-359        x0 = [0.1] * n_parms
-360
-361    if priors is None:
-362        def general_chisqfunc_uncorr(p, ivars, pr):
-363            model = anp.concatenate([anp.array(funcd[key](p, xd[key])).reshape(-1) for key in key_ls])
-364            return (ivars - model) / dy_f
-365    else:
-366        def general_chisqfunc_uncorr(p, ivars, pr):
-367            model = anp.concatenate([anp.array(funcd[key](p, xd[key])).reshape(-1) for key in key_ls])
-368            return anp.concatenate(((ivars - model) / dy_f, (p[prior_mask] - pr) / dp_f))
-369
-370    def chisqfunc_uncorr(p):
-371        return anp.sum(general_chisqfunc_uncorr(p, y_f, p_f) ** 2)
-372
-373    if kwargs.get('correlated_fit') is True:
-374        if 'inv_chol_cov_matrix' in kwargs:
-375            chol_inv = kwargs.get('inv_chol_cov_matrix')
-376            if (chol_inv[0].shape[0] != len(dy_f)):
-377                raise TypeError('The number of columns of the inverse covariance matrix handed over needs to be equal to the number of y errors.')
-378            if (chol_inv[0].shape[0] != chol_inv[0].shape[1]):
-379                raise TypeError('The inverse covariance matrix handed over needs to have the same number of rows as columns.')
-380            if (chol_inv[1] != key_ls):
-381                raise ValueError('The keys of inverse covariance matrix are not the same or do not appear in the same order as the x and y values.')
-382            chol_inv = chol_inv[0]
-383            if np.any(np.diag(chol_inv) <= 0) or (not np.all(chol_inv == np.tril(chol_inv))):
-384                raise ValueError('The inverse covariance matrix inv_chol_cov_matrix[0] has to be a lower triangular matrix constructed from a Cholesky decomposition.')
-385        else:
-386            corr = covariance(y_all, correlation=True, **kwargs)
-387            inverrdiag = np.diag(1 / np.asarray(dy_f))
-388            chol_inv = invert_corr_cov_cholesky(corr, inverrdiag)
-389
-390        def general_chisqfunc(p, ivars, pr):
-391            model = anp.concatenate([anp.array(funcd[key](p, xd[key])).reshape(-1) for key in key_ls])
-392            return anp.concatenate((anp.dot(chol_inv, (ivars - model)), (p[prior_mask] - pr) / dp_f))
-393
-394        def chisqfunc(p):
-395            return anp.sum(general_chisqfunc(p, y_f, p_f) ** 2)
-396    else:
-397        general_chisqfunc = general_chisqfunc_uncorr
-398        chisqfunc = chisqfunc_uncorr
-399
-400    output.method = kwargs.get('method', 'Levenberg-Marquardt')
-401    if not silent:
-402        print('Method:', output.method)
-403
-404    if output.method != 'Levenberg-Marquardt':
-405        if output.method == 'migrad':
-406            tolerance = 1e-4  # default value of 1e-1 set by iminuit can be problematic
-407            if 'tol' in kwargs:
-408                tolerance = kwargs.get('tol')
-409            fit_result = iminuit.minimize(chisqfunc_uncorr, x0, tol=tolerance)  # Stopping criterion 0.002 * tol * errordef
-410            if kwargs.get('correlated_fit') is True:
-411                fit_result = iminuit.minimize(chisqfunc, fit_result.x, tol=tolerance)
-412            output.iterations = fit_result.nfev
-413        else:
-414            tolerance = 1e-12
-415            if 'tol' in kwargs:
-416                tolerance = kwargs.get('tol')
-417            fit_result = scipy.optimize.minimize(chisqfunc_uncorr, x0, method=kwargs.get('method'), tol=tolerance)
-418            if kwargs.get('correlated_fit') is True:
-419                fit_result = scipy.optimize.minimize(chisqfunc, fit_result.x, method=kwargs.get('method'), tol=tolerance)
-420            output.iterations = fit_result.nit
-421
-422        chisquare = fit_result.fun
+308        n_parms = max(n_parms_ls)
+309
+310    if len(key_ls) > 1:
+311        for key in key_ls:
+312            if np.asarray(yd[key]).shape != funcd[key](np.arange(n_parms), xd[key]).shape:
+313                raise ValueError(f"Fit function {key} returns the wrong shape ({funcd[key](np.arange(n_parms), xd[key]).shape} instead of {np.asarray(yd[key]).shape})\nIf the fit function is just a constant you could try adding x*0 to get the correct shape.")
+314
+315    if not silent:
+316        print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1))
+317
+318    if priors is not None:
+319        if isinstance(priors, (list, np.ndarray)):
+320            if n_parms != len(priors):
+321                raise ValueError("'priors' does not have the correct length.")
+322
+323            loc_priors = []
+324            for i_n, i_prior in enumerate(priors):
+325                loc_priors.append(_construct_prior_obs(i_prior, i_n))
+326
+327            prior_mask = np.arange(len(priors))
+328            output.priors = loc_priors
+329
+330        elif isinstance(priors, dict):
+331            loc_priors = []
+332            prior_mask = []
+333            output.priors = {}
+334            for pos, prior in priors.items():
+335                if isinstance(pos, int):
+336                    prior_mask.append(pos)
+337                else:
+338                    raise TypeError("Prior position needs to be an integer.")
+339                loc_priors.append(_construct_prior_obs(prior, pos))
+340
+341                output.priors[pos] = loc_priors[-1]
+342            if max(prior_mask) >= n_parms:
+343                raise ValueError("Prior position out of range.")
+344        else:
+345            raise TypeError("Unkown type for `priors`.")
+346
+347        p_f = [o.value for o in loc_priors]
+348        dp_f = [o.dvalue for o in loc_priors]
+349        if np.any(np.asarray(dp_f) <= 0.0):
+350            raise Exception("No prior errors available, run the gamma method first.")
+351    else:
+352        p_f = dp_f = np.array([])
+353        prior_mask = []
+354        loc_priors = []
+355
+356    if 'initial_guess' in kwargs:
+357        x0 = kwargs.get('initial_guess')
+358        if len(x0) != n_parms:
+359            raise ValueError(f'Initial guess does not have the correct length: {len(x0)} vs. {n_parms}')
+360    else:
+361        x0 = [0.1] * n_parms
+362
+363    if priors is None:
+364        def general_chisqfunc_uncorr(p, ivars, pr):
+365            model = anp.concatenate([anp.array(funcd[key](p, xd[key])).reshape(-1) for key in key_ls])
+366            return (ivars - model) / dy_f
+367    else:
+368        def general_chisqfunc_uncorr(p, ivars, pr):
+369            model = anp.concatenate([anp.array(funcd[key](p, xd[key])).reshape(-1) for key in key_ls])
+370            return anp.concatenate(((ivars - model) / dy_f, (p[prior_mask] - pr) / dp_f))
+371
+372    def chisqfunc_uncorr(p):
+373        return anp.sum(general_chisqfunc_uncorr(p, y_f, p_f) ** 2)
+374
+375    if kwargs.get('correlated_fit') is True:
+376        if 'inv_chol_cov_matrix' in kwargs:
+377            chol_inv = kwargs.get('inv_chol_cov_matrix')
+378            if (chol_inv[0].shape[0] != len(dy_f)):
+379                raise TypeError('The number of columns of the inverse covariance matrix handed over needs to be equal to the number of y errors.')
+380            if (chol_inv[0].shape[0] != chol_inv[0].shape[1]):
+381                raise TypeError('The inverse covariance matrix handed over needs to have the same number of rows as columns.')
+382            if (chol_inv[1] != key_ls):
+383                raise ValueError('The keys of inverse covariance matrix are not the same or do not appear in the same order as the x and y values.')
+384            chol_inv = chol_inv[0]
+385            if np.any(np.diag(chol_inv) <= 0) or (not np.all(chol_inv == np.tril(chol_inv))):
+386                raise ValueError('The inverse covariance matrix inv_chol_cov_matrix[0] has to be a lower triangular matrix constructed from a Cholesky decomposition.')
+387        else:
+388            corr = covariance(y_all, correlation=True, **kwargs)
+389            inverrdiag = np.diag(1 / np.asarray(dy_f))
+390            chol_inv = invert_corr_cov_cholesky(corr, inverrdiag)
+391
+392        def general_chisqfunc(p, ivars, pr):
+393            model = anp.concatenate([anp.array(funcd[key](p, xd[key])).reshape(-1) for key in key_ls])
+394            return anp.concatenate((anp.dot(chol_inv, (ivars - model)), (p[prior_mask] - pr) / dp_f))
+395
+396        def chisqfunc(p):
+397            return anp.sum(general_chisqfunc(p, y_f, p_f) ** 2)
+398    else:
+399        general_chisqfunc = general_chisqfunc_uncorr
+400        chisqfunc = chisqfunc_uncorr
+401
+402    output.method = kwargs.get('method', 'Levenberg-Marquardt')
+403    if not silent:
+404        print('Method:', output.method)
+405
+406    if output.method != 'Levenberg-Marquardt':
+407        if output.method == 'migrad':
+408            tolerance = 1e-4  # default value of 1e-1 set by iminuit can be problematic
+409            if 'tol' in kwargs:
+410                tolerance = kwargs.get('tol')
+411            fit_result = iminuit.minimize(chisqfunc_uncorr, x0, tol=tolerance)  # Stopping criterion 0.002 * tol * errordef
+412            if kwargs.get('correlated_fit') is True:
+413                fit_result = iminuit.minimize(chisqfunc, fit_result.x, tol=tolerance)
+414            output.iterations = fit_result.nfev
+415        else:
+416            tolerance = 1e-12
+417            if 'tol' in kwargs:
+418                tolerance = kwargs.get('tol')
+419            fit_result = scipy.optimize.minimize(chisqfunc_uncorr, x0, method=kwargs.get('method'), tol=tolerance)
+420            if kwargs.get('correlated_fit') is True:
+421                fit_result = scipy.optimize.minimize(chisqfunc, fit_result.x, method=kwargs.get('method'), tol=tolerance)
+422            output.iterations = fit_result.nit
 423
-424    else:
-425        if 'tol' in kwargs:
-426            print('tol cannot be set for Levenberg-Marquardt')
-427
-428        def chisqfunc_residuals_uncorr(p):
-429            return general_chisqfunc_uncorr(p, y_f, p_f)
-430
-431        fit_result = scipy.optimize.least_squares(chisqfunc_residuals_uncorr, x0, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15)
-432        if kwargs.get('correlated_fit') is True:
-433            def chisqfunc_residuals(p):
-434                return general_chisqfunc(p, y_f, p_f)
-435
-436            fit_result = scipy.optimize.least_squares(chisqfunc_residuals, fit_result.x, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15)
+424        chisquare = fit_result.fun
+425
+426    else:
+427        if 'tol' in kwargs:
+428            print('tol cannot be set for Levenberg-Marquardt')
+429
+430        def chisqfunc_residuals_uncorr(p):
+431            return general_chisqfunc_uncorr(p, y_f, p_f)
+432
+433        fit_result = scipy.optimize.least_squares(chisqfunc_residuals_uncorr, x0, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15)
+434        if kwargs.get('correlated_fit') is True:
+435            def chisqfunc_residuals(p):
+436                return general_chisqfunc(p, y_f, p_f)
 437
-438        chisquare = np.sum(fit_result.fun ** 2)
-439        assert np.isclose(chisquare, chisqfunc(fit_result.x), atol=1e-14)
-440
-441        output.iterations = fit_result.nfev
+438            fit_result = scipy.optimize.least_squares(chisqfunc_residuals, fit_result.x, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15)
+439
+440        chisquare = np.sum(fit_result.fun ** 2)
+441        assert np.isclose(chisquare, chisqfunc(fit_result.x), atol=1e-14)
 442
-443    if not fit_result.success:
-444        raise Exception('The minimization procedure did not converge.')
-445
-446    output.chisquare = chisquare
-447    output.dof = y_all.shape[-1] - n_parms + len(loc_priors)
-448    output.p_value = 1 - scipy.stats.chi2.cdf(output.chisquare, output.dof)
-449    if output.dof > 0:
-450        output.chisquare_by_dof = output.chisquare / output.dof
-451    else:
-452        output.chisquare_by_dof = float('nan')
-453
-454    output.message = fit_result.message
-455    if not silent:
-456        print(fit_result.message)
-457        print('chisquare/d.o.f.:', output.chisquare_by_dof)
-458        print('fit parameters', fit_result.x)
-459
-460    def prepare_hat_matrix():
-461        hat_vector = []
-462        for key in key_ls:
-463            if (len(xd[key]) != 0):
-464                hat_vector.append(jacobian(funcd[key])(fit_result.x, xd[key]))
-465        hat_vector = [item for sublist in hat_vector for item in sublist]
-466        return hat_vector
-467
-468    if kwargs.get('expected_chisquare') is True:
-469        if kwargs.get('correlated_fit') is not True:
-470            W = np.diag(1 / np.asarray(dy_f))
-471            cov = covariance(y_all)
-472            hat_vector = prepare_hat_matrix()
-473            A = W @ hat_vector
-474            P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T
-475            expected_chisquare = np.trace((np.identity(y_all.shape[-1]) - P_phi) @ W @ cov @ W) + len(loc_priors)
-476            output.chisquare_by_expected_chisquare = output.chisquare / expected_chisquare
-477            if not silent:
-478                print('chisquare/expected_chisquare:', output.chisquare_by_expected_chisquare)
-479
-480    fitp = fit_result.x
+443        output.iterations = fit_result.nfev
+444
+445    if not fit_result.success:
+446        raise Exception('The minimization procedure did not converge.')
+447
+448    output.chisquare = chisquare
+449    output.dof = y_all.shape[-1] - n_parms + len(loc_priors)
+450    output.p_value = 1 - scipy.stats.chi2.cdf(output.chisquare, output.dof)
+451    if output.dof > 0:
+452        output.chisquare_by_dof = output.chisquare / output.dof
+453    else:
+454        output.chisquare_by_dof = float('nan')
+455
+456    output.message = fit_result.message
+457    if not silent:
+458        print(fit_result.message)
+459        print('chisquare/d.o.f.:', output.chisquare_by_dof)
+460        print('fit parameters', fit_result.x)
+461
+462    def prepare_hat_matrix():
+463        hat_vector = []
+464        for key in key_ls:
+465            if (len(xd[key]) != 0):
+466                hat_vector.append(jacobian(funcd[key])(fit_result.x, xd[key]))
+467        hat_vector = [item for sublist in hat_vector for item in sublist]
+468        return hat_vector
+469
+470    if kwargs.get('expected_chisquare') is True:
+471        if kwargs.get('correlated_fit') is not True:
+472            W = np.diag(1 / np.asarray(dy_f))
+473            cov = covariance(y_all)
+474            hat_vector = prepare_hat_matrix()
+475            A = W @ hat_vector
+476            P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T
+477            expected_chisquare = np.trace((np.identity(y_all.shape[-1]) - P_phi) @ W @ cov @ W) + len(loc_priors)
+478            output.chisquare_by_expected_chisquare = output.chisquare / expected_chisquare
+479            if not silent:
+480                print('chisquare/expected_chisquare:', output.chisquare_by_expected_chisquare)
 481
-482    try:
-483        hess = hessian(chisqfunc)(fitp)
-484    except (TypeError, ValueError, np.linalg.LinAlgError):
-485        raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None
-486
-487    len_y = len(y_f)
+482    fitp = fit_result.x
+483
+484    try:
+485        hess = hessian(chisqfunc)(fitp)
+486    except (TypeError, ValueError, np.linalg.LinAlgError):
+487        raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None
 488
-489    def chisqfunc_compact(d):
-490        return anp.sum(general_chisqfunc(d[:n_parms], d[n_parms: n_parms + len_y], d[n_parms + len_y:]) ** 2)
-491
-492    jac_jac_y = hessian(chisqfunc_compact)(np.concatenate((fitp, y_f, p_f)))
+489    len_y = len(y_f)
+490
+491    def chisqfunc_compact(d):
+492        return anp.sum(general_chisqfunc(d[:n_parms], d[n_parms: n_parms + len_y], d[n_parms + len_y:]) ** 2)
 493
-494    # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv
-495    try:
-496        deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms, n_parms:])
-497    except np.linalg.LinAlgError:
-498        raise Exception("Cannot invert hessian matrix.")
-499
-500    result = []
-501    for i in range(n_parms):
-502        result.append(derived_observable(lambda x_all, **kwargs: (x_all[0] + np.finfo(np.float64).eps) / (y_all[0].value + np.finfo(np.float64).eps) * fitp[i], list(y_all) + loc_priors, man_grad=list(deriv_y[i])))
-503
-504    output.fit_parameters = result
+494    jac_jac_y = hessian(chisqfunc_compact)(np.concatenate((fitp, y_f, p_f)))
+495
+496    # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv
+497    try:
+498        deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms, n_parms:])
+499    except np.linalg.LinAlgError as err:
+500        raise Exception("Cannot invert hessian matrix.") from err
+501
+502    result = []
+503    for i in range(n_parms):
+504        result.append(derived_observable(lambda x_all, i=i, **kwargs: (x_all[0] + np.finfo(np.float64).eps) / (y_all[0].value + np.finfo(np.float64).eps) * fitp[i], list(y_all) + loc_priors, man_grad=list(deriv_y[i])))
 505
-506    # Hotelling t-squared p-value for correlated fits.
-507    if kwargs.get('correlated_fit') is True:
-508        n_cov = np.min(np.vectorize(lambda x_all: x_all.N)(y_all))
-509        output.t2_p_value = 1 - scipy.stats.f.cdf((n_cov - output.dof) / (output.dof * (n_cov - 1)) * output.chisquare,
-510                                                  output.dof, n_cov - output.dof)
-511
-512    if kwargs.get('resplot') is True:
-513        for key in key_ls:
-514            residual_plot(xd[key], yd[key], funcd[key], result, title=key)
-515
-516    if kwargs.get('qqplot') is True:
-517        for key in key_ls:
-518            qqplot(xd[key], yd[key], funcd[key], result, title=key)
-519
-520    return output
+506    output.fit_parameters = result
+507
+508    # Hotelling t-squared p-value for correlated fits.
+509    if kwargs.get('correlated_fit') is True:
+510        n_cov = np.min(np.vectorize(lambda x_all: x_all.N)(y_all))
+511        output.t2_p_value = 1 - scipy.stats.f.cdf((n_cov - output.dof) / (output.dof * (n_cov - 1)) * output.chisquare,
+512                                                  output.dof, n_cov - output.dof)
+513
+514    if kwargs.get('resplot') is True:
+515        for key in key_ls:
+516            residual_plot(xd[key], yd[key], funcd[key], result, title=key)
+517
+518    if kwargs.get('qqplot') is True:
+519        for key in key_ls:
+520            qqplot(xd[key], yd[key], funcd[key], result, title=key)
 521
-522
-523def total_least_squares(x, y, func, silent=False, **kwargs):
-524    r'''Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters.
-525
-526    Parameters
-527    ----------
-528    x : list
-529        list of Obs, or a tuple of lists of Obs
-530    y : list
-531        list of Obs. The dvalues of the Obs are used as x- and yerror for the fit.
-532    func : object
-533        func has to be of the form
-534
-535        ```python
-536        import autograd.numpy as anp
-537
-538        def func(a, x):
-539            return a[0] + a[1] * x + a[2] * anp.sinh(x)
-540        ```
-541
-542        For multiple x values func can be of the form
+522    return output
+523
+524
+525def total_least_squares(x, y, func, silent=False, **kwargs):
+526    r'''Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters.
+527
+528    Parameters
+529    ----------
+530    x : list
+531        list of Obs, or a tuple of lists of Obs
+532    y : list
+533        list of Obs. The dvalues of the Obs are used as x- and yerror for the fit.
+534    func : object
+535        func has to be of the form
+536
+537        ```python
+538        import autograd.numpy as anp
+539
+540        def func(a, x):
+541            return a[0] + a[1] * x + a[2] * anp.sinh(x)
+542        ```
 543
-544        ```python
-545        def func(a, x):
-546            (x1, x2) = x
-547            return a[0] * x1 ** 2 + a[1] * x2
-548        ```
-549
-550        It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation
-551        will not work.
-552    silent : bool, optional
-553        If True all output to the console is omitted (default False).
-554    initial_guess : list
-555        can provide an initial guess for the input parameters. Relevant for non-linear
-556        fits with many parameters.
-557    expected_chisquare : bool
-558        If True prints the expected chisquare which is
-559        corrected by effects caused by correlated input data.
-560        This can take a while as the full correlation matrix
-561        has to be calculated (default False).
-562    num_grad : bool
-563        Use numerical differentiation instead of automatic differentiation to perform the error propagation (default False).
-564    n_parms : int, optional
-565        Number of fit parameters. Overrides automatic detection of parameter count.
-566        Useful when autodetection fails. Must match the length of initial_guess (if provided).
-567
-568    Notes
-569    -----
-570    Based on the odrpack orthogonal distance regression library.
-571
-572    Returns
-573    -------
-574    output : Fit_result
-575        Parameters and information on the fitted result.
-576    '''
-577
-578    output = Fit_result()
+544        For multiple x values func can be of the form
+545
+546        ```python
+547        def func(a, x):
+548            (x1, x2) = x
+549            return a[0] * x1 ** 2 + a[1] * x2
+550        ```
+551
+552        It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation
+553        will not work.
+554    silent : bool, optional
+555        If True all output to the console is omitted (default False).
+556    initial_guess : list
+557        can provide an initial guess for the input parameters. Relevant for non-linear
+558        fits with many parameters.
+559    expected_chisquare : bool
+560        If True prints the expected chisquare which is
+561        corrected by effects caused by correlated input data.
+562        This can take a while as the full correlation matrix
+563        has to be calculated (default False).
+564    num_grad : bool
+565        Use numerical differentiation instead of automatic differentiation to perform the error propagation (default False).
+566    n_parms : int, optional
+567        Number of fit parameters. Overrides automatic detection of parameter count.
+568        Useful when autodetection fails. Must match the length of initial_guess (if provided).
+569
+570    Notes
+571    -----
+572    Based on the odrpack orthogonal distance regression library.
+573
+574    Returns
+575    -------
+576    output : Fit_result
+577        Parameters and information on the fitted result.
+578    '''
 579
-580    output.fit_function = func
+580    output = Fit_result()
 581
-582    x = np.array(x)
+582    output.fit_function = func
 583
-584    x_shape = x.shape
+584    x = np.array(x)
 585
-586    if kwargs.get('num_grad') is True:
-587        jacobian = num_jacobian
-588        hessian = num_hessian
-589    else:
-590        jacobian = auto_jacobian
-591        hessian = auto_hessian
-592
-593    if not callable(func):
-594        raise TypeError('func has to be a function.')
-595
-596    if 'n_parms' in kwargs:
-597        n_parms = kwargs.get('n_parms')
-598        if not isinstance(n_parms, int):
-599            raise TypeError(
-600                f"'n_parms' must be an integer, got {n_parms!r} "
-601                f"of type {type(n_parms).__name__}."
-602            )
-603        if n_parms <= 0:
-604            raise ValueError(
-605                f"'n_parms' must be a positive integer, got {n_parms}."
-606            )
-607    else:
-608        for i in range(100):
-609            try:
-610                func(np.arange(i), x.T[0])
-611            except TypeError:
-612                continue
-613            except IndexError:
+586    x_shape = x.shape
+587
+588    if kwargs.get('num_grad') is True:
+589        jacobian = num_jacobian
+590        hessian = num_hessian
+591    else:
+592        jacobian = auto_jacobian
+593        hessian = auto_hessian
+594
+595    if not callable(func):
+596        raise TypeError('func has to be a function.')
+597
+598    if 'n_parms' in kwargs:
+599        n_parms = kwargs.get('n_parms')
+600        if not isinstance(n_parms, int):
+601            raise TypeError(
+602                f"'n_parms' must be an integer, got {n_parms!r} "
+603                f"of type {type(n_parms).__name__}."
+604            )
+605        if n_parms <= 0:
+606            raise ValueError(
+607                f"'n_parms' must be a positive integer, got {n_parms}."
+608            )
+609    else:
+610        for i in range(100):
+611            try:
+612                func(np.arange(i), x.T[0])
+613            except TypeError:
 614                continue
-615            else:
-616                break
-617        else:
-618            raise RuntimeError("Fit function is not valid.")
-619
-620        n_parms = i
+615            except IndexError:
+616                continue
+617            else:
+618                break
+619        else:
+620            raise RuntimeError("Fit function is not valid.")
 621
-622    if not silent:
-623        print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1))
-624
-625    x_f = np.vectorize(lambda o: o.value)(x)
-626    dx_f = np.vectorize(lambda o: o.dvalue)(x)
-627    y_f = np.array([o.value for o in y])
-628    dy_f = np.array([o.dvalue for o in y])
-629
-630    if np.any(np.asarray(dx_f) <= 0.0):
-631        raise Exception('No x errors available, run the gamma method first.')
-632
-633    if np.any(np.asarray(dy_f) <= 0.0):
-634        raise Exception('No y errors available, run the gamma method first.')
-635
-636    if 'initial_guess' in kwargs:
-637        x0 = np.asarray(kwargs.get('initial_guess'), dtype=np.float64)
-638        if len(x0) != n_parms:
-639            raise Exception('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms))
-640    else:
-641        x0 = np.ones(n_parms, dtype=np.float64)
-642
-643    # odrpack expects f(x, beta), but pyerrors convention is f(beta, x)
-644    def wrapped_func(x, beta):
-645        return func(beta, x)
-646
-647    out = odr_fit(
-648        wrapped_func,
-649        np.asarray(x_f, dtype=np.float64),
-650        np.asarray(y_f, dtype=np.float64),
-651        beta0=x0,
-652        weight_x=1.0 / np.asarray(dx_f, dtype=np.float64) ** 2,
-653        weight_y=1.0 / np.asarray(dy_f, dtype=np.float64) ** 2,
-654        partol=np.finfo(np.float64).eps,
-655        task='explicit-ODR',
-656        diff_scheme='central'
-657    )
-658
-659    output.residual_variance = out.res_var
+622        n_parms = i
+623
+624    if not silent:
+625        print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1))
+626
+627    x_f = np.vectorize(lambda o: o.value)(x)
+628    dx_f = np.vectorize(lambda o: o.dvalue)(x)
+629    y_f = np.array([o.value for o in y])
+630    dy_f = np.array([o.dvalue for o in y])
+631
+632    if np.any(np.asarray(dx_f) <= 0.0):
+633        raise Exception('No x errors available, run the gamma method first.')
+634
+635    if np.any(np.asarray(dy_f) <= 0.0):
+636        raise Exception('No y errors available, run the gamma method first.')
+637
+638    if 'initial_guess' in kwargs:
+639        x0 = np.asarray(kwargs.get('initial_guess'), dtype=np.float64)
+640        if len(x0) != n_parms:
+641            raise Exception(f'Initial guess does not have the correct length: {len(x0)} vs. {n_parms}')
+642    else:
+643        x0 = np.ones(n_parms, dtype=np.float64)
+644
+645    # odrpack expects f(x, beta), but pyerrors convention is f(beta, x)
+646    def wrapped_func(x, beta):
+647        return func(beta, x)
+648
+649    out = odr_fit(
+650        wrapped_func,
+651        np.asarray(x_f, dtype=np.float64),
+652        np.asarray(y_f, dtype=np.float64),
+653        beta0=x0,
+654        weight_x=1.0 / np.asarray(dx_f, dtype=np.float64) ** 2,
+655        weight_y=1.0 / np.asarray(dy_f, dtype=np.float64) ** 2,
+656        partol=np.finfo(np.float64).eps,
+657        task='explicit-ODR',
+658        diff_scheme='central'
+659    )
 660
-661    output.method = 'ODR'
+661    output.residual_variance = out.res_var
 662
-663    output.message = out.stopreason
+663    output.method = 'ODR'
 664
-665    output.xplus = out.xplusd
+665    output.message = out.stopreason
 666
-667    if not silent:
-668        print('Method: ODR')
-669        print(out.stopreason)
-670        print('Residual variance:', output.residual_variance)
-671
-672    if not out.success:
-673        # ODRPACK95 info code structure (see User Guide §4):
-674        #   info % 10        -> convergence: 1=sum-of-sq, 2=param, 3=both
-675        #   info // 10 % 10  -> 1 = problem not full rank at solution
-676        convergence_status = out.info % 10
-677        rank_deficient = (out.info // 10 % 10) == 1
-678
-679        if convergence_status in [1, 2, 3] and rank_deficient:
-680            warnings.warn(
-681                f"ODR fit is rank deficient (irank={out.irank}, inv_condnum={out.inv_condnum:.2e}). "
-682                "This may indicate a vanishing chi-squared (n_obs == n_parms). "
-683                "Results may be unreliable.",
-684                RuntimeWarning
-685            )
-686        else:
-687            raise Exception('The minimization procedure did not converge.')
-688
-689    m = x_f.size
+667    output.xplus = out.xplusd
+668
+669    if not silent:
+670        print('Method: ODR')
+671        print(out.stopreason)
+672        print('Residual variance:', output.residual_variance)
+673
+674    if not out.success:
+675        # ODRPACK95 info code structure (see User Guide §4):
+676        #   info % 10        -> convergence: 1=sum-of-sq, 2=param, 3=both
+677        #   info // 10 % 10  -> 1 = problem not full rank at solution
+678        convergence_status = out.info % 10
+679        rank_deficient = (out.info // 10 % 10) == 1
+680
+681        if convergence_status in [1, 2, 3] and rank_deficient:
+682            warnings.warn(
+683                f"ODR fit is rank deficient (irank={out.irank}, inv_condnum={out.inv_condnum:.2e}). "
+684                "This may indicate a vanishing chi-squared (n_obs == n_parms). "
+685                "Results may be unreliable.",
+686                RuntimeWarning, stacklevel=2
+687            )
+688        else:
+689            raise Exception('The minimization procedure did not converge.')
 690
-691    def odr_chisquare(p):
-692        model = func(p[:n_parms], p[n_parms:].reshape(x_shape))
-693        chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((x_f - p[n_parms:].reshape(x_shape)) / dx_f) ** 2)
-694        return chisq
-695
-696    if kwargs.get('expected_chisquare') is True:
-697        W = np.diag(1 / np.asarray(np.concatenate((dy_f.ravel(), dx_f.ravel()))))
-698
-699        if kwargs.get('covariance') is not None:
-700            cov = kwargs.get('covariance')
-701        else:
-702            cov = covariance(np.concatenate((y, x.ravel())))
-703
-704        number_of_x_parameters = int(m / x_f.shape[-1])
+691    m = x_f.size
+692
+693    def odr_chisquare(p):
+694        model = func(p[:n_parms], p[n_parms:].reshape(x_shape))
+695        chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((x_f - p[n_parms:].reshape(x_shape)) / dx_f) ** 2)
+696        return chisq
+697
+698    if kwargs.get('expected_chisquare') is True:
+699        W = np.diag(1 / np.asarray(np.concatenate((dy_f.ravel(), dx_f.ravel()))))
+700
+701        if kwargs.get('covariance') is not None:
+702            cov = kwargs.get('covariance')
+703        else:
+704            cov = covariance(np.concatenate((y, x.ravel())))
 705
-706        old_jac = jacobian(func)(out.beta, out.xplusd)
-707        fused_row1 = np.concatenate((old_jac, np.concatenate((number_of_x_parameters * [np.zeros(old_jac.shape)]), axis=0)))
-708        fused_row2 = np.concatenate((jacobian(lambda x, y: func(y, x))(out.xplusd, out.beta).reshape(x_f.shape[-1], x_f.shape[-1] * number_of_x_parameters), np.identity(number_of_x_parameters * old_jac.shape[0])))
-709        new_jac = np.concatenate((fused_row1, fused_row2), axis=1)
-710
-711        A = W @ new_jac
-712        P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T
-713        expected_chisquare = np.trace((np.identity(P_phi.shape[0]) - P_phi) @ W @ cov @ W)
-714        if expected_chisquare <= 0.0:
-715            warnings.warn("Negative expected_chisquare.", RuntimeWarning)
-716            expected_chisquare = np.abs(expected_chisquare)
-717        output.chisquare_by_expected_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplusd.ravel()))) / expected_chisquare
-718        if not silent:
-719            print('chisquare/expected_chisquare:',
-720                  output.chisquare_by_expected_chisquare)
-721
-722    fitp = out.beta
-723    try:
-724        hess = hessian(odr_chisquare)(np.concatenate((fitp, out.xplusd.ravel())))
-725    except (TypeError, ValueError, np.linalg.LinAlgError):
-726        raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None
-727
-728    def odr_chisquare_compact_x(d):
-729        model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape))
-730        chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((d[n_parms + m:].reshape(x_shape) - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2)
-731        return chisq
-732
-733    jac_jac_x = hessian(odr_chisquare_compact_x)(np.concatenate((fitp, out.xplusd.ravel(), x_f.ravel())))
+706        number_of_x_parameters = int(m / x_f.shape[-1])
+707
+708        old_jac = jacobian(func)(out.beta, out.xplusd)
+709        fused_row1 = np.concatenate((old_jac, np.concatenate((number_of_x_parameters * [np.zeros(old_jac.shape)]), axis=0)))
+710        fused_row2 = np.concatenate((jacobian(lambda x, y: func(y, x))(out.xplusd, out.beta).reshape(x_f.shape[-1], x_f.shape[-1] * number_of_x_parameters), np.identity(number_of_x_parameters * old_jac.shape[0])))
+711        new_jac = np.concatenate((fused_row1, fused_row2), axis=1)
+712
+713        A = W @ new_jac
+714        P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T
+715        expected_chisquare = np.trace((np.identity(P_phi.shape[0]) - P_phi) @ W @ cov @ W)
+716        if expected_chisquare <= 0.0:
+717            warnings.warn("Negative expected_chisquare.", RuntimeWarning, stacklevel=2)
+718            expected_chisquare = np.abs(expected_chisquare)
+719        output.chisquare_by_expected_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplusd.ravel()))) / expected_chisquare
+720        if not silent:
+721            print('chisquare/expected_chisquare:',
+722                  output.chisquare_by_expected_chisquare)
+723
+724    fitp = out.beta
+725    try:
+726        hess = hessian(odr_chisquare)(np.concatenate((fitp, out.xplusd.ravel())))
+727    except (TypeError, ValueError, np.linalg.LinAlgError):
+728        raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None
+729
+730    def odr_chisquare_compact_x(d):
+731        model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape))
+732        chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((d[n_parms + m:].reshape(x_shape) - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2)
+733        return chisq
 734
-735    # Compute hess^{-1} @ jac_jac_x[:n_parms + m, n_parms + m:] using LAPACK dgesv
-736    try:
-737        deriv_x = -scipy.linalg.solve(hess, jac_jac_x[:n_parms + m, n_parms + m:])
-738    except np.linalg.LinAlgError:
-739        raise Exception("Cannot invert hessian matrix.")
-740
-741    def odr_chisquare_compact_y(d):
-742        model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape))
-743        chisq = anp.sum(((d[n_parms + m:] - model) / dy_f) ** 2) + anp.sum(((x_f - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2)
-744        return chisq
-745
-746    jac_jac_y = hessian(odr_chisquare_compact_y)(np.concatenate((fitp, out.xplusd.ravel(), y_f)))
+735    jac_jac_x = hessian(odr_chisquare_compact_x)(np.concatenate((fitp, out.xplusd.ravel(), x_f.ravel())))
+736
+737    # Compute hess^{-1} @ jac_jac_x[:n_parms + m, n_parms + m:] using LAPACK dgesv
+738    try:
+739        deriv_x = -scipy.linalg.solve(hess, jac_jac_x[:n_parms + m, n_parms + m:])
+740    except np.linalg.LinAlgError as err:
+741        raise Exception("Cannot invert hessian matrix.") from err
+742
+743    def odr_chisquare_compact_y(d):
+744        model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape))
+745        chisq = anp.sum(((d[n_parms + m:] - model) / dy_f) ** 2) + anp.sum(((x_f - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2)
+746        return chisq
 747
-748    # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv
-749    try:
-750        deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms + m, n_parms + m:])
-751    except np.linalg.LinAlgError:
-752        raise Exception("Cannot invert hessian matrix.")
-753
-754    result = []
-755    for i in range(n_parms):
-756        result.append(derived_observable(lambda my_var, **kwargs: (my_var[0] + np.finfo(np.float64).eps) / (x.ravel()[0].value + np.finfo(np.float64).eps) * out.beta[i], list(x.ravel()) + list(y), man_grad=list(deriv_x[i]) + list(deriv_y[i])))
-757
-758    output.fit_parameters = result
+748    jac_jac_y = hessian(odr_chisquare_compact_y)(np.concatenate((fitp, out.xplusd.ravel(), y_f)))
+749
+750    # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv
+751    try:
+752        deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms + m, n_parms + m:])
+753    except np.linalg.LinAlgError as err:
+754        raise Exception("Cannot invert hessian matrix.") from err
+755
+756    result = []
+757    for i in range(n_parms):
+758        result.append(derived_observable(lambda my_var, i=i, **kwargs: (my_var[0] + np.finfo(np.float64).eps) / (x.ravel()[0].value + np.finfo(np.float64).eps) * out.beta[i], list(x.ravel()) + list(y), man_grad=list(deriv_x[i]) + list(deriv_y[i])))
 759
-760    output.odr_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplusd.ravel())))
-761    output.dof = x.shape[-1] - n_parms
-762    output.p_value = 1 - scipy.stats.chi2.cdf(output.odr_chisquare, output.dof)
-763
-764    return output
+760    output.fit_parameters = result
+761
+762    output.odr_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplusd.ravel())))
+763    output.dof = x.shape[-1] - n_parms
+764    output.p_value = 1 - scipy.stats.chi2.cdf(output.odr_chisquare, output.dof)
 765
-766
-767def fit_lin(x, y, **kwargs):
-768    """Performs a linear fit to y = n + m * x and returns two Obs n, m.
-769
-770    Parameters
-771    ----------
-772    x : list
-773        Can either be a list of floats in which case no xerror is assumed, or
-774        a list of Obs, where the dvalues of the Obs are used as xerror for the fit.
-775    y : list
-776        List of Obs, the dvalues of the Obs are used as yerror for the fit.
-777
-778    Returns
-779    -------
-780    fit_parameters : list[Obs]
-781        LIist of fitted observables.
-782    """
-783
-784    def f(a, x):
-785        y = a[0] + a[1] * x
-786        return y
-787
-788    if all(isinstance(n, Obs) for n in x):
-789        out = total_least_squares(x, y, f, **kwargs)
-790        return out.fit_parameters
-791    elif all(isinstance(n, float) or isinstance(n, int) for n in x) or isinstance(x, np.ndarray):
-792        out = least_squares(x, y, f, **kwargs)
-793        return out.fit_parameters
-794    else:
-795        raise TypeError('Unsupported types for x')
-796
-797
-798def qqplot(x, o_y, func, p, title=""):
-799    """Generates a quantile-quantile plot of the fit result which can be used to
-800       check if the residuals of the fit are gaussian distributed.
-801
-802    Returns
-803    -------
-804    None
-805    """
-806
-807    residuals = []
-808    for i_x, i_y in zip(x, o_y):
-809        residuals.append((i_y - func(p, i_x)) / i_y.dvalue)
-810    residuals = sorted(residuals)
-811    my_y = [o.value for o in residuals]
-812    probplot = scipy.stats.probplot(my_y)
-813    my_x = probplot[0][0]
-814    plt.figure(figsize=(8, 8 / 1.618))
-815    plt.errorbar(my_x, my_y, fmt='o')
-816    fit_start = my_x[0]
-817    fit_stop = my_x[-1]
-818    samples = np.arange(fit_start, fit_stop, 0.01)
-819    plt.plot(samples, samples, 'k--', zorder=11, label='Standard normal distribution')
-820    plt.plot(samples, probplot[1][0] * samples + probplot[1][1], zorder=10, label='Least squares fit, r=' + str(np.around(probplot[1][2], 3)), marker='', ls='-')
-821
-822    plt.xlabel('Theoretical quantiles')
-823    plt.ylabel('Ordered Values')
-824    plt.legend(title=title)
-825    plt.draw()
-826
-827
-828def residual_plot(x, y, func, fit_res, title=""):
-829    """Generates a plot which compares the fit to the data and displays the corresponding residuals
-830
-831    For uncorrelated data the residuals are expected to be distributed ~N(0,1).
+766    return output
+767
+768
+769def fit_lin(x, y, **kwargs):
+770    """Performs a linear fit to y = n + m * x and returns two Obs n, m.
+771
+772    Parameters
+773    ----------
+774    x : list
+775        Can either be a list of floats in which case no xerror is assumed, or
+776        a list of Obs, where the dvalues of the Obs are used as xerror for the fit.
+777    y : list
+778        List of Obs, the dvalues of the Obs are used as yerror for the fit.
+779
+780    Returns
+781    -------
+782    fit_parameters : list[Obs]
+783        LIist of fitted observables.
+784    """
+785
+786    def f(a, x):
+787        y = a[0] + a[1] * x
+788        return y
+789
+790    if all(isinstance(n, Obs) for n in x):
+791        out = total_least_squares(x, y, f, **kwargs)
+792        return out.fit_parameters
+793    elif all(isinstance(n, float) or isinstance(n, int) for n in x) or isinstance(x, np.ndarray):
+794        out = least_squares(x, y, f, **kwargs)
+795        return out.fit_parameters
+796    else:
+797        raise TypeError('Unsupported types for x')
+798
+799
+800def qqplot(x, o_y, func, p, title=""):
+801    """Generates a quantile-quantile plot of the fit result which can be used to
+802       check if the residuals of the fit are gaussian distributed.
+803
+804    Returns
+805    -------
+806    None
+807    """
+808
+809    residuals = []
+810    for i_x, i_y in zip(x, o_y, strict=True):
+811        residuals.append((i_y - func(p, i_x)) / i_y.dvalue)
+812    residuals = sorted(residuals)
+813    my_y = [o.value for o in residuals]
+814    probplot = scipy.stats.probplot(my_y)
+815    my_x = probplot[0][0]
+816    plt.figure(figsize=(8, 8 / 1.618))
+817    plt.errorbar(my_x, my_y, fmt='o')
+818    fit_start = my_x[0]
+819    fit_stop = my_x[-1]
+820    samples = np.arange(fit_start, fit_stop, 0.01)
+821    plt.plot(samples, samples, 'k--', zorder=11, label='Standard normal distribution')
+822    plt.plot(samples, probplot[1][0] * samples + probplot[1][1], zorder=10, label='Least squares fit, r=' + str(np.around(probplot[1][2], 3)), marker='', ls='-')
+823
+824    plt.xlabel('Theoretical quantiles')
+825    plt.ylabel('Ordered Values')
+826    plt.legend(title=title)
+827    plt.draw()
+828
+829
+830def residual_plot(x, y, func, fit_res, title=""):
+831    """Generates a plot which compares the fit to the data and displays the corresponding residuals
 832
-833    Returns
-834    -------
-835    None
-836    """
-837    sorted_x = sorted(x)
-838    xstart = sorted_x[0] - 0.5 * (sorted_x[1] - sorted_x[0])
-839    xstop = sorted_x[-1] + 0.5 * (sorted_x[-1] - sorted_x[-2])
-840    x_samples = np.arange(xstart, xstop + 0.01, 0.01)
-841
-842    plt.figure(figsize=(8, 8 / 1.618))
-843    gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0)
-844    ax0 = plt.subplot(gs[0])
-845    ax0.errorbar(x, [o.value for o in y], yerr=[o.dvalue for o in y], ls='none', fmt='o', capsize=3, markersize=5, label='Data')
-846    ax0.plot(x_samples, func([o.value for o in fit_res], x_samples), label='Fit', zorder=10, ls='-', ms=0)
-847    ax0.set_xticklabels([])
-848    ax0.set_xlim([xstart, xstop])
+833    For uncorrelated data the residuals are expected to be distributed ~N(0,1).
+834
+835    Returns
+836    -------
+837    None
+838    """
+839    sorted_x = sorted(x)
+840    xstart = sorted_x[0] - 0.5 * (sorted_x[1] - sorted_x[0])
+841    xstop = sorted_x[-1] + 0.5 * (sorted_x[-1] - sorted_x[-2])
+842    x_samples = np.arange(xstart, xstop + 0.01, 0.01)
+843
+844    plt.figure(figsize=(8, 8 / 1.618))
+845    gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0)
+846    ax0 = plt.subplot(gs[0])
+847    ax0.errorbar(x, [o.value for o in y], yerr=[o.dvalue for o in y], ls='none', fmt='o', capsize=3, markersize=5, label='Data')
+848    ax0.plot(x_samples, func([o.value for o in fit_res], x_samples), label='Fit', zorder=10, ls='-', ms=0)
 849    ax0.set_xticklabels([])
-850    ax0.legend(title=title)
-851
-852    residuals = (np.asarray([o.value for o in y]) - func([o.value for o in fit_res], np.asarray(x))) / np.asarray([o.dvalue for o in y])
-853    ax1 = plt.subplot(gs[1])
-854    ax1.plot(x, residuals, 'ko', ls='none', markersize=5)
-855    ax1.tick_params(direction='out')
-856    ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True)
-857    ax1.axhline(y=0.0, ls='--', color='k', marker=" ")
-858    ax1.fill_between(x_samples, -1.0, 1.0, alpha=0.1, facecolor='k')
-859    ax1.set_xlim([xstart, xstop])
-860    ax1.set_ylabel('Residuals')
-861    plt.subplots_adjust(wspace=None, hspace=None)
-862    plt.draw()
-863
-864
-865def error_band(x, func, beta):
-866    """Calculate the error band for an array of sample values x, for given fit function func with optimized parameters beta.
-867
-868    Returns
-869    -------
-870    err : np.array(Obs)
-871        Error band for an array of sample values x
-872    """
-873    cov = covariance(beta)
-874    if np.any(np.abs(cov - cov.T) > 1000 * np.finfo(np.float64).eps):
-875        warnings.warn("Covariance matrix is not symmetric within floating point precision", RuntimeWarning)
-876
-877    deriv = []
-878    for i, item in enumerate(x):
-879        deriv.append(np.array(egrad(func)([o.value for o in beta], item)))
-880
-881    err = []
-882    for i, item in enumerate(x):
-883        err.append(np.sqrt(deriv[i] @ cov @ deriv[i]))
-884    err = np.array(err)
-885
-886    return err
+850    ax0.set_xlim([xstart, xstop])
+851    ax0.set_xticklabels([])
+852    ax0.legend(title=title)
+853
+854    residuals = (np.asarray([o.value for o in y]) - func([o.value for o in fit_res], np.asarray(x))) / np.asarray([o.dvalue for o in y])
+855    ax1 = plt.subplot(gs[1])
+856    ax1.plot(x, residuals, 'ko', ls='none', markersize=5)
+857    ax1.tick_params(direction='out')
+858    ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True)
+859    ax1.axhline(y=0.0, ls='--', color='k', marker=" ")
+860    ax1.fill_between(x_samples, -1.0, 1.0, alpha=0.1, facecolor='k')
+861    ax1.set_xlim([xstart, xstop])
+862    ax1.set_ylabel('Residuals')
+863    plt.subplots_adjust(wspace=None, hspace=None)
+864    plt.draw()
+865
+866
+867def error_band(x, func, beta):
+868    """Calculate the error band for an array of sample values x, for given fit function func with optimized parameters beta.
+869
+870    Returns
+871    -------
+872    err : np.array(Obs)
+873        Error band for an array of sample values x
+874    """
+875    cov = covariance(beta)
+876    if np.any(np.abs(cov - cov.T) > 1000 * np.finfo(np.float64).eps):
+877        warnings.warn("Covariance matrix is not symmetric within floating point precision", RuntimeWarning, stacklevel=2)
+878
+879    deriv = []
+880    for item in x:
+881        deriv.append(np.array(egrad(func)([o.value for o in beta], item)))
+882
+883    err = []
+884    for i, _item in enumerate(x):
+885        err.append(np.sqrt(deriv[i] @ cov @ deriv[i]))
+886    err = np.array(err)
 887
-888
-889def ks_test(objects=None):
-890    """Performs a Kolmogorov–Smirnov test for the p-values of all fit object.
-891
-892    Parameters
-893    ----------
-894    objects : list
-895        List of fit results to include in the analysis (optional).
-896
-897    Returns
-898    -------
-899    None
-900    """
-901
-902    if objects is None:
-903        obs_list = []
-904        for obj in gc.get_objects():
-905            if isinstance(obj, Fit_result):
-906                obs_list.append(obj)
-907    else:
-908        obs_list = objects
-909
-910    p_values = [o.p_value for o in obs_list]
+888    return err
+889
+890
+891def ks_test(objects=None):
+892    """Performs a Kolmogorov–Smirnov test for the p-values of all fit object.
+893
+894    Parameters
+895    ----------
+896    objects : list
+897        List of fit results to include in the analysis (optional).
+898
+899    Returns
+900    -------
+901    None
+902    """
+903
+904    if objects is None:
+905        obs_list = []
+906        for obj in gc.get_objects():
+907            if isinstance(obj, Fit_result):
+908                obs_list.append(obj)
+909    else:
+910        obs_list = objects
 911
-912    bins = len(p_values)
-913    x = np.arange(0, 1.001, 0.001)
-914    plt.plot(x, x, 'k', zorder=1)
-915    plt.xlim(0, 1)
-916    plt.ylim(0, 1)
-917    plt.xlabel('p-value')
-918    plt.ylabel('Cumulative probability')
-919    plt.title(str(bins) + ' p-values')
-920
-921    n = np.arange(1, bins + 1) / np.float64(bins)
-922    Xs = np.sort(p_values)
-923    plt.step(Xs, n)
-924    diffs = n - Xs
-925    loc_max_diff = np.argmax(np.abs(diffs))
-926    loc = Xs[loc_max_diff]
-927    plt.annotate('', xy=(loc, loc), xytext=(loc, loc + diffs[loc_max_diff]), arrowprops=dict(arrowstyle='<->', shrinkA=0, shrinkB=0))
-928    plt.draw()
-929
-930    print(scipy.stats.kstest(p_values, 'uniform'))
+912    p_values = [o.p_value for o in obs_list]
+913
+914    bins = len(p_values)
+915    x = np.arange(0, 1.001, 0.001)
+916    plt.plot(x, x, 'k', zorder=1)
+917    plt.xlim(0, 1)
+918    plt.ylim(0, 1)
+919    plt.xlabel('p-value')
+920    plt.ylabel('Cumulative probability')
+921    plt.title(str(bins) + ' p-values')
+922
+923    n = np.arange(1, bins + 1) / np.float64(bins)
+924    Xs = np.sort(p_values)
+925    plt.step(Xs, n)
+926    diffs = n - Xs
+927    loc_max_diff = np.argmax(np.abs(diffs))
+928    loc = Xs[loc_max_diff]
+929    plt.annotate('', xy=(loc, loc), xytext=(loc, loc + diffs[loc_max_diff]), arrowprops=dict(arrowstyle='<->', shrinkA=0, shrinkB=0))
+930    plt.draw()
 931
-932
-933def _extract_val_and_dval(string):
-934    split_string = string.split('(')
-935    if '.' in split_string[0] and '.' not in split_string[1][:-1]:
-936        factor = 10 ** -len(split_string[0].partition('.')[2])
-937    else:
-938        factor = 1
-939    return float(split_string[0]), float(split_string[1][:-1]) * factor
-940
-941
-942def _construct_prior_obs(i_prior, i_n):
-943    if isinstance(i_prior, Obs):
-944        return i_prior
-945    elif isinstance(i_prior, str):
-946        loc_val, loc_dval = _extract_val_and_dval(i_prior)
-947        return cov_Obs(loc_val, loc_dval ** 2, '#prior' + str(i_n) + f"_{np.random.randint(2147483647):010d}")
-948    else:
-949        raise TypeError("Prior entries need to be 'Obs' or 'str'.")
+932    print(scipy.stats.kstest(p_values, 'uniform'))
+933
+934
+935def _extract_val_and_dval(string):
+936    split_string = string.split('(')
+937    if '.' in split_string[0] and '.' not in split_string[1][:-1]:
+938        factor = 10 ** -len(split_string[0].partition('.')[2])
+939    else:
+940        factor = 1
+941    return float(split_string[0]), float(split_string[1][:-1]) * factor
+942
+943
+944def _construct_prior_obs(i_prior, i_n):
+945    if isinstance(i_prior, Obs):
+946        return i_prior
+947    elif isinstance(i_prior, str):
+948        loc_val, loc_dval = _extract_val_and_dval(i_prior)
+949        return cov_Obs(loc_val, loc_dval ** 2, '#prior' + str(i_n) + f"_{np.random.randint(2147483647):010d}")  # noqa: NPY002
+950    else:
+951        raise TypeError("Prior entries need to be 'Obs' or 'str'.")
 
@@ -1073,57 +1075,57 @@
-
21class Fit_result(Sequence):
-22    """Represents fit results.
-23
-24    Attributes
-25    ----------
-26    fit_parameters : list
-27        results for the individual fit parameters,
-28        also accessible via indices.
-29    chisquare_by_dof : float
-30        reduced chisquare.
-31    p_value : float
-32        p-value of the fit
-33    t2_p_value : float
-34        Hotelling t-squared p-value for correlated fits.
-35    """
-36
-37    def __init__(self):
-38        self.fit_parameters = None
-39
-40    def __getitem__(self, idx):
-41        return self.fit_parameters[idx]
-42
-43    def __len__(self):
-44        return len(self.fit_parameters)
-45
-46    def gamma_method(self, **kwargs):
-47        """Apply the gamma method to all fit parameters"""
-48        [o.gamma_method(**kwargs) for o in self.fit_parameters]
-49
-50    gm = gamma_method
+            
23class Fit_result(Sequence):
+24    """Represents fit results.
+25
+26    Attributes
+27    ----------
+28    fit_parameters : list
+29        results for the individual fit parameters,
+30        also accessible via indices.
+31    chisquare_by_dof : float
+32        reduced chisquare.
+33    p_value : float
+34        p-value of the fit
+35    t2_p_value : float
+36        Hotelling t-squared p-value for correlated fits.
+37    """
+38
+39    def __init__(self):
+40        self.fit_parameters = None
+41
+42    def __getitem__(self, idx):
+43        return self.fit_parameters[idx]
+44
+45    def __len__(self):
+46        return len(self.fit_parameters)
+47
+48    def gamma_method(self, **kwargs):
+49        """Apply the gamma method to all fit parameters"""
+50        [o.gamma_method(**kwargs) for o in self.fit_parameters]
 51
-52    def __str__(self):
-53        my_str = 'Goodness of fit:\n'
-54        if hasattr(self, 'chisquare_by_dof'):
-55            my_str += '\u03C7\u00b2/d.o.f. = ' + f'{self.chisquare_by_dof:2.6f}' + '\n'
-56        elif hasattr(self, 'residual_variance'):
-57            my_str += 'residual variance = ' + f'{self.residual_variance:2.6f}' + '\n'
-58        if hasattr(self, 'chisquare_by_expected_chisquare'):
-59            my_str += '\u03C7\u00b2/\u03C7\u00b2exp  = ' + f'{self.chisquare_by_expected_chisquare:2.6f}' + '\n'
-60        if hasattr(self, 'p_value'):
-61            my_str += 'p-value   = ' + f'{self.p_value:2.4f}' + '\n'
-62        if hasattr(self, 't2_p_value'):
-63            my_str += 't\u00B2p-value = ' + f'{self.t2_p_value:2.4f}' + '\n'
-64        my_str += 'Fit parameters:\n'
-65        for i_par, par in enumerate(self.fit_parameters):
-66            my_str += str(i_par) + '\t' + ' ' * int(par >= 0) + str(par).rjust(int(par < 0.0)) + '\n'
-67        return my_str
-68
-69    def __repr__(self):
-70        m = max(map(len, list(self.__dict__.keys()))) + 1
-71        return '\n'.join([key.rjust(m) + ': ' + repr(value) for key, value in sorted(self.__dict__.items())])
+52    gm = gamma_method
+53
+54    def __str__(self):
+55        my_str = 'Goodness of fit:\n'
+56        if hasattr(self, 'chisquare_by_dof'):
+57            my_str += '\u03C7\u00b2/d.o.f. = ' + f'{self.chisquare_by_dof:2.6f}' + '\n'
+58        elif hasattr(self, 'residual_variance'):
+59            my_str += 'residual variance = ' + f'{self.residual_variance:2.6f}' + '\n'
+60        if hasattr(self, 'chisquare_by_expected_chisquare'):
+61            my_str += '\u03C7\u00b2/\u03C7\u00b2exp  = ' + f'{self.chisquare_by_expected_chisquare:2.6f}' + '\n'
+62        if hasattr(self, 'p_value'):
+63            my_str += 'p-value   = ' + f'{self.p_value:2.4f}' + '\n'
+64        if hasattr(self, 't2_p_value'):
+65            my_str += 't\u00B2p-value = ' + f'{self.t2_p_value:2.4f}' + '\n'
+66        my_str += 'Fit parameters:\n'
+67        for i_par, par in enumerate(self.fit_parameters):
+68            my_str += str(i_par) + '\t' + ' ' * int(par >= 0) + str(par).rjust(int(par < 0.0)) + '\n'
+69        return my_str
+70
+71    def __repr__(self):
+72        m = max(map(len, list(self.__dict__.keys()))) + 1
+73        return '\n'.join([key.rjust(m) + ': ' + repr(value) for key, value in sorted(self.__dict__.items())])
 
@@ -1167,9 +1169,9 @@ Hotelling t-squared p-value for correlated fits.
-
46    def gamma_method(self, **kwargs):
-47        """Apply the gamma method to all fit parameters"""
-48        [o.gamma_method(**kwargs) for o in self.fit_parameters]
+            
48    def gamma_method(self, **kwargs):
+49        """Apply the gamma method to all fit parameters"""
+50        [o.gamma_method(**kwargs) for o in self.fit_parameters]
 
@@ -1189,9 +1191,9 @@ Hotelling t-squared p-value for correlated fits.
-
46    def gamma_method(self, **kwargs):
-47        """Apply the gamma method to all fit parameters"""
-48        [o.gamma_method(**kwargs) for o in self.fit_parameters]
+            
48    def gamma_method(self, **kwargs):
+49        """Apply the gamma method to all fit parameters"""
+50        [o.gamma_method(**kwargs) for o in self.fit_parameters]
 
@@ -1212,454 +1214,454 @@ Hotelling t-squared p-value for correlated fits.
-
 74def least_squares(x, y, func, priors=None, silent=False, **kwargs):
- 75    r'''Performs a non-linear fit to y = func(x).
- 76        ```
- 77
- 78    Parameters
- 79    ----------
- 80    For an uncombined fit:
- 81
- 82    x : list
- 83        list of floats.
- 84    y : list
- 85        list of Obs.
- 86    func : object
- 87        fit function, has to be of the form
- 88
- 89        ```python
- 90        import autograd.numpy as anp
- 91
- 92        def func(a, x):
- 93            return a[0] + a[1] * x + a[2] * anp.sinh(x)
- 94        ```
- 95
- 96        For multiple x values func can be of the form
+            
 76def least_squares(x, y, func, priors=None, silent=False, **kwargs):
+ 77    r'''Performs a non-linear fit to y = func(x).
+ 78        ```
+ 79
+ 80    Parameters
+ 81    ----------
+ 82    For an uncombined fit:
+ 83
+ 84    x : list
+ 85        list of floats.
+ 86    y : list
+ 87        list of Obs.
+ 88    func : object
+ 89        fit function, has to be of the form
+ 90
+ 91        ```python
+ 92        import autograd.numpy as anp
+ 93
+ 94        def func(a, x):
+ 95            return a[0] + a[1] * x + a[2] * anp.sinh(x)
+ 96        ```
  97
- 98        ```python
- 99        def func(a, x):
-100            (x1, x2) = x
-101            return a[0] * x1 ** 2 + a[1] * x2
-102        ```
-103        It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation
-104        will not work.
-105
-106    OR For a combined fit:
+ 98        For multiple x values func can be of the form
+ 99
+100        ```python
+101        def func(a, x):
+102            (x1, x2) = x
+103            return a[0] * x1 ** 2 + a[1] * x2
+104        ```
+105        It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation
+106        will not work.
 107
-108    x : dict
-109        dict of lists.
-110    y : dict
-111        dict of lists of Obs.
-112    funcs : dict
-113        dict of objects
-114        fit functions have to be of the form (here a[0] is the common fit parameter)
-115        ```python
-116        import autograd.numpy as anp
-117        funcs = {"a": func_a,
-118                "b": func_b}
-119
-120        def func_a(a, x):
-121            return a[1] * anp.exp(-a[0] * x)
-122
-123        def func_b(a, x):
-124            return a[2] * anp.exp(-a[0] * x)
-125
-126        It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation
-127        will not work.
-128
-129    priors : dict or list, optional
-130        priors can either be a dictionary with integer keys and the corresponding priors as values or
-131        a list with an entry for every parameter in the fit. The entries can either be
-132        Obs (e.g. results from a previous fit) or strings containing a value and an error formatted like
-133        0.548(23), 500(40) or 0.5(0.4)
-134    silent : bool, optional
-135        If True all output to the console is omitted (default False).
-136    initial_guess : list
-137        can provide an initial guess for the input parameters. Relevant for
-138        non-linear fits with many parameters. In case of correlated fits the guess is used to perform
-139        an uncorrelated fit which then serves as guess for the correlated fit.
-140    method : str, optional
-141        can be used to choose an alternative method for the minimization of chisquare.
-142        The possible methods are the ones which can be used for scipy.optimize.minimize and
-143        migrad of iminuit. If no method is specified, Levenberg–Marquardt is used.
-144        Reliable alternatives are migrad, Powell and Nelder-Mead.
-145    tol: float, optional
-146        can be used (only for combined fits and methods other than Levenberg–Marquardt) to set the tolerance for convergence
-147        to a different value to either speed up convergence at the cost of a larger error on the fitted parameters (and possibly
-148        invalid estimates for parameter uncertainties) or smaller values to get more accurate parameter values
-149        The stopping criterion depends on the method, e.g. migrad: edm_max = 0.002 * tol * errordef (EDM criterion: edm < edm_max)
-150    correlated_fit : bool
-151        If True, use the full inverse covariance matrix in the definition of the chisquare cost function.
-152        For details about how the covariance matrix is estimated see `pyerrors.obs.covariance`.
-153        In practice the correlation matrix is Cholesky decomposed and inverted (instead of the covariance matrix).
-154        This procedure should be numerically more stable as the correlation matrix is typically better conditioned (Jacobi preconditioning).
-155    inv_chol_cov_matrix [array,list], optional
-156        array: shape = (number of y values) X (number of y values)
-157        list:   for an uncombined fit: [""]
-158                for a combined fit: list of keys belonging to the corr_matrix saved in the array, must be the same as the keys of the y dict in alphabetical order
-159        If correlated_fit=True is set as well, can provide an inverse covariance matrix (y errors, dy_f included!) of your own choosing for a correlated fit.
-160        The matrix must be a lower triangular matrix constructed from a Cholesky decomposition: The function invert_corr_cov_cholesky(corr, inverrdiag) can be
-161        used to construct it from a correlation matrix (corr) and the errors dy_f of the data points (inverrdiag = np.diag(1 / np.asarray(dy_f))). For the correct
-162        ordering the correlation matrix (corr) can be sorted via the function sort_corr(corr, kl, yd) where kl is the list of keys and yd the y dict.
-163    expected_chisquare : bool
-164        If True estimates the expected chisquare which is
-165        corrected by effects caused by correlated input data (default False).
-166    resplot : bool
-167        If True, a plot which displays fit, data and residuals is generated (default False).
-168    qqplot : bool
-169        If True, a quantile-quantile plot of the fit result is generated (default False).
-170    num_grad : bool
-171        Use numerical differentation instead of automatic differentiation to perform the error propagation (default False).
-172    n_parms : int, optional
-173        Number of fit parameters. Overrides automatic detection of parameter count.
-174        Useful when autodetection fails. Must match the length of initial_guess or priors (if provided).
-175
-176    Returns
-177    -------
-178    output : Fit_result
-179        Parameters and information on the fitted result.
-180    Examples
-181    ------
-182    >>> # Example of a correlated (correlated_fit = True, inv_chol_cov_matrix handed over) combined fit, based on a randomly generated data set
-183    >>> import numpy as np
-184    >>> from scipy.stats import norm
-185    >>> from scipy.linalg import cholesky
-186    >>> import pyerrors as pe
-187    >>> # generating the random data set
-188    >>> num_samples = 400
-189    >>> N = 3
-190    >>> x = np.arange(N)
-191    >>> x1 = norm.rvs(size=(N, num_samples)) # generate random numbers
-192    >>> x2 = norm.rvs(size=(N, num_samples)) # generate random numbers
-193    >>> r = r1 = r2 = np.zeros((N, N))
-194    >>> y = {}
-195    >>> for i in range(N):
-196    >>>    for j in range(N):
-197    >>>        r[i, j] = np.exp(-0.8 * np.fabs(i - j)) # element in correlation matrix
-198    >>> errl = np.sqrt([3.4, 2.5, 3.6]) # set y errors
-199    >>> for i in range(N):
-200    >>>    for j in range(N):
-201    >>>        r[i, j] *= errl[i] * errl[j] # element in covariance matrix
-202    >>> c = cholesky(r, lower=True)
-203    >>> y = {'a': np.dot(c, x1), 'b': np.dot(c, x2)} # generate y data with the covariance matrix defined
-204    >>> # random data set has been generated, now the dictionaries and the inverse covariance matrix to be handed over are built
-205    >>> x_dict = {}
-206    >>> y_dict = {}
-207    >>> chol_inv_dict = {}
-208    >>> data = []
-209    >>> for key in y.keys():
-210    >>>    x_dict[key] = x
-211    >>>    for i in range(N):
-212    >>>        data.append(pe.Obs([[i + 1 + o for o in y[key][i]]], ['ens'])) # generate y Obs from the y data
-213    >>>    [o.gamma_method() for o in data]
-214    >>>    corr = pe.covariance(data, correlation=True)
-215    >>>    inverrdiag = np.diag(1 / np.asarray([o.dvalue for o in data]))
-216    >>>    chol_inv = pe.obs.invert_corr_cov_cholesky(corr, inverrdiag) # gives form of the inverse covariance matrix needed for the combined correlated fit below
-217    >>> y_dict = {'a': data[:3], 'b': data[3:]}
-218    >>> # common fit parameter p[0] in combined fit
-219    >>> def fit1(p, x):
-220    >>>    return p[0] + p[1] * x
-221    >>> def fit2(p, x):
-222    >>>    return p[0] + p[2] * x
-223    >>> fitf_dict = {'a': fit1, 'b':fit2}
-224    >>> fitp_inv_cov_combined_fit = pe.least_squares(x_dict,y_dict, fitf_dict, correlated_fit = True, inv_chol_cov_matrix = [chol_inv,['a','b']])
-225    Fit with 3 parameters
-226    Method: Levenberg-Marquardt
-227    `ftol` termination condition is satisfied.
-228    chisquare/d.o.f.: 0.5388013574561786 # random
-229    fit parameters [1.11897846 0.96361162 0.92325319] # random
-230
-231    '''
-232    output = Fit_result()
-233
-234    if (isinstance(x, dict) and isinstance(y, dict) and isinstance(func, dict)):
-235        xd = {key: anp.asarray(x[key]) for key in x}
-236        yd = y
-237        funcd = func
-238        output.fit_function = func
-239    elif (isinstance(x, dict) or isinstance(y, dict) or isinstance(func, dict)):
-240        raise TypeError("All arguments have to be dictionaries in order to perform a combined fit.")
-241    else:
-242        x = np.asarray(x)
-243        xd = {"": x}
-244        yd = {"": y}
-245        funcd = {"": func}
-246        output.fit_function = func
-247
-248    if kwargs.get('num_grad') is True:
-249        jacobian = num_jacobian
-250        hessian = num_hessian
-251    else:
-252        jacobian = auto_jacobian
-253        hessian = auto_hessian
-254
-255    key_ls = sorted(list(xd.keys()))
+108    OR For a combined fit:
+109
+110    x : dict
+111        dict of lists.
+112    y : dict
+113        dict of lists of Obs.
+114    funcs : dict
+115        dict of objects
+116        fit functions have to be of the form (here a[0] is the common fit parameter)
+117        ```python
+118        import autograd.numpy as anp
+119        funcs = {"a": func_a,
+120                "b": func_b}
+121
+122        def func_a(a, x):
+123            return a[1] * anp.exp(-a[0] * x)
+124
+125        def func_b(a, x):
+126            return a[2] * anp.exp(-a[0] * x)
+127
+128        It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation
+129        will not work.
+130
+131    priors : dict or list, optional
+132        priors can either be a dictionary with integer keys and the corresponding priors as values or
+133        a list with an entry for every parameter in the fit. The entries can either be
+134        Obs (e.g. results from a previous fit) or strings containing a value and an error formatted like
+135        0.548(23), 500(40) or 0.5(0.4)
+136    silent : bool, optional
+137        If True all output to the console is omitted (default False).
+138    initial_guess : list
+139        can provide an initial guess for the input parameters. Relevant for
+140        non-linear fits with many parameters. In case of correlated fits the guess is used to perform
+141        an uncorrelated fit which then serves as guess for the correlated fit.
+142    method : str, optional
+143        can be used to choose an alternative method for the minimization of chisquare.
+144        The possible methods are the ones which can be used for scipy.optimize.minimize and
+145        migrad of iminuit. If no method is specified, Levenberg–Marquardt is used.
+146        Reliable alternatives are migrad, Powell and Nelder-Mead.
+147    tol: float, optional
+148        can be used (only for combined fits and methods other than Levenberg–Marquardt) to set the tolerance for convergence
+149        to a different value to either speed up convergence at the cost of a larger error on the fitted parameters (and possibly
+150        invalid estimates for parameter uncertainties) or smaller values to get more accurate parameter values
+151        The stopping criterion depends on the method, e.g. migrad: edm_max = 0.002 * tol * errordef (EDM criterion: edm < edm_max)
+152    correlated_fit : bool
+153        If True, use the full inverse covariance matrix in the definition of the chisquare cost function.
+154        For details about how the covariance matrix is estimated see `pyerrors.obs.covariance`.
+155        In practice the correlation matrix is Cholesky decomposed and inverted (instead of the covariance matrix).
+156        This procedure should be numerically more stable as the correlation matrix is typically better conditioned (Jacobi preconditioning).
+157    inv_chol_cov_matrix [array,list], optional
+158        array: shape = (number of y values) X (number of y values)
+159        list:   for an uncombined fit: [""]
+160                for a combined fit: list of keys belonging to the corr_matrix saved in the array, must be the same as the keys of the y dict in alphabetical order
+161        If correlated_fit=True is set as well, can provide an inverse covariance matrix (y errors, dy_f included!) of your own choosing for a correlated fit.
+162        The matrix must be a lower triangular matrix constructed from a Cholesky decomposition: The function invert_corr_cov_cholesky(corr, inverrdiag) can be
+163        used to construct it from a correlation matrix (corr) and the errors dy_f of the data points (inverrdiag = np.diag(1 / np.asarray(dy_f))). For the correct
+164        ordering the correlation matrix (corr) can be sorted via the function sort_corr(corr, kl, yd) where kl is the list of keys and yd the y dict.
+165    expected_chisquare : bool
+166        If True estimates the expected chisquare which is
+167        corrected by effects caused by correlated input data (default False).
+168    resplot : bool
+169        If True, a plot which displays fit, data and residuals is generated (default False).
+170    qqplot : bool
+171        If True, a quantile-quantile plot of the fit result is generated (default False).
+172    num_grad : bool
+173        Use numerical differentation instead of automatic differentiation to perform the error propagation (default False).
+174    n_parms : int, optional
+175        Number of fit parameters. Overrides automatic detection of parameter count.
+176        Useful when autodetection fails. Must match the length of initial_guess or priors (if provided).
+177
+178    Returns
+179    -------
+180    output : Fit_result
+181        Parameters and information on the fitted result.
+182    Examples
+183    ------
+184    >>> # Example of a correlated (correlated_fit = True, inv_chol_cov_matrix handed over) combined fit, based on a randomly generated data set
+185    >>> import numpy as np
+186    >>> from scipy.stats import norm
+187    >>> from scipy.linalg import cholesky
+188    >>> import pyerrors as pe
+189    >>> # generating the random data set
+190    >>> num_samples = 400
+191    >>> N = 3
+192    >>> x = np.arange(N)
+193    >>> x1 = norm.rvs(size=(N, num_samples)) # generate random numbers
+194    >>> x2 = norm.rvs(size=(N, num_samples)) # generate random numbers
+195    >>> r = r1 = r2 = np.zeros((N, N))
+196    >>> y = {}
+197    >>> for i in range(N):
+198    >>>    for j in range(N):
+199    >>>        r[i, j] = np.exp(-0.8 * np.fabs(i - j)) # element in correlation matrix
+200    >>> errl = np.sqrt([3.4, 2.5, 3.6]) # set y errors
+201    >>> for i in range(N):
+202    >>>    for j in range(N):
+203    >>>        r[i, j] *= errl[i] * errl[j] # element in covariance matrix
+204    >>> c = cholesky(r, lower=True)
+205    >>> y = {'a': np.dot(c, x1), 'b': np.dot(c, x2)} # generate y data with the covariance matrix defined
+206    >>> # random data set has been generated, now the dictionaries and the inverse covariance matrix to be handed over are built
+207    >>> x_dict = {}
+208    >>> y_dict = {}
+209    >>> chol_inv_dict = {}
+210    >>> data = []
+211    >>> for key in y.keys():
+212    >>>    x_dict[key] = x
+213    >>>    for i in range(N):
+214    >>>        data.append(pe.Obs([[i + 1 + o for o in y[key][i]]], ['ens'])) # generate y Obs from the y data
+215    >>>    [o.gamma_method() for o in data]
+216    >>>    corr = pe.covariance(data, correlation=True)
+217    >>>    inverrdiag = np.diag(1 / np.asarray([o.dvalue for o in data]))
+218    >>>    chol_inv = pe.obs.invert_corr_cov_cholesky(corr, inverrdiag) # gives form of the inverse covariance matrix needed for the combined correlated fit below
+219    >>> y_dict = {'a': data[:3], 'b': data[3:]}
+220    >>> # common fit parameter p[0] in combined fit
+221    >>> def fit1(p, x):
+222    >>>    return p[0] + p[1] * x
+223    >>> def fit2(p, x):
+224    >>>    return p[0] + p[2] * x
+225    >>> fitf_dict = {'a': fit1, 'b':fit2}
+226    >>> fitp_inv_cov_combined_fit = pe.least_squares(x_dict,y_dict, fitf_dict, correlated_fit = True, inv_chol_cov_matrix = [chol_inv,['a','b']])
+227    Fit with 3 parameters
+228    Method: Levenberg-Marquardt
+229    `ftol` termination condition is satisfied.
+230    chisquare/d.o.f.: 0.5388013574561786 # random
+231    fit parameters [1.11897846 0.96361162 0.92325319] # random
+232
+233    '''
+234    output = Fit_result()
+235
+236    if (isinstance(x, dict) and isinstance(y, dict) and isinstance(func, dict)):
+237        xd = {key: anp.asarray(x[key]) for key in x}
+238        yd = y
+239        funcd = func
+240        output.fit_function = func
+241    elif (isinstance(x, dict) or isinstance(y, dict) or isinstance(func, dict)):
+242        raise TypeError("All arguments have to be dictionaries in order to perform a combined fit.")
+243    else:
+244        x = np.asarray(x)
+245        xd = {"": x}
+246        yd = {"": y}
+247        funcd = {"": func}
+248        output.fit_function = func
+249
+250    if kwargs.get('num_grad') is True:
+251        jacobian = num_jacobian
+252        hessian = num_hessian
+253    else:
+254        jacobian = auto_jacobian
+255        hessian = auto_hessian
 256
-257    if sorted(list(yd.keys())) != key_ls:
-258        raise ValueError('x and y dictionaries do not contain the same keys.')
-259
-260    if sorted(list(funcd.keys())) != key_ls:
-261        raise ValueError('x and func dictionaries do not contain the same keys.')
-262
-263    x_all = np.concatenate([np.array(xd[key]).transpose() for key in key_ls]).transpose()
-264    y_all = np.concatenate([np.array(yd[key]) for key in key_ls])
-265
-266    y_f = [o.value for o in y_all]
-267    dy_f = [o.dvalue for o in y_all]
-268
-269    if len(x_all.shape) > 2:
-270        raise ValueError("Unknown format for x values")
-271
-272    if np.any(np.asarray(dy_f) <= 0.0):
-273        raise Exception("No y errors available, run the gamma method first.")
-274
-275    # number of fit parameters
-276    if 'n_parms' in kwargs:
-277        n_parms = kwargs.get('n_parms')
-278        if not isinstance(n_parms, int):
-279            raise TypeError(
-280                f"'n_parms' must be an integer, got {n_parms!r} "
-281                f"of type {type(n_parms).__name__}."
-282            )
-283        if n_parms <= 0:
-284            raise ValueError(
-285                f"'n_parms' must be a positive integer, got {n_parms}."
-286            )
-287    else:
-288        n_parms_ls = []
-289        for key in key_ls:
-290            if not callable(funcd[key]):
-291                raise TypeError('func (key=' + key + ') is not a function.')
-292            if np.asarray(xd[key]).shape[-1] != len(yd[key]):
-293                raise ValueError('x and y input (key=' + key + ') do not have the same length')
-294            for n_loc in range(100):
-295                try:
-296                    funcd[key](np.arange(n_loc), x_all.T[0])
-297                except TypeError:
-298                    continue
-299                except IndexError:
+257    key_ls = sorted(list(xd.keys()))
+258
+259    if sorted(list(yd.keys())) != key_ls:
+260        raise ValueError('x and y dictionaries do not contain the same keys.')
+261
+262    if sorted(list(funcd.keys())) != key_ls:
+263        raise ValueError('x and func dictionaries do not contain the same keys.')
+264
+265    x_all = np.concatenate([np.array(xd[key]).transpose() for key in key_ls]).transpose()
+266    y_all = np.concatenate([np.array(yd[key]) for key in key_ls])
+267
+268    y_f = [o.value for o in y_all]
+269    dy_f = [o.dvalue for o in y_all]
+270
+271    if len(x_all.shape) > 2:
+272        raise ValueError("Unknown format for x values")
+273
+274    if np.any(np.asarray(dy_f) <= 0.0):
+275        raise Exception("No y errors available, run the gamma method first.")
+276
+277    # number of fit parameters
+278    if 'n_parms' in kwargs:
+279        n_parms = kwargs.get('n_parms')
+280        if not isinstance(n_parms, int):
+281            raise TypeError(
+282                f"'n_parms' must be an integer, got {n_parms!r} "
+283                f"of type {type(n_parms).__name__}."
+284            )
+285        if n_parms <= 0:
+286            raise ValueError(
+287                f"'n_parms' must be a positive integer, got {n_parms}."
+288            )
+289    else:
+290        n_parms_ls = []
+291        for key in key_ls:
+292            if not callable(funcd[key]):
+293                raise TypeError('func (key=' + key + ') is not a function.')
+294            if np.asarray(xd[key]).shape[-1] != len(yd[key]):
+295                raise ValueError('x and y input (key=' + key + ') do not have the same length')
+296            for n_loc in range(100):
+297                try:
+298                    funcd[key](np.arange(n_loc), x_all.T[0])
+299                except TypeError:
 300                    continue
-301                else:
-302                    break
-303            else:
-304                raise RuntimeError("Fit function (key=" + key + ") is not valid.")
-305            n_parms_ls.append(n_loc)
-306
-307        n_parms = max(n_parms_ls)
+301                except IndexError:
+302                    continue
+303                else:
+304                    break
+305            else:
+306                raise RuntimeError("Fit function (key=" + key + ") is not valid.")
+307            n_parms_ls.append(n_loc)
 308
-309    if len(key_ls) > 1:
-310        for key in key_ls:
-311            if np.asarray(yd[key]).shape != funcd[key](np.arange(n_parms), xd[key]).shape:
-312                raise ValueError(f"Fit function {key} returns the wrong shape ({funcd[key](np.arange(n_parms), xd[key]).shape} instead of {np.asarray(yd[key]).shape})\nIf the fit function is just a constant you could try adding x*0 to get the correct shape.")
-313
-314    if not silent:
-315        print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1))
-316
-317    if priors is not None:
-318        if isinstance(priors, (list, np.ndarray)):
-319            if n_parms != len(priors):
-320                raise ValueError("'priors' does not have the correct length.")
-321
-322            loc_priors = []
-323            for i_n, i_prior in enumerate(priors):
-324                loc_priors.append(_construct_prior_obs(i_prior, i_n))
-325
-326            prior_mask = np.arange(len(priors))
-327            output.priors = loc_priors
-328
-329        elif isinstance(priors, dict):
-330            loc_priors = []
-331            prior_mask = []
-332            output.priors = {}
-333            for pos, prior in priors.items():
-334                if isinstance(pos, int):
-335                    prior_mask.append(pos)
-336                else:
-337                    raise TypeError("Prior position needs to be an integer.")
-338                loc_priors.append(_construct_prior_obs(prior, pos))
-339
-340                output.priors[pos] = loc_priors[-1]
-341            if max(prior_mask) >= n_parms:
-342                raise ValueError("Prior position out of range.")
-343        else:
-344            raise TypeError("Unkown type for `priors`.")
-345
-346        p_f = [o.value for o in loc_priors]
-347        dp_f = [o.dvalue for o in loc_priors]
-348        if np.any(np.asarray(dp_f) <= 0.0):
-349            raise Exception("No prior errors available, run the gamma method first.")
-350    else:
-351        p_f = dp_f = np.array([])
-352        prior_mask = []
-353        loc_priors = []
-354
-355    if 'initial_guess' in kwargs:
-356        x0 = kwargs.get('initial_guess')
-357        if len(x0) != n_parms:
-358            raise ValueError('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms))
-359    else:
-360        x0 = [0.1] * n_parms
-361
-362    if priors is None:
-363        def general_chisqfunc_uncorr(p, ivars, pr):
-364            model = anp.concatenate([anp.array(funcd[key](p, xd[key])).reshape(-1) for key in key_ls])
-365            return (ivars - model) / dy_f
-366    else:
-367        def general_chisqfunc_uncorr(p, ivars, pr):
-368            model = anp.concatenate([anp.array(funcd[key](p, xd[key])).reshape(-1) for key in key_ls])
-369            return anp.concatenate(((ivars - model) / dy_f, (p[prior_mask] - pr) / dp_f))
-370
-371    def chisqfunc_uncorr(p):
-372        return anp.sum(general_chisqfunc_uncorr(p, y_f, p_f) ** 2)
-373
-374    if kwargs.get('correlated_fit') is True:
-375        if 'inv_chol_cov_matrix' in kwargs:
-376            chol_inv = kwargs.get('inv_chol_cov_matrix')
-377            if (chol_inv[0].shape[0] != len(dy_f)):
-378                raise TypeError('The number of columns of the inverse covariance matrix handed over needs to be equal to the number of y errors.')
-379            if (chol_inv[0].shape[0] != chol_inv[0].shape[1]):
-380                raise TypeError('The inverse covariance matrix handed over needs to have the same number of rows as columns.')
-381            if (chol_inv[1] != key_ls):
-382                raise ValueError('The keys of inverse covariance matrix are not the same or do not appear in the same order as the x and y values.')
-383            chol_inv = chol_inv[0]
-384            if np.any(np.diag(chol_inv) <= 0) or (not np.all(chol_inv == np.tril(chol_inv))):
-385                raise ValueError('The inverse covariance matrix inv_chol_cov_matrix[0] has to be a lower triangular matrix constructed from a Cholesky decomposition.')
-386        else:
-387            corr = covariance(y_all, correlation=True, **kwargs)
-388            inverrdiag = np.diag(1 / np.asarray(dy_f))
-389            chol_inv = invert_corr_cov_cholesky(corr, inverrdiag)
-390
-391        def general_chisqfunc(p, ivars, pr):
-392            model = anp.concatenate([anp.array(funcd[key](p, xd[key])).reshape(-1) for key in key_ls])
-393            return anp.concatenate((anp.dot(chol_inv, (ivars - model)), (p[prior_mask] - pr) / dp_f))
-394
-395        def chisqfunc(p):
-396            return anp.sum(general_chisqfunc(p, y_f, p_f) ** 2)
-397    else:
-398        general_chisqfunc = general_chisqfunc_uncorr
-399        chisqfunc = chisqfunc_uncorr
-400
-401    output.method = kwargs.get('method', 'Levenberg-Marquardt')
-402    if not silent:
-403        print('Method:', output.method)
-404
-405    if output.method != 'Levenberg-Marquardt':
-406        if output.method == 'migrad':
-407            tolerance = 1e-4  # default value of 1e-1 set by iminuit can be problematic
-408            if 'tol' in kwargs:
-409                tolerance = kwargs.get('tol')
-410            fit_result = iminuit.minimize(chisqfunc_uncorr, x0, tol=tolerance)  # Stopping criterion 0.002 * tol * errordef
-411            if kwargs.get('correlated_fit') is True:
-412                fit_result = iminuit.minimize(chisqfunc, fit_result.x, tol=tolerance)
-413            output.iterations = fit_result.nfev
-414        else:
-415            tolerance = 1e-12
-416            if 'tol' in kwargs:
-417                tolerance = kwargs.get('tol')
-418            fit_result = scipy.optimize.minimize(chisqfunc_uncorr, x0, method=kwargs.get('method'), tol=tolerance)
-419            if kwargs.get('correlated_fit') is True:
-420                fit_result = scipy.optimize.minimize(chisqfunc, fit_result.x, method=kwargs.get('method'), tol=tolerance)
-421            output.iterations = fit_result.nit
-422
-423        chisquare = fit_result.fun
+309        n_parms = max(n_parms_ls)
+310
+311    if len(key_ls) > 1:
+312        for key in key_ls:
+313            if np.asarray(yd[key]).shape != funcd[key](np.arange(n_parms), xd[key]).shape:
+314                raise ValueError(f"Fit function {key} returns the wrong shape ({funcd[key](np.arange(n_parms), xd[key]).shape} instead of {np.asarray(yd[key]).shape})\nIf the fit function is just a constant you could try adding x*0 to get the correct shape.")
+315
+316    if not silent:
+317        print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1))
+318
+319    if priors is not None:
+320        if isinstance(priors, (list, np.ndarray)):
+321            if n_parms != len(priors):
+322                raise ValueError("'priors' does not have the correct length.")
+323
+324            loc_priors = []
+325            for i_n, i_prior in enumerate(priors):
+326                loc_priors.append(_construct_prior_obs(i_prior, i_n))
+327
+328            prior_mask = np.arange(len(priors))
+329            output.priors = loc_priors
+330
+331        elif isinstance(priors, dict):
+332            loc_priors = []
+333            prior_mask = []
+334            output.priors = {}
+335            for pos, prior in priors.items():
+336                if isinstance(pos, int):
+337                    prior_mask.append(pos)
+338                else:
+339                    raise TypeError("Prior position needs to be an integer.")
+340                loc_priors.append(_construct_prior_obs(prior, pos))
+341
+342                output.priors[pos] = loc_priors[-1]
+343            if max(prior_mask) >= n_parms:
+344                raise ValueError("Prior position out of range.")
+345        else:
+346            raise TypeError("Unkown type for `priors`.")
+347
+348        p_f = [o.value for o in loc_priors]
+349        dp_f = [o.dvalue for o in loc_priors]
+350        if np.any(np.asarray(dp_f) <= 0.0):
+351            raise Exception("No prior errors available, run the gamma method first.")
+352    else:
+353        p_f = dp_f = np.array([])
+354        prior_mask = []
+355        loc_priors = []
+356
+357    if 'initial_guess' in kwargs:
+358        x0 = kwargs.get('initial_guess')
+359        if len(x0) != n_parms:
+360            raise ValueError(f'Initial guess does not have the correct length: {len(x0)} vs. {n_parms}')
+361    else:
+362        x0 = [0.1] * n_parms
+363
+364    if priors is None:
+365        def general_chisqfunc_uncorr(p, ivars, pr):
+366            model = anp.concatenate([anp.array(funcd[key](p, xd[key])).reshape(-1) for key in key_ls])
+367            return (ivars - model) / dy_f
+368    else:
+369        def general_chisqfunc_uncorr(p, ivars, pr):
+370            model = anp.concatenate([anp.array(funcd[key](p, xd[key])).reshape(-1) for key in key_ls])
+371            return anp.concatenate(((ivars - model) / dy_f, (p[prior_mask] - pr) / dp_f))
+372
+373    def chisqfunc_uncorr(p):
+374        return anp.sum(general_chisqfunc_uncorr(p, y_f, p_f) ** 2)
+375
+376    if kwargs.get('correlated_fit') is True:
+377        if 'inv_chol_cov_matrix' in kwargs:
+378            chol_inv = kwargs.get('inv_chol_cov_matrix')
+379            if (chol_inv[0].shape[0] != len(dy_f)):
+380                raise TypeError('The number of columns of the inverse covariance matrix handed over needs to be equal to the number of y errors.')
+381            if (chol_inv[0].shape[0] != chol_inv[0].shape[1]):
+382                raise TypeError('The inverse covariance matrix handed over needs to have the same number of rows as columns.')
+383            if (chol_inv[1] != key_ls):
+384                raise ValueError('The keys of inverse covariance matrix are not the same or do not appear in the same order as the x and y values.')
+385            chol_inv = chol_inv[0]
+386            if np.any(np.diag(chol_inv) <= 0) or (not np.all(chol_inv == np.tril(chol_inv))):
+387                raise ValueError('The inverse covariance matrix inv_chol_cov_matrix[0] has to be a lower triangular matrix constructed from a Cholesky decomposition.')
+388        else:
+389            corr = covariance(y_all, correlation=True, **kwargs)
+390            inverrdiag = np.diag(1 / np.asarray(dy_f))
+391            chol_inv = invert_corr_cov_cholesky(corr, inverrdiag)
+392
+393        def general_chisqfunc(p, ivars, pr):
+394            model = anp.concatenate([anp.array(funcd[key](p, xd[key])).reshape(-1) for key in key_ls])
+395            return anp.concatenate((anp.dot(chol_inv, (ivars - model)), (p[prior_mask] - pr) / dp_f))
+396
+397        def chisqfunc(p):
+398            return anp.sum(general_chisqfunc(p, y_f, p_f) ** 2)
+399    else:
+400        general_chisqfunc = general_chisqfunc_uncorr
+401        chisqfunc = chisqfunc_uncorr
+402
+403    output.method = kwargs.get('method', 'Levenberg-Marquardt')
+404    if not silent:
+405        print('Method:', output.method)
+406
+407    if output.method != 'Levenberg-Marquardt':
+408        if output.method == 'migrad':
+409            tolerance = 1e-4  # default value of 1e-1 set by iminuit can be problematic
+410            if 'tol' in kwargs:
+411                tolerance = kwargs.get('tol')
+412            fit_result = iminuit.minimize(chisqfunc_uncorr, x0, tol=tolerance)  # Stopping criterion 0.002 * tol * errordef
+413            if kwargs.get('correlated_fit') is True:
+414                fit_result = iminuit.minimize(chisqfunc, fit_result.x, tol=tolerance)
+415            output.iterations = fit_result.nfev
+416        else:
+417            tolerance = 1e-12
+418            if 'tol' in kwargs:
+419                tolerance = kwargs.get('tol')
+420            fit_result = scipy.optimize.minimize(chisqfunc_uncorr, x0, method=kwargs.get('method'), tol=tolerance)
+421            if kwargs.get('correlated_fit') is True:
+422                fit_result = scipy.optimize.minimize(chisqfunc, fit_result.x, method=kwargs.get('method'), tol=tolerance)
+423            output.iterations = fit_result.nit
 424
-425    else:
-426        if 'tol' in kwargs:
-427            print('tol cannot be set for Levenberg-Marquardt')
-428
-429        def chisqfunc_residuals_uncorr(p):
-430            return general_chisqfunc_uncorr(p, y_f, p_f)
-431
-432        fit_result = scipy.optimize.least_squares(chisqfunc_residuals_uncorr, x0, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15)
-433        if kwargs.get('correlated_fit') is True:
-434            def chisqfunc_residuals(p):
-435                return general_chisqfunc(p, y_f, p_f)
-436
-437            fit_result = scipy.optimize.least_squares(chisqfunc_residuals, fit_result.x, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15)
+425        chisquare = fit_result.fun
+426
+427    else:
+428        if 'tol' in kwargs:
+429            print('tol cannot be set for Levenberg-Marquardt')
+430
+431        def chisqfunc_residuals_uncorr(p):
+432            return general_chisqfunc_uncorr(p, y_f, p_f)
+433
+434        fit_result = scipy.optimize.least_squares(chisqfunc_residuals_uncorr, x0, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15)
+435        if kwargs.get('correlated_fit') is True:
+436            def chisqfunc_residuals(p):
+437                return general_chisqfunc(p, y_f, p_f)
 438
-439        chisquare = np.sum(fit_result.fun ** 2)
-440        assert np.isclose(chisquare, chisqfunc(fit_result.x), atol=1e-14)
-441
-442        output.iterations = fit_result.nfev
+439            fit_result = scipy.optimize.least_squares(chisqfunc_residuals, fit_result.x, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15)
+440
+441        chisquare = np.sum(fit_result.fun ** 2)
+442        assert np.isclose(chisquare, chisqfunc(fit_result.x), atol=1e-14)
 443
-444    if not fit_result.success:
-445        raise Exception('The minimization procedure did not converge.')
-446
-447    output.chisquare = chisquare
-448    output.dof = y_all.shape[-1] - n_parms + len(loc_priors)
-449    output.p_value = 1 - scipy.stats.chi2.cdf(output.chisquare, output.dof)
-450    if output.dof > 0:
-451        output.chisquare_by_dof = output.chisquare / output.dof
-452    else:
-453        output.chisquare_by_dof = float('nan')
-454
-455    output.message = fit_result.message
-456    if not silent:
-457        print(fit_result.message)
-458        print('chisquare/d.o.f.:', output.chisquare_by_dof)
-459        print('fit parameters', fit_result.x)
-460
-461    def prepare_hat_matrix():
-462        hat_vector = []
-463        for key in key_ls:
-464            if (len(xd[key]) != 0):
-465                hat_vector.append(jacobian(funcd[key])(fit_result.x, xd[key]))
-466        hat_vector = [item for sublist in hat_vector for item in sublist]
-467        return hat_vector
-468
-469    if kwargs.get('expected_chisquare') is True:
-470        if kwargs.get('correlated_fit') is not True:
-471            W = np.diag(1 / np.asarray(dy_f))
-472            cov = covariance(y_all)
-473            hat_vector = prepare_hat_matrix()
-474            A = W @ hat_vector
-475            P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T
-476            expected_chisquare = np.trace((np.identity(y_all.shape[-1]) - P_phi) @ W @ cov @ W) + len(loc_priors)
-477            output.chisquare_by_expected_chisquare = output.chisquare / expected_chisquare
-478            if not silent:
-479                print('chisquare/expected_chisquare:', output.chisquare_by_expected_chisquare)
-480
-481    fitp = fit_result.x
+444        output.iterations = fit_result.nfev
+445
+446    if not fit_result.success:
+447        raise Exception('The minimization procedure did not converge.')
+448
+449    output.chisquare = chisquare
+450    output.dof = y_all.shape[-1] - n_parms + len(loc_priors)
+451    output.p_value = 1 - scipy.stats.chi2.cdf(output.chisquare, output.dof)
+452    if output.dof > 0:
+453        output.chisquare_by_dof = output.chisquare / output.dof
+454    else:
+455        output.chisquare_by_dof = float('nan')
+456
+457    output.message = fit_result.message
+458    if not silent:
+459        print(fit_result.message)
+460        print('chisquare/d.o.f.:', output.chisquare_by_dof)
+461        print('fit parameters', fit_result.x)
+462
+463    def prepare_hat_matrix():
+464        hat_vector = []
+465        for key in key_ls:
+466            if (len(xd[key]) != 0):
+467                hat_vector.append(jacobian(funcd[key])(fit_result.x, xd[key]))
+468        hat_vector = [item for sublist in hat_vector for item in sublist]
+469        return hat_vector
+470
+471    if kwargs.get('expected_chisquare') is True:
+472        if kwargs.get('correlated_fit') is not True:
+473            W = np.diag(1 / np.asarray(dy_f))
+474            cov = covariance(y_all)
+475            hat_vector = prepare_hat_matrix()
+476            A = W @ hat_vector
+477            P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T
+478            expected_chisquare = np.trace((np.identity(y_all.shape[-1]) - P_phi) @ W @ cov @ W) + len(loc_priors)
+479            output.chisquare_by_expected_chisquare = output.chisquare / expected_chisquare
+480            if not silent:
+481                print('chisquare/expected_chisquare:', output.chisquare_by_expected_chisquare)
 482
-483    try:
-484        hess = hessian(chisqfunc)(fitp)
-485    except (TypeError, ValueError, np.linalg.LinAlgError):
-486        raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None
-487
-488    len_y = len(y_f)
+483    fitp = fit_result.x
+484
+485    try:
+486        hess = hessian(chisqfunc)(fitp)
+487    except (TypeError, ValueError, np.linalg.LinAlgError):
+488        raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None
 489
-490    def chisqfunc_compact(d):
-491        return anp.sum(general_chisqfunc(d[:n_parms], d[n_parms: n_parms + len_y], d[n_parms + len_y:]) ** 2)
-492
-493    jac_jac_y = hessian(chisqfunc_compact)(np.concatenate((fitp, y_f, p_f)))
+490    len_y = len(y_f)
+491
+492    def chisqfunc_compact(d):
+493        return anp.sum(general_chisqfunc(d[:n_parms], d[n_parms: n_parms + len_y], d[n_parms + len_y:]) ** 2)
 494
-495    # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv
-496    try:
-497        deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms, n_parms:])
-498    except np.linalg.LinAlgError:
-499        raise Exception("Cannot invert hessian matrix.")
-500
-501    result = []
-502    for i in range(n_parms):
-503        result.append(derived_observable(lambda x_all, **kwargs: (x_all[0] + np.finfo(np.float64).eps) / (y_all[0].value + np.finfo(np.float64).eps) * fitp[i], list(y_all) + loc_priors, man_grad=list(deriv_y[i])))
-504
-505    output.fit_parameters = result
+495    jac_jac_y = hessian(chisqfunc_compact)(np.concatenate((fitp, y_f, p_f)))
+496
+497    # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv
+498    try:
+499        deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms, n_parms:])
+500    except np.linalg.LinAlgError as err:
+501        raise Exception("Cannot invert hessian matrix.") from err
+502
+503    result = []
+504    for i in range(n_parms):
+505        result.append(derived_observable(lambda x_all, i=i, **kwargs: (x_all[0] + np.finfo(np.float64).eps) / (y_all[0].value + np.finfo(np.float64).eps) * fitp[i], list(y_all) + loc_priors, man_grad=list(deriv_y[i])))
 506
-507    # Hotelling t-squared p-value for correlated fits.
-508    if kwargs.get('correlated_fit') is True:
-509        n_cov = np.min(np.vectorize(lambda x_all: x_all.N)(y_all))
-510        output.t2_p_value = 1 - scipy.stats.f.cdf((n_cov - output.dof) / (output.dof * (n_cov - 1)) * output.chisquare,
-511                                                  output.dof, n_cov - output.dof)
-512
-513    if kwargs.get('resplot') is True:
-514        for key in key_ls:
-515            residual_plot(xd[key], yd[key], funcd[key], result, title=key)
-516
-517    if kwargs.get('qqplot') is True:
-518        for key in key_ls:
-519            qqplot(xd[key], yd[key], funcd[key], result, title=key)
-520
-521    return output
+507    output.fit_parameters = result
+508
+509    # Hotelling t-squared p-value for correlated fits.
+510    if kwargs.get('correlated_fit') is True:
+511        n_cov = np.min(np.vectorize(lambda x_all: x_all.N)(y_all))
+512        output.t2_p_value = 1 - scipy.stats.f.cdf((n_cov - output.dof) / (output.dof * (n_cov - 1)) * output.chisquare,
+513                                                  output.dof, n_cov - output.dof)
+514
+515    if kwargs.get('resplot') is True:
+516        for key in key_ls:
+517            residual_plot(xd[key], yd[key], funcd[key], result, title=key)
+518
+519    if kwargs.get('qqplot') is True:
+520        for key in key_ls:
+521            qqplot(xd[key], yd[key], funcd[key], result, title=key)
+522
+523    return output
 
@@ -1839,248 +1841,248 @@ Parameters and information on the fitted result.
-
524def total_least_squares(x, y, func, silent=False, **kwargs):
-525    r'''Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters.
-526
-527    Parameters
-528    ----------
-529    x : list
-530        list of Obs, or a tuple of lists of Obs
-531    y : list
-532        list of Obs. The dvalues of the Obs are used as x- and yerror for the fit.
-533    func : object
-534        func has to be of the form
-535
-536        ```python
-537        import autograd.numpy as anp
-538
-539        def func(a, x):
-540            return a[0] + a[1] * x + a[2] * anp.sinh(x)
-541        ```
-542
-543        For multiple x values func can be of the form
+            
526def total_least_squares(x, y, func, silent=False, **kwargs):
+527    r'''Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters.
+528
+529    Parameters
+530    ----------
+531    x : list
+532        list of Obs, or a tuple of lists of Obs
+533    y : list
+534        list of Obs. The dvalues of the Obs are used as x- and yerror for the fit.
+535    func : object
+536        func has to be of the form
+537
+538        ```python
+539        import autograd.numpy as anp
+540
+541        def func(a, x):
+542            return a[0] + a[1] * x + a[2] * anp.sinh(x)
+543        ```
 544
-545        ```python
-546        def func(a, x):
-547            (x1, x2) = x
-548            return a[0] * x1 ** 2 + a[1] * x2
-549        ```
-550
-551        It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation
-552        will not work.
-553    silent : bool, optional
-554        If True all output to the console is omitted (default False).
-555    initial_guess : list
-556        can provide an initial guess for the input parameters. Relevant for non-linear
-557        fits with many parameters.
-558    expected_chisquare : bool
-559        If True prints the expected chisquare which is
-560        corrected by effects caused by correlated input data.
-561        This can take a while as the full correlation matrix
-562        has to be calculated (default False).
-563    num_grad : bool
-564        Use numerical differentiation instead of automatic differentiation to perform the error propagation (default False).
-565    n_parms : int, optional
-566        Number of fit parameters. Overrides automatic detection of parameter count.
-567        Useful when autodetection fails. Must match the length of initial_guess (if provided).
-568
-569    Notes
-570    -----
-571    Based on the odrpack orthogonal distance regression library.
-572
-573    Returns
-574    -------
-575    output : Fit_result
-576        Parameters and information on the fitted result.
-577    '''
-578
-579    output = Fit_result()
+545        For multiple x values func can be of the form
+546
+547        ```python
+548        def func(a, x):
+549            (x1, x2) = x
+550            return a[0] * x1 ** 2 + a[1] * x2
+551        ```
+552
+553        It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation
+554        will not work.
+555    silent : bool, optional
+556        If True all output to the console is omitted (default False).
+557    initial_guess : list
+558        can provide an initial guess for the input parameters. Relevant for non-linear
+559        fits with many parameters.
+560    expected_chisquare : bool
+561        If True prints the expected chisquare which is
+562        corrected by effects caused by correlated input data.
+563        This can take a while as the full correlation matrix
+564        has to be calculated (default False).
+565    num_grad : bool
+566        Use numerical differentiation instead of automatic differentiation to perform the error propagation (default False).
+567    n_parms : int, optional
+568        Number of fit parameters. Overrides automatic detection of parameter count.
+569        Useful when autodetection fails. Must match the length of initial_guess (if provided).
+570
+571    Notes
+572    -----
+573    Based on the odrpack orthogonal distance regression library.
+574
+575    Returns
+576    -------
+577    output : Fit_result
+578        Parameters and information on the fitted result.
+579    '''
 580
-581    output.fit_function = func
+581    output = Fit_result()
 582
-583    x = np.array(x)
+583    output.fit_function = func
 584
-585    x_shape = x.shape
+585    x = np.array(x)
 586
-587    if kwargs.get('num_grad') is True:
-588        jacobian = num_jacobian
-589        hessian = num_hessian
-590    else:
-591        jacobian = auto_jacobian
-592        hessian = auto_hessian
-593
-594    if not callable(func):
-595        raise TypeError('func has to be a function.')
-596
-597    if 'n_parms' in kwargs:
-598        n_parms = kwargs.get('n_parms')
-599        if not isinstance(n_parms, int):
-600            raise TypeError(
-601                f"'n_parms' must be an integer, got {n_parms!r} "
-602                f"of type {type(n_parms).__name__}."
-603            )
-604        if n_parms <= 0:
-605            raise ValueError(
-606                f"'n_parms' must be a positive integer, got {n_parms}."
-607            )
-608    else:
-609        for i in range(100):
-610            try:
-611                func(np.arange(i), x.T[0])
-612            except TypeError:
-613                continue
-614            except IndexError:
+587    x_shape = x.shape
+588
+589    if kwargs.get('num_grad') is True:
+590        jacobian = num_jacobian
+591        hessian = num_hessian
+592    else:
+593        jacobian = auto_jacobian
+594        hessian = auto_hessian
+595
+596    if not callable(func):
+597        raise TypeError('func has to be a function.')
+598
+599    if 'n_parms' in kwargs:
+600        n_parms = kwargs.get('n_parms')
+601        if not isinstance(n_parms, int):
+602            raise TypeError(
+603                f"'n_parms' must be an integer, got {n_parms!r} "
+604                f"of type {type(n_parms).__name__}."
+605            )
+606        if n_parms <= 0:
+607            raise ValueError(
+608                f"'n_parms' must be a positive integer, got {n_parms}."
+609            )
+610    else:
+611        for i in range(100):
+612            try:
+613                func(np.arange(i), x.T[0])
+614            except TypeError:
 615                continue
-616            else:
-617                break
-618        else:
-619            raise RuntimeError("Fit function is not valid.")
-620
-621        n_parms = i
+616            except IndexError:
+617                continue
+618            else:
+619                break
+620        else:
+621            raise RuntimeError("Fit function is not valid.")
 622
-623    if not silent:
-624        print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1))
-625
-626    x_f = np.vectorize(lambda o: o.value)(x)
-627    dx_f = np.vectorize(lambda o: o.dvalue)(x)
-628    y_f = np.array([o.value for o in y])
-629    dy_f = np.array([o.dvalue for o in y])
-630
-631    if np.any(np.asarray(dx_f) <= 0.0):
-632        raise Exception('No x errors available, run the gamma method first.')
-633
-634    if np.any(np.asarray(dy_f) <= 0.0):
-635        raise Exception('No y errors available, run the gamma method first.')
-636
-637    if 'initial_guess' in kwargs:
-638        x0 = np.asarray(kwargs.get('initial_guess'), dtype=np.float64)
-639        if len(x0) != n_parms:
-640            raise Exception('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms))
-641    else:
-642        x0 = np.ones(n_parms, dtype=np.float64)
-643
-644    # odrpack expects f(x, beta), but pyerrors convention is f(beta, x)
-645    def wrapped_func(x, beta):
-646        return func(beta, x)
-647
-648    out = odr_fit(
-649        wrapped_func,
-650        np.asarray(x_f, dtype=np.float64),
-651        np.asarray(y_f, dtype=np.float64),
-652        beta0=x0,
-653        weight_x=1.0 / np.asarray(dx_f, dtype=np.float64) ** 2,
-654        weight_y=1.0 / np.asarray(dy_f, dtype=np.float64) ** 2,
-655        partol=np.finfo(np.float64).eps,
-656        task='explicit-ODR',
-657        diff_scheme='central'
-658    )
-659
-660    output.residual_variance = out.res_var
+623        n_parms = i
+624
+625    if not silent:
+626        print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1))
+627
+628    x_f = np.vectorize(lambda o: o.value)(x)
+629    dx_f = np.vectorize(lambda o: o.dvalue)(x)
+630    y_f = np.array([o.value for o in y])
+631    dy_f = np.array([o.dvalue for o in y])
+632
+633    if np.any(np.asarray(dx_f) <= 0.0):
+634        raise Exception('No x errors available, run the gamma method first.')
+635
+636    if np.any(np.asarray(dy_f) <= 0.0):
+637        raise Exception('No y errors available, run the gamma method first.')
+638
+639    if 'initial_guess' in kwargs:
+640        x0 = np.asarray(kwargs.get('initial_guess'), dtype=np.float64)
+641        if len(x0) != n_parms:
+642            raise Exception(f'Initial guess does not have the correct length: {len(x0)} vs. {n_parms}')
+643    else:
+644        x0 = np.ones(n_parms, dtype=np.float64)
+645
+646    # odrpack expects f(x, beta), but pyerrors convention is f(beta, x)
+647    def wrapped_func(x, beta):
+648        return func(beta, x)
+649
+650    out = odr_fit(
+651        wrapped_func,
+652        np.asarray(x_f, dtype=np.float64),
+653        np.asarray(y_f, dtype=np.float64),
+654        beta0=x0,
+655        weight_x=1.0 / np.asarray(dx_f, dtype=np.float64) ** 2,
+656        weight_y=1.0 / np.asarray(dy_f, dtype=np.float64) ** 2,
+657        partol=np.finfo(np.float64).eps,
+658        task='explicit-ODR',
+659        diff_scheme='central'
+660    )
 661
-662    output.method = 'ODR'
+662    output.residual_variance = out.res_var
 663
-664    output.message = out.stopreason
+664    output.method = 'ODR'
 665
-666    output.xplus = out.xplusd
+666    output.message = out.stopreason
 667
-668    if not silent:
-669        print('Method: ODR')
-670        print(out.stopreason)
-671        print('Residual variance:', output.residual_variance)
-672
-673    if not out.success:
-674        # ODRPACK95 info code structure (see User Guide §4):
-675        #   info % 10        -> convergence: 1=sum-of-sq, 2=param, 3=both
-676        #   info // 10 % 10  -> 1 = problem not full rank at solution
-677        convergence_status = out.info % 10
-678        rank_deficient = (out.info // 10 % 10) == 1
-679
-680        if convergence_status in [1, 2, 3] and rank_deficient:
-681            warnings.warn(
-682                f"ODR fit is rank deficient (irank={out.irank}, inv_condnum={out.inv_condnum:.2e}). "
-683                "This may indicate a vanishing chi-squared (n_obs == n_parms). "
-684                "Results may be unreliable.",
-685                RuntimeWarning
-686            )
-687        else:
-688            raise Exception('The minimization procedure did not converge.')
-689
-690    m = x_f.size
+668    output.xplus = out.xplusd
+669
+670    if not silent:
+671        print('Method: ODR')
+672        print(out.stopreason)
+673        print('Residual variance:', output.residual_variance)
+674
+675    if not out.success:
+676        # ODRPACK95 info code structure (see User Guide §4):
+677        #   info % 10        -> convergence: 1=sum-of-sq, 2=param, 3=both
+678        #   info // 10 % 10  -> 1 = problem not full rank at solution
+679        convergence_status = out.info % 10
+680        rank_deficient = (out.info // 10 % 10) == 1
+681
+682        if convergence_status in [1, 2, 3] and rank_deficient:
+683            warnings.warn(
+684                f"ODR fit is rank deficient (irank={out.irank}, inv_condnum={out.inv_condnum:.2e}). "
+685                "This may indicate a vanishing chi-squared (n_obs == n_parms). "
+686                "Results may be unreliable.",
+687                RuntimeWarning, stacklevel=2
+688            )
+689        else:
+690            raise Exception('The minimization procedure did not converge.')
 691
-692    def odr_chisquare(p):
-693        model = func(p[:n_parms], p[n_parms:].reshape(x_shape))
-694        chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((x_f - p[n_parms:].reshape(x_shape)) / dx_f) ** 2)
-695        return chisq
-696
-697    if kwargs.get('expected_chisquare') is True:
-698        W = np.diag(1 / np.asarray(np.concatenate((dy_f.ravel(), dx_f.ravel()))))
-699
-700        if kwargs.get('covariance') is not None:
-701            cov = kwargs.get('covariance')
-702        else:
-703            cov = covariance(np.concatenate((y, x.ravel())))
-704
-705        number_of_x_parameters = int(m / x_f.shape[-1])
+692    m = x_f.size
+693
+694    def odr_chisquare(p):
+695        model = func(p[:n_parms], p[n_parms:].reshape(x_shape))
+696        chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((x_f - p[n_parms:].reshape(x_shape)) / dx_f) ** 2)
+697        return chisq
+698
+699    if kwargs.get('expected_chisquare') is True:
+700        W = np.diag(1 / np.asarray(np.concatenate((dy_f.ravel(), dx_f.ravel()))))
+701
+702        if kwargs.get('covariance') is not None:
+703            cov = kwargs.get('covariance')
+704        else:
+705            cov = covariance(np.concatenate((y, x.ravel())))
 706
-707        old_jac = jacobian(func)(out.beta, out.xplusd)
-708        fused_row1 = np.concatenate((old_jac, np.concatenate((number_of_x_parameters * [np.zeros(old_jac.shape)]), axis=0)))
-709        fused_row2 = np.concatenate((jacobian(lambda x, y: func(y, x))(out.xplusd, out.beta).reshape(x_f.shape[-1], x_f.shape[-1] * number_of_x_parameters), np.identity(number_of_x_parameters * old_jac.shape[0])))
-710        new_jac = np.concatenate((fused_row1, fused_row2), axis=1)
-711
-712        A = W @ new_jac
-713        P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T
-714        expected_chisquare = np.trace((np.identity(P_phi.shape[0]) - P_phi) @ W @ cov @ W)
-715        if expected_chisquare <= 0.0:
-716            warnings.warn("Negative expected_chisquare.", RuntimeWarning)
-717            expected_chisquare = np.abs(expected_chisquare)
-718        output.chisquare_by_expected_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplusd.ravel()))) / expected_chisquare
-719        if not silent:
-720            print('chisquare/expected_chisquare:',
-721                  output.chisquare_by_expected_chisquare)
-722
-723    fitp = out.beta
-724    try:
-725        hess = hessian(odr_chisquare)(np.concatenate((fitp, out.xplusd.ravel())))
-726    except (TypeError, ValueError, np.linalg.LinAlgError):
-727        raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None
-728
-729    def odr_chisquare_compact_x(d):
-730        model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape))
-731        chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((d[n_parms + m:].reshape(x_shape) - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2)
-732        return chisq
-733
-734    jac_jac_x = hessian(odr_chisquare_compact_x)(np.concatenate((fitp, out.xplusd.ravel(), x_f.ravel())))
+707        number_of_x_parameters = int(m / x_f.shape[-1])
+708
+709        old_jac = jacobian(func)(out.beta, out.xplusd)
+710        fused_row1 = np.concatenate((old_jac, np.concatenate((number_of_x_parameters * [np.zeros(old_jac.shape)]), axis=0)))
+711        fused_row2 = np.concatenate((jacobian(lambda x, y: func(y, x))(out.xplusd, out.beta).reshape(x_f.shape[-1], x_f.shape[-1] * number_of_x_parameters), np.identity(number_of_x_parameters * old_jac.shape[0])))
+712        new_jac = np.concatenate((fused_row1, fused_row2), axis=1)
+713
+714        A = W @ new_jac
+715        P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T
+716        expected_chisquare = np.trace((np.identity(P_phi.shape[0]) - P_phi) @ W @ cov @ W)
+717        if expected_chisquare <= 0.0:
+718            warnings.warn("Negative expected_chisquare.", RuntimeWarning, stacklevel=2)
+719            expected_chisquare = np.abs(expected_chisquare)
+720        output.chisquare_by_expected_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplusd.ravel()))) / expected_chisquare
+721        if not silent:
+722            print('chisquare/expected_chisquare:',
+723                  output.chisquare_by_expected_chisquare)
+724
+725    fitp = out.beta
+726    try:
+727        hess = hessian(odr_chisquare)(np.concatenate((fitp, out.xplusd.ravel())))
+728    except (TypeError, ValueError, np.linalg.LinAlgError):
+729        raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None
+730
+731    def odr_chisquare_compact_x(d):
+732        model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape))
+733        chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((d[n_parms + m:].reshape(x_shape) - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2)
+734        return chisq
 735
-736    # Compute hess^{-1} @ jac_jac_x[:n_parms + m, n_parms + m:] using LAPACK dgesv
-737    try:
-738        deriv_x = -scipy.linalg.solve(hess, jac_jac_x[:n_parms + m, n_parms + m:])
-739    except np.linalg.LinAlgError:
-740        raise Exception("Cannot invert hessian matrix.")
-741
-742    def odr_chisquare_compact_y(d):
-743        model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape))
-744        chisq = anp.sum(((d[n_parms + m:] - model) / dy_f) ** 2) + anp.sum(((x_f - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2)
-745        return chisq
-746
-747    jac_jac_y = hessian(odr_chisquare_compact_y)(np.concatenate((fitp, out.xplusd.ravel(), y_f)))
+736    jac_jac_x = hessian(odr_chisquare_compact_x)(np.concatenate((fitp, out.xplusd.ravel(), x_f.ravel())))
+737
+738    # Compute hess^{-1} @ jac_jac_x[:n_parms + m, n_parms + m:] using LAPACK dgesv
+739    try:
+740        deriv_x = -scipy.linalg.solve(hess, jac_jac_x[:n_parms + m, n_parms + m:])
+741    except np.linalg.LinAlgError as err:
+742        raise Exception("Cannot invert hessian matrix.") from err
+743
+744    def odr_chisquare_compact_y(d):
+745        model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape))
+746        chisq = anp.sum(((d[n_parms + m:] - model) / dy_f) ** 2) + anp.sum(((x_f - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2)
+747        return chisq
 748
-749    # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv
-750    try:
-751        deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms + m, n_parms + m:])
-752    except np.linalg.LinAlgError:
-753        raise Exception("Cannot invert hessian matrix.")
-754
-755    result = []
-756    for i in range(n_parms):
-757        result.append(derived_observable(lambda my_var, **kwargs: (my_var[0] + np.finfo(np.float64).eps) / (x.ravel()[0].value + np.finfo(np.float64).eps) * out.beta[i], list(x.ravel()) + list(y), man_grad=list(deriv_x[i]) + list(deriv_y[i])))
-758
-759    output.fit_parameters = result
+749    jac_jac_y = hessian(odr_chisquare_compact_y)(np.concatenate((fitp, out.xplusd.ravel(), y_f)))
+750
+751    # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv
+752    try:
+753        deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms + m, n_parms + m:])
+754    except np.linalg.LinAlgError as err:
+755        raise Exception("Cannot invert hessian matrix.") from err
+756
+757    result = []
+758    for i in range(n_parms):
+759        result.append(derived_observable(lambda my_var, i=i, **kwargs: (my_var[0] + np.finfo(np.float64).eps) / (x.ravel()[0].value + np.finfo(np.float64).eps) * out.beta[i], list(x.ravel()) + list(y), man_grad=list(deriv_x[i]) + list(deriv_y[i])))
 760
-761    output.odr_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplusd.ravel())))
-762    output.dof = x.shape[-1] - n_parms
-763    output.p_value = 1 - scipy.stats.chi2.cdf(output.odr_chisquare, output.dof)
-764
-765    return output
+761    output.fit_parameters = result
+762
+763    output.odr_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplusd.ravel())))
+764    output.dof = x.shape[-1] - n_parms
+765    output.p_value = 1 - scipy.stats.chi2.cdf(output.odr_chisquare, output.dof)
+766
+767    return output
 
@@ -2157,35 +2159,35 @@ Parameters and information on the fitted result.
-
768def fit_lin(x, y, **kwargs):
-769    """Performs a linear fit to y = n + m * x and returns two Obs n, m.
-770
-771    Parameters
-772    ----------
-773    x : list
-774        Can either be a list of floats in which case no xerror is assumed, or
-775        a list of Obs, where the dvalues of the Obs are used as xerror for the fit.
-776    y : list
-777        List of Obs, the dvalues of the Obs are used as yerror for the fit.
-778
-779    Returns
-780    -------
-781    fit_parameters : list[Obs]
-782        LIist of fitted observables.
-783    """
-784
-785    def f(a, x):
-786        y = a[0] + a[1] * x
-787        return y
-788
-789    if all(isinstance(n, Obs) for n in x):
-790        out = total_least_squares(x, y, f, **kwargs)
-791        return out.fit_parameters
-792    elif all(isinstance(n, float) or isinstance(n, int) for n in x) or isinstance(x, np.ndarray):
-793        out = least_squares(x, y, f, **kwargs)
-794        return out.fit_parameters
-795    else:
-796        raise TypeError('Unsupported types for x')
+            
770def fit_lin(x, y, **kwargs):
+771    """Performs a linear fit to y = n + m * x and returns two Obs n, m.
+772
+773    Parameters
+774    ----------
+775    x : list
+776        Can either be a list of floats in which case no xerror is assumed, or
+777        a list of Obs, where the dvalues of the Obs are used as xerror for the fit.
+778    y : list
+779        List of Obs, the dvalues of the Obs are used as yerror for the fit.
+780
+781    Returns
+782    -------
+783    fit_parameters : list[Obs]
+784        LIist of fitted observables.
+785    """
+786
+787    def f(a, x):
+788        y = a[0] + a[1] * x
+789        return y
+790
+791    if all(isinstance(n, Obs) for n in x):
+792        out = total_least_squares(x, y, f, **kwargs)
+793        return out.fit_parameters
+794    elif all(isinstance(n, float) or isinstance(n, int) for n in x) or isinstance(x, np.ndarray):
+795        out = least_squares(x, y, f, **kwargs)
+796        return out.fit_parameters
+797    else:
+798        raise TypeError('Unsupported types for x')
 
@@ -2222,34 +2224,34 @@ LIist of fitted observables.
-
799def qqplot(x, o_y, func, p, title=""):
-800    """Generates a quantile-quantile plot of the fit result which can be used to
-801       check if the residuals of the fit are gaussian distributed.
-802
-803    Returns
-804    -------
-805    None
-806    """
-807
-808    residuals = []
-809    for i_x, i_y in zip(x, o_y):
-810        residuals.append((i_y - func(p, i_x)) / i_y.dvalue)
-811    residuals = sorted(residuals)
-812    my_y = [o.value for o in residuals]
-813    probplot = scipy.stats.probplot(my_y)
-814    my_x = probplot[0][0]
-815    plt.figure(figsize=(8, 8 / 1.618))
-816    plt.errorbar(my_x, my_y, fmt='o')
-817    fit_start = my_x[0]
-818    fit_stop = my_x[-1]
-819    samples = np.arange(fit_start, fit_stop, 0.01)
-820    plt.plot(samples, samples, 'k--', zorder=11, label='Standard normal distribution')
-821    plt.plot(samples, probplot[1][0] * samples + probplot[1][1], zorder=10, label='Least squares fit, r=' + str(np.around(probplot[1][2], 3)), marker='', ls='-')
-822
-823    plt.xlabel('Theoretical quantiles')
-824    plt.ylabel('Ordered Values')
-825    plt.legend(title=title)
-826    plt.draw()
+            
801def qqplot(x, o_y, func, p, title=""):
+802    """Generates a quantile-quantile plot of the fit result which can be used to
+803       check if the residuals of the fit are gaussian distributed.
+804
+805    Returns
+806    -------
+807    None
+808    """
+809
+810    residuals = []
+811    for i_x, i_y in zip(x, o_y, strict=True):
+812        residuals.append((i_y - func(p, i_x)) / i_y.dvalue)
+813    residuals = sorted(residuals)
+814    my_y = [o.value for o in residuals]
+815    probplot = scipy.stats.probplot(my_y)
+816    my_x = probplot[0][0]
+817    plt.figure(figsize=(8, 8 / 1.618))
+818    plt.errorbar(my_x, my_y, fmt='o')
+819    fit_start = my_x[0]
+820    fit_stop = my_x[-1]
+821    samples = np.arange(fit_start, fit_stop, 0.01)
+822    plt.plot(samples, samples, 'k--', zorder=11, label='Standard normal distribution')
+823    plt.plot(samples, probplot[1][0] * samples + probplot[1][1], zorder=10, label='Least squares fit, r=' + str(np.around(probplot[1][2], 3)), marker='', ls='-')
+824
+825    plt.xlabel('Theoretical quantiles')
+826    plt.ylabel('Ordered Values')
+827    plt.legend(title=title)
+828    plt.draw()
 
@@ -2276,41 +2278,41 @@ LIist of fitted observables.
-
829def residual_plot(x, y, func, fit_res, title=""):
-830    """Generates a plot which compares the fit to the data and displays the corresponding residuals
-831
-832    For uncorrelated data the residuals are expected to be distributed ~N(0,1).
+            
831def residual_plot(x, y, func, fit_res, title=""):
+832    """Generates a plot which compares the fit to the data and displays the corresponding residuals
 833
-834    Returns
-835    -------
-836    None
-837    """
-838    sorted_x = sorted(x)
-839    xstart = sorted_x[0] - 0.5 * (sorted_x[1] - sorted_x[0])
-840    xstop = sorted_x[-1] + 0.5 * (sorted_x[-1] - sorted_x[-2])
-841    x_samples = np.arange(xstart, xstop + 0.01, 0.01)
-842
-843    plt.figure(figsize=(8, 8 / 1.618))
-844    gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0)
-845    ax0 = plt.subplot(gs[0])
-846    ax0.errorbar(x, [o.value for o in y], yerr=[o.dvalue for o in y], ls='none', fmt='o', capsize=3, markersize=5, label='Data')
-847    ax0.plot(x_samples, func([o.value for o in fit_res], x_samples), label='Fit', zorder=10, ls='-', ms=0)
-848    ax0.set_xticklabels([])
-849    ax0.set_xlim([xstart, xstop])
+834    For uncorrelated data the residuals are expected to be distributed ~N(0,1).
+835
+836    Returns
+837    -------
+838    None
+839    """
+840    sorted_x = sorted(x)
+841    xstart = sorted_x[0] - 0.5 * (sorted_x[1] - sorted_x[0])
+842    xstop = sorted_x[-1] + 0.5 * (sorted_x[-1] - sorted_x[-2])
+843    x_samples = np.arange(xstart, xstop + 0.01, 0.01)
+844
+845    plt.figure(figsize=(8, 8 / 1.618))
+846    gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0)
+847    ax0 = plt.subplot(gs[0])
+848    ax0.errorbar(x, [o.value for o in y], yerr=[o.dvalue for o in y], ls='none', fmt='o', capsize=3, markersize=5, label='Data')
+849    ax0.plot(x_samples, func([o.value for o in fit_res], x_samples), label='Fit', zorder=10, ls='-', ms=0)
 850    ax0.set_xticklabels([])
-851    ax0.legend(title=title)
-852
-853    residuals = (np.asarray([o.value for o in y]) - func([o.value for o in fit_res], np.asarray(x))) / np.asarray([o.dvalue for o in y])
-854    ax1 = plt.subplot(gs[1])
-855    ax1.plot(x, residuals, 'ko', ls='none', markersize=5)
-856    ax1.tick_params(direction='out')
-857    ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True)
-858    ax1.axhline(y=0.0, ls='--', color='k', marker=" ")
-859    ax1.fill_between(x_samples, -1.0, 1.0, alpha=0.1, facecolor='k')
-860    ax1.set_xlim([xstart, xstop])
-861    ax1.set_ylabel('Residuals')
-862    plt.subplots_adjust(wspace=None, hspace=None)
-863    plt.draw()
+851    ax0.set_xlim([xstart, xstop])
+852    ax0.set_xticklabels([])
+853    ax0.legend(title=title)
+854
+855    residuals = (np.asarray([o.value for o in y]) - func([o.value for o in fit_res], np.asarray(x))) / np.asarray([o.dvalue for o in y])
+856    ax1 = plt.subplot(gs[1])
+857    ax1.plot(x, residuals, 'ko', ls='none', markersize=5)
+858    ax1.tick_params(direction='out')
+859    ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True)
+860    ax1.axhline(y=0.0, ls='--', color='k', marker=" ")
+861    ax1.fill_between(x_samples, -1.0, 1.0, alpha=0.1, facecolor='k')
+862    ax1.set_xlim([xstart, xstop])
+863    ax1.set_ylabel('Residuals')
+864    plt.subplots_adjust(wspace=None, hspace=None)
+865    plt.draw()
 
@@ -2338,28 +2340,28 @@ LIist of fitted observables.
-
866def error_band(x, func, beta):
-867    """Calculate the error band for an array of sample values x, for given fit function func with optimized parameters beta.
-868
-869    Returns
-870    -------
-871    err : np.array(Obs)
-872        Error band for an array of sample values x
-873    """
-874    cov = covariance(beta)
-875    if np.any(np.abs(cov - cov.T) > 1000 * np.finfo(np.float64).eps):
-876        warnings.warn("Covariance matrix is not symmetric within floating point precision", RuntimeWarning)
-877
-878    deriv = []
-879    for i, item in enumerate(x):
-880        deriv.append(np.array(egrad(func)([o.value for o in beta], item)))
-881
-882    err = []
-883    for i, item in enumerate(x):
-884        err.append(np.sqrt(deriv[i] @ cov @ deriv[i]))
-885    err = np.array(err)
-886
-887    return err
+            
868def error_band(x, func, beta):
+869    """Calculate the error band for an array of sample values x, for given fit function func with optimized parameters beta.
+870
+871    Returns
+872    -------
+873    err : np.array(Obs)
+874        Error band for an array of sample values x
+875    """
+876    cov = covariance(beta)
+877    if np.any(np.abs(cov - cov.T) > 1000 * np.finfo(np.float64).eps):
+878        warnings.warn("Covariance matrix is not symmetric within floating point precision", RuntimeWarning, stacklevel=2)
+879
+880    deriv = []
+881    for item in x:
+882        deriv.append(np.array(egrad(func)([o.value for o in beta], item)))
+883
+884    err = []
+885    for i, _item in enumerate(x):
+886        err.append(np.sqrt(deriv[i] @ cov @ deriv[i]))
+887    err = np.array(err)
+888
+889    return err
 
@@ -2386,48 +2388,48 @@ Error band for an array of sample values x
-
890def ks_test(objects=None):
-891    """Performs a Kolmogorov–Smirnov test for the p-values of all fit object.
-892
-893    Parameters
-894    ----------
-895    objects : list
-896        List of fit results to include in the analysis (optional).
-897
-898    Returns
-899    -------
-900    None
-901    """
-902
-903    if objects is None:
-904        obs_list = []
-905        for obj in gc.get_objects():
-906            if isinstance(obj, Fit_result):
-907                obs_list.append(obj)
-908    else:
-909        obs_list = objects
-910
-911    p_values = [o.p_value for o in obs_list]
+            
892def ks_test(objects=None):
+893    """Performs a Kolmogorov–Smirnov test for the p-values of all fit object.
+894
+895    Parameters
+896    ----------
+897    objects : list
+898        List of fit results to include in the analysis (optional).
+899
+900    Returns
+901    -------
+902    None
+903    """
+904
+905    if objects is None:
+906        obs_list = []
+907        for obj in gc.get_objects():
+908            if isinstance(obj, Fit_result):
+909                obs_list.append(obj)
+910    else:
+911        obs_list = objects
 912
-913    bins = len(p_values)
-914    x = np.arange(0, 1.001, 0.001)
-915    plt.plot(x, x, 'k', zorder=1)
-916    plt.xlim(0, 1)
-917    plt.ylim(0, 1)
-918    plt.xlabel('p-value')
-919    plt.ylabel('Cumulative probability')
-920    plt.title(str(bins) + ' p-values')
-921
-922    n = np.arange(1, bins + 1) / np.float64(bins)
-923    Xs = np.sort(p_values)
-924    plt.step(Xs, n)
-925    diffs = n - Xs
-926    loc_max_diff = np.argmax(np.abs(diffs))
-927    loc = Xs[loc_max_diff]
-928    plt.annotate('', xy=(loc, loc), xytext=(loc, loc + diffs[loc_max_diff]), arrowprops=dict(arrowstyle='<->', shrinkA=0, shrinkB=0))
-929    plt.draw()
-930
-931    print(scipy.stats.kstest(p_values, 'uniform'))
+913    p_values = [o.p_value for o in obs_list]
+914
+915    bins = len(p_values)
+916    x = np.arange(0, 1.001, 0.001)
+917    plt.plot(x, x, 'k', zorder=1)
+918    plt.xlim(0, 1)
+919    plt.ylim(0, 1)
+920    plt.xlabel('p-value')
+921    plt.ylabel('Cumulative probability')
+922    plt.title(str(bins) + ' p-values')
+923
+924    n = np.arange(1, bins + 1) / np.float64(bins)
+925    Xs = np.sort(p_values)
+926    plt.step(Xs, n)
+927    diffs = n - Xs
+928    loc_max_diff = np.argmax(np.abs(diffs))
+929    loc = Xs[loc_max_diff]
+930    plt.annotate('', xy=(loc, loc), xytext=(loc, loc + diffs[loc_max_diff]), arrowprops=dict(arrowstyle='<->', shrinkA=0, shrinkB=0))
+931    plt.draw()
+932
+933    print(scipy.stats.kstest(p_values, 'uniform'))
 
diff --git a/docs/pyerrors/input/bdio.html b/docs/pyerrors/input/bdio.html index 7e769d65..40228ea5 100644 --- a/docs/pyerrors/input/bdio.html +++ b/docs/pyerrors/input/bdio.html @@ -87,698 +87,700 @@
  1import ctypes
   2import hashlib
-  3import autograd.numpy as np  # Thinly-wrapped numpy
-  4from ..obs import Obs
+  3
+  4import autograd.numpy as np  # Thinly-wrapped numpy
   5
-  6
-  7def read_ADerrors(file_path, bdio_path='./libbdio.so', **kwargs):
-  8    """ Extract generic MCMC data from a bdio file
-  9
- 10    read_ADerrors requires bdio to be compiled into a shared library. This can be achieved by
- 11    adding the flag -fPIC to CC and changing the all target to
- 12
- 13    all:		bdio.o $(LIBDIR)
- 14                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
- 15                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
- 16
- 17    Parameters
- 18    ----------
- 19    file_path -- path to the bdio file
- 20    bdio_path -- path to the shared bdio library libbdio.so (default ./libbdio.so)
- 21
- 22    Returns
- 23    -------
- 24    data : List[Obs]
- 25        Extracted data
- 26    """
- 27    bdio = ctypes.cdll.LoadLibrary(bdio_path)
- 28
- 29    bdio_open = bdio.bdio_open
- 30    bdio_open.restype = ctypes.c_void_p
- 31
- 32    bdio_close = bdio.bdio_close
- 33    bdio_close.restype = ctypes.c_int
- 34    bdio_close.argtypes = [ctypes.c_void_p]
- 35
- 36    bdio_seek_record = bdio.bdio_seek_record
- 37    bdio_seek_record.restype = ctypes.c_int
- 38    bdio_seek_record.argtypes = [ctypes.c_void_p]
- 39
- 40    bdio_get_rlen = bdio.bdio_get_rlen
- 41    bdio_get_rlen.restype = ctypes.c_int
- 42    bdio_get_rlen.argtypes = [ctypes.c_void_p]
- 43
- 44    bdio_get_ruinfo = bdio.bdio_get_ruinfo
- 45    bdio_get_ruinfo.restype = ctypes.c_int
- 46    bdio_get_ruinfo.argtypes = [ctypes.c_void_p]
- 47
- 48    bdio_read = bdio.bdio_read
- 49    bdio_read.restype = ctypes.c_size_t
- 50    bdio_read.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
- 51
- 52    bdio_read_f64 = bdio.bdio_read_f64
- 53    bdio_read_f64.restype = ctypes.c_size_t
- 54    bdio_read_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
- 55
- 56    bdio_read_int32 = bdio.bdio_read_int32
- 57    bdio_read_int32.restype = ctypes.c_size_t
- 58    bdio_read_int32.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
- 59
- 60    b_path = file_path.encode('utf-8')
- 61    read = 'r'
- 62    b_read = read.encode('utf-8')
- 63
- 64    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_read), None)
+  6from ..obs import Obs
+  7
+  8
+  9def read_ADerrors(file_path, bdio_path='./libbdio.so', **kwargs):
+ 10    """ Extract generic MCMC data from a bdio file
+ 11
+ 12    read_ADerrors requires bdio to be compiled into a shared library. This can be achieved by
+ 13    adding the flag -fPIC to CC and changing the all target to
+ 14
+ 15    all:		bdio.o $(LIBDIR)
+ 16                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
+ 17                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
+ 18
+ 19    Parameters
+ 20    ----------
+ 21    file_path -- path to the bdio file
+ 22    bdio_path -- path to the shared bdio library libbdio.so (default ./libbdio.so)
+ 23
+ 24    Returns
+ 25    -------
+ 26    data : List[Obs]
+ 27        Extracted data
+ 28    """
+ 29    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+ 30
+ 31    bdio_open = bdio.bdio_open
+ 32    bdio_open.restype = ctypes.c_void_p
+ 33
+ 34    bdio_close = bdio.bdio_close
+ 35    bdio_close.restype = ctypes.c_int
+ 36    bdio_close.argtypes = [ctypes.c_void_p]
+ 37
+ 38    bdio_seek_record = bdio.bdio_seek_record
+ 39    bdio_seek_record.restype = ctypes.c_int
+ 40    bdio_seek_record.argtypes = [ctypes.c_void_p]
+ 41
+ 42    bdio_get_rlen = bdio.bdio_get_rlen
+ 43    bdio_get_rlen.restype = ctypes.c_int
+ 44    bdio_get_rlen.argtypes = [ctypes.c_void_p]
+ 45
+ 46    bdio_get_ruinfo = bdio.bdio_get_ruinfo
+ 47    bdio_get_ruinfo.restype = ctypes.c_int
+ 48    bdio_get_ruinfo.argtypes = [ctypes.c_void_p]
+ 49
+ 50    bdio_read = bdio.bdio_read
+ 51    bdio_read.restype = ctypes.c_size_t
+ 52    bdio_read.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
+ 53
+ 54    bdio_read_f64 = bdio.bdio_read_f64
+ 55    bdio_read_f64.restype = ctypes.c_size_t
+ 56    bdio_read_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
+ 57
+ 58    bdio_read_int32 = bdio.bdio_read_int32
+ 59    bdio_read_int32.restype = ctypes.c_size_t
+ 60    bdio_read_int32.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
+ 61
+ 62    b_path = file_path.encode('utf-8')
+ 63    read = 'r'
+ 64    b_read = read.encode('utf-8')
  65
- 66    return_list = []
+ 66    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_read), None)
  67
- 68    print('Reading of bdio file started')
- 69    while True:
- 70        bdio_seek_record(fbdio)
- 71        ruinfo = bdio_get_ruinfo(fbdio)
- 72
- 73        if ruinfo == 7:
- 74            print('MD5sum found')  # For now we just ignore these entries and do not perform any checks on them
- 75            continue
- 76
- 77        if ruinfo < 0:
- 78            # EOF reached
- 79            break
- 80        bdio_get_rlen(fbdio)
- 81
- 82        def read_c_double():
- 83            d_buf = ctypes.c_double
- 84            pd_buf = d_buf()
- 85            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
- 86            bdio_read_f64(ppd_buf, ctypes.c_size_t(8), ctypes.c_void_p(fbdio))
- 87            return pd_buf.value
- 88
- 89        mean = read_c_double()
- 90        print('mean', mean)
- 91
- 92        def read_c_size_t():
- 93            d_buf = ctypes.c_size_t
- 94            pd_buf = d_buf()
- 95            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
- 96            bdio_read_int32(ppd_buf, ctypes.c_size_t(4), ctypes.c_void_p(fbdio))
- 97            return pd_buf.value
- 98
- 99        neid = read_c_size_t()
-100        print('neid', neid)
-101
-102        ndata = []
-103        for index in range(neid):
-104            ndata.append(read_c_size_t())
-105        print('ndata', ndata)
-106
-107        nrep = []
-108        for index in range(neid):
-109            nrep.append(read_c_size_t())
-110        print('nrep', nrep)
-111
-112        vrep = []
-113        for index in range(neid):
-114            vrep.append([])
-115            for jndex in range(nrep[index]):
-116                vrep[-1].append(read_c_size_t())
-117        print('vrep', vrep)
-118
-119        ids = []
-120        for index in range(neid):
-121            ids.append(read_c_size_t())
-122        print('ids', ids)
-123
-124        nt = []
-125        for index in range(neid):
-126            nt.append(read_c_size_t())
-127        print('nt', nt)
-128
-129        zero = []
-130        for index in range(neid):
-131            zero.append(read_c_double())
-132        print('zero', zero)
-133
-134        four = []
-135        for index in range(neid):
-136            four.append(read_c_double())
-137        print('four', four)
-138
-139        d_buf = ctypes.c_double * np.sum(ndata)
-140        pd_buf = d_buf()
-141        ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
-142        bdio_read_f64(ppd_buf, ctypes.c_size_t(8 * np.sum(ndata)), ctypes.c_void_p(fbdio))
-143        delta = pd_buf[:]
-144
-145        samples = np.split(np.asarray(delta) + mean, np.cumsum([a for su in vrep for a in su])[:-1])
-146        no_reps = [len(o) for o in vrep]
-147        assert len(ids) == len(no_reps)
-148        tmp_names = []
-149        ens_length = max([len(str(o)) for o in ids])
-150        for loc_id, reps in zip(ids, no_reps):
-151            for index in range(reps):
-152                missing_chars = ens_length - len(str(loc_id))
-153                tmp_names.append(str(loc_id) + ' ' * missing_chars + '|r' + '{0:03d}'.format(index))
-154
-155        return_list.append(Obs(samples, tmp_names))
+ 68    return_list = []
+ 69
+ 70    print('Reading of bdio file started')
+ 71    while True:
+ 72        bdio_seek_record(fbdio)
+ 73        ruinfo = bdio_get_ruinfo(fbdio)
+ 74
+ 75        if ruinfo == 7:
+ 76            print('MD5sum found')  # For now we just ignore these entries and do not perform any checks on them
+ 77            continue
+ 78
+ 79        if ruinfo < 0:
+ 80            # EOF reached
+ 81            break
+ 82        bdio_get_rlen(fbdio)
+ 83
+ 84        def read_c_double():
+ 85            d_buf = ctypes.c_double
+ 86            pd_buf = d_buf()
+ 87            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+ 88            bdio_read_f64(ppd_buf, ctypes.c_size_t(8), ctypes.c_void_p(fbdio))
+ 89            return pd_buf.value
+ 90
+ 91        mean = read_c_double()
+ 92        print('mean', mean)
+ 93
+ 94        def read_c_size_t():
+ 95            d_buf = ctypes.c_size_t
+ 96            pd_buf = d_buf()
+ 97            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+ 98            bdio_read_int32(ppd_buf, ctypes.c_size_t(4), ctypes.c_void_p(fbdio))
+ 99            return pd_buf.value
+100
+101        neid = read_c_size_t()
+102        print('neid', neid)
+103
+104        ndata = []
+105        for _ in range(neid):
+106            ndata.append(read_c_size_t())
+107        print('ndata', ndata)
+108
+109        nrep = []
+110        for _ in range(neid):
+111            nrep.append(read_c_size_t())
+112        print('nrep', nrep)
+113
+114        vrep = []
+115        for index in range(neid):
+116            vrep.append([])
+117            for _jndex in range(nrep[index]):
+118                vrep[-1].append(read_c_size_t())
+119        print('vrep', vrep)
+120
+121        ids = []
+122        for _ in range(neid):
+123            ids.append(read_c_size_t())
+124        print('ids', ids)
+125
+126        nt = []
+127        for _ in range(neid):
+128            nt.append(read_c_size_t())
+129        print('nt', nt)
+130
+131        zero = []
+132        for _ in range(neid):
+133            zero.append(read_c_double())
+134        print('zero', zero)
+135
+136        four = []
+137        for _ in range(neid):
+138            four.append(read_c_double())
+139        print('four', four)
+140
+141        d_buf = ctypes.c_double * np.sum(ndata)
+142        pd_buf = d_buf()
+143        ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+144        bdio_read_f64(ppd_buf, ctypes.c_size_t(8 * np.sum(ndata)), ctypes.c_void_p(fbdio))
+145        delta = pd_buf[:]
+146
+147        samples = np.split(np.asarray(delta) + mean, np.cumsum([a for su in vrep for a in su])[:-1])
+148        no_reps = [len(o) for o in vrep]
+149        assert len(ids) == len(no_reps)
+150        tmp_names = []
+151        ens_length = max([len(str(o)) for o in ids])
+152        for loc_id, reps in zip(ids, no_reps, strict=True):
+153            for index in range(reps):
+154                missing_chars = ens_length - len(str(loc_id))
+155                tmp_names.append(str(loc_id) + ' ' * missing_chars + '|r' + f'{index:03d}')
 156
-157    bdio_close(fbdio)
-158    print()
-159    print(len(return_list), 'observable(s) extracted.')
-160    return return_list
-161
-162
-163def write_ADerrors(obs_list, file_path, bdio_path='./libbdio.so', **kwargs):
-164    """ Write Obs to a bdio file according to ADerrors conventions
-165
-166    read_mesons requires bdio to be compiled into a shared library. This can be achieved by
-167    adding the flag -fPIC to CC and changing the all target to
-168
-169    all:		bdio.o $(LIBDIR)
-170                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
-171                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
-172
-173    Parameters
-174    ----------
-175    file_path -- path to the bdio file
-176    bdio_path -- path to the shared bdio library libbdio.so (default ./libbdio.so)
-177
-178    Returns
-179    -------
-180    success : int
-181        returns 0 is successful
-182    """
-183
-184    for obs in obs_list:
-185        if not hasattr(obs, 'e_names'):
-186            raise Exception('Run the gamma method first for all obs.')
-187
-188    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+157        return_list.append(Obs(samples, tmp_names))
+158
+159    bdio_close(fbdio)
+160    print()
+161    print(len(return_list), 'observable(s) extracted.')
+162    return return_list
+163
+164
+165def write_ADerrors(obs_list, file_path, bdio_path='./libbdio.so', **kwargs):
+166    """ Write Obs to a bdio file according to ADerrors conventions
+167
+168    read_mesons requires bdio to be compiled into a shared library. This can be achieved by
+169    adding the flag -fPIC to CC and changing the all target to
+170
+171    all:		bdio.o $(LIBDIR)
+172                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
+173                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
+174
+175    Parameters
+176    ----------
+177    file_path -- path to the bdio file
+178    bdio_path -- path to the shared bdio library libbdio.so (default ./libbdio.so)
+179
+180    Returns
+181    -------
+182    success : int
+183        returns 0 is successful
+184    """
+185
+186    for obs in obs_list:
+187        if not hasattr(obs, 'e_names'):
+188            raise Exception('Run the gamma method first for all obs.')
 189
-190    bdio_open = bdio.bdio_open
-191    bdio_open.restype = ctypes.c_void_p
-192
-193    bdio_close = bdio.bdio_close
-194    bdio_close.restype = ctypes.c_int
-195    bdio_close.argtypes = [ctypes.c_void_p]
-196
-197    bdio_start_record = bdio.bdio_start_record
-198    bdio_start_record.restype = ctypes.c_int
-199    bdio_start_record.argtypes = [ctypes.c_size_t, ctypes.c_size_t, ctypes.c_void_p]
-200
-201    bdio_flush_record = bdio.bdio_flush_record
-202    bdio_flush_record.restype = ctypes.c_int
-203    bdio_flush_record.argytpes = [ctypes.c_void_p]
-204
-205    bdio_write_f64 = bdio.bdio_write_f64
-206    bdio_write_f64.restype = ctypes.c_size_t
-207    bdio_write_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
-208
-209    bdio_write_int32 = bdio.bdio_write_int32
-210    bdio_write_int32.restype = ctypes.c_size_t
-211    bdio_write_int32.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
-212
-213    b_path = file_path.encode('utf-8')
-214    write = 'w'
-215    b_write = write.encode('utf-8')
-216    form = 'pyerrors ADerror export'
-217    b_form = form.encode('utf-8')
-218
-219    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_write), b_form)
+190    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+191
+192    bdio_open = bdio.bdio_open
+193    bdio_open.restype = ctypes.c_void_p
+194
+195    bdio_close = bdio.bdio_close
+196    bdio_close.restype = ctypes.c_int
+197    bdio_close.argtypes = [ctypes.c_void_p]
+198
+199    bdio_start_record = bdio.bdio_start_record
+200    bdio_start_record.restype = ctypes.c_int
+201    bdio_start_record.argtypes = [ctypes.c_size_t, ctypes.c_size_t, ctypes.c_void_p]
+202
+203    bdio_flush_record = bdio.bdio_flush_record
+204    bdio_flush_record.restype = ctypes.c_int
+205    bdio_flush_record.argytpes = [ctypes.c_void_p]
+206
+207    bdio_write_f64 = bdio.bdio_write_f64
+208    bdio_write_f64.restype = ctypes.c_size_t
+209    bdio_write_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
+210
+211    bdio_write_int32 = bdio.bdio_write_int32
+212    bdio_write_int32.restype = ctypes.c_size_t
+213    bdio_write_int32.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
+214
+215    b_path = file_path.encode('utf-8')
+216    write = 'w'
+217    b_write = write.encode('utf-8')
+218    form = 'pyerrors ADerror export'
+219    b_form = form.encode('utf-8')
 220
-221    for obs in obs_list:
-222        # mean = obs.value
-223        neid = len(obs.e_names)
-224        vrep = [[obs.shape[o] for o in sl] for sl in list(obs.e_content.values())]
-225        vrep_write = [item for sublist in vrep for item in sublist]
-226        ndata = [np.sum(o) for o in vrep]
-227        nrep = [len(o) for o in vrep]
-228        print('ndata', ndata)
-229        print('nrep', nrep)
-230        print('vrep', vrep)
-231        keys = list(obs.e_content.keys())
-232        ids = []
-233        for key in keys:
-234            try:  # Try to convert key to integer
-235                ids.append(int(key))
-236            except Exception:  # If not possible construct a hash
-237                ids.append(int(hashlib.sha256(key.encode('utf-8')).hexdigest(), 16) % 10 ** 8)
-238        print('ids', ids)
-239        nt = []
-240        for e, e_name in enumerate(obs.e_names):
-241
-242            r_length = []
-243            for r_name in obs.e_content[e_name]:
-244                r_length.append(len(obs.deltas[r_name]))
-245
-246            # e_N = np.sum(r_length)
-247            nt.append(max(r_length) // 2)
-248        print('nt', nt)
-249        zero = neid * [0.0]
-250        four = neid * [4.0]
-251        print('zero', zero)
-252        print('four', four)
-253        delta = np.concatenate([item for sublist in [[obs.deltas[o] for o in sl] for sl in list(obs.e_content.values())] for item in sublist])
-254
-255        bdio_start_record(0x00, 8, fbdio)
+221    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_write), b_form)
+222
+223    for obs in obs_list:
+224        # mean = obs.value
+225        neid = len(obs.e_names)
+226        vrep = [[obs.shape[o] for o in sl] for sl in list(obs.e_content.values())]
+227        vrep_write = [item for sublist in vrep for item in sublist]
+228        ndata = [np.sum(o) for o in vrep]
+229        nrep = [len(o) for o in vrep]
+230        print('ndata', ndata)
+231        print('nrep', nrep)
+232        print('vrep', vrep)
+233        keys = list(obs.e_content.keys())
+234        ids = []
+235        for key in keys:
+236            try:  # Try to convert key to integer
+237                ids.append(int(key))
+238            except Exception:  # If not possible construct a hash
+239                ids.append(int(hashlib.sha256(key.encode('utf-8')).hexdigest(), 16) % 10 ** 8)
+240        print('ids', ids)
+241        nt = []
+242        for _e, e_name in enumerate(obs.e_names):
+243
+244            r_length = []
+245            for r_name in obs.e_content[e_name]:
+246                r_length.append(len(obs.deltas[r_name]))
+247
+248            # e_N = np.sum(r_length)
+249            nt.append(max(r_length) // 2)
+250        print('nt', nt)
+251        zero = neid * [0.0]
+252        four = neid * [4.0]
+253        print('zero', zero)
+254        print('four', four)
+255        delta = np.concatenate([item for sublist in [[obs.deltas[o] for o in sl] for sl in list(obs.e_content.values())] for item in sublist])
 256
-257        def write_c_double(double):
-258            pd_buf = ctypes.c_double(double)
-259            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
-260            bdio_write_f64(ppd_buf, ctypes.c_size_t(8), ctypes.c_void_p(fbdio))
-261
-262        def write_c_size_t(int32):
-263            pd_buf = ctypes.c_size_t(int32)
-264            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
-265            bdio_write_int32(ppd_buf, ctypes.c_size_t(4), ctypes.c_void_p(fbdio))
-266
-267        write_c_double(obs.value)
-268        write_c_size_t(neid)
-269
-270        for element in ndata:
-271            write_c_size_t(element)
-272        for element in nrep:
+257        bdio_start_record(0x00, 8, fbdio)
+258
+259        def write_c_double(double):
+260            pd_buf = ctypes.c_double(double)
+261            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+262            bdio_write_f64(ppd_buf, ctypes.c_size_t(8), ctypes.c_void_p(fbdio))
+263
+264        def write_c_size_t(int32):
+265            pd_buf = ctypes.c_size_t(int32)
+266            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+267            bdio_write_int32(ppd_buf, ctypes.c_size_t(4), ctypes.c_void_p(fbdio))
+268
+269        write_c_double(obs.value)
+270        write_c_size_t(neid)
+271
+272        for element in ndata:
 273            write_c_size_t(element)
-274        for element in vrep_write:
+274        for element in nrep:
 275            write_c_size_t(element)
-276        for element in ids:
+276        for element in vrep_write:
 277            write_c_size_t(element)
-278        for element in nt:
+278        for element in ids:
 279            write_c_size_t(element)
-280
-281        for element in zero:
-282            write_c_double(element)
-283        for element in four:
+280        for element in nt:
+281            write_c_size_t(element)
+282
+283        for element in zero:
 284            write_c_double(element)
-285
-286        for element in delta:
-287            write_c_double(element)
-288
-289    bdio_close(fbdio)
-290    return 0
-291
-292
-293def _get_kwd(string, key):
-294    return (string.split(key, 1)[1]).split(" ", 1)[0]
-295
-296
-297def _get_corr_name(string, key):
-298    return (string.split(key, 1)[1]).split(' NDIM=', 1)[0]
-299
-300
-301def read_mesons(file_path, bdio_path='./libbdio.so', **kwargs):
-302    """ Extract mesons data from a bdio file and return it as a dictionary
-303
-304    The dictionary can be accessed with a tuple consisting of (type, source_position, kappa1, kappa2)
+285        for element in four:
+286            write_c_double(element)
+287
+288        for element in delta:
+289            write_c_double(element)
+290
+291    bdio_close(fbdio)
+292    return 0
+293
+294
+295def _get_kwd(string, key):
+296    return (string.split(key, 1)[1]).split(" ", 1)[0]
+297
+298
+299def _get_corr_name(string, key):
+300    return (string.split(key, 1)[1]).split(' NDIM=', 1)[0]
+301
+302
+303def read_mesons(file_path, bdio_path='./libbdio.so', **kwargs):
+304    """ Extract mesons data from a bdio file and return it as a dictionary
 305
-306    read_mesons requires bdio to be compiled into a shared library. This can be achieved by
-307    adding the flag -fPIC to CC and changing the all target to
-308
-309    all:		bdio.o $(LIBDIR)
-310                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
-311                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
-312
-313    Parameters
-314    ----------
-315    file_path : str
-316        path to the bdio file
-317    bdio_path : str
-318        path to the shared bdio library libbdio.so (default ./libbdio.so)
-319    start : int
-320        The first configuration to be read (default 1)
-321    stop : int
-322        The last configuration to be read (default None)
-323    step : int
-324        Fixed step size between two measurements (default 1)
-325    alternative_ensemble_name : str
-326        Manually overwrite ensemble name
-327
-328    Returns
-329    -------
-330    data : dict
-331        Extracted meson data
-332    """
-333
-334    start = kwargs.get('start', 1)
-335    stop = kwargs.get('stop', None)
-336    step = kwargs.get('step', 1)
-337
-338    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+306    The dictionary can be accessed with a tuple consisting of (type, source_position, kappa1, kappa2)
+307
+308    read_mesons requires bdio to be compiled into a shared library. This can be achieved by
+309    adding the flag -fPIC to CC and changing the all target to
+310
+311    all:		bdio.o $(LIBDIR)
+312                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
+313                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
+314
+315    Parameters
+316    ----------
+317    file_path : str
+318        path to the bdio file
+319    bdio_path : str
+320        path to the shared bdio library libbdio.so (default ./libbdio.so)
+321    start : int
+322        The first configuration to be read (default 1)
+323    stop : int
+324        The last configuration to be read (default None)
+325    step : int
+326        Fixed step size between two measurements (default 1)
+327    alternative_ensemble_name : str
+328        Manually overwrite ensemble name
+329
+330    Returns
+331    -------
+332    data : dict
+333        Extracted meson data
+334    """
+335
+336    start = kwargs.get('start', 1)
+337    stop = kwargs.get('stop', None)
+338    step = kwargs.get('step', 1)
 339
-340    bdio_open = bdio.bdio_open
-341    bdio_open.restype = ctypes.c_void_p
-342
-343    bdio_close = bdio.bdio_close
-344    bdio_close.restype = ctypes.c_int
-345    bdio_close.argtypes = [ctypes.c_void_p]
-346
-347    bdio_seek_record = bdio.bdio_seek_record
-348    bdio_seek_record.restype = ctypes.c_int
-349    bdio_seek_record.argtypes = [ctypes.c_void_p]
-350
-351    bdio_get_rlen = bdio.bdio_get_rlen
-352    bdio_get_rlen.restype = ctypes.c_int
-353    bdio_get_rlen.argtypes = [ctypes.c_void_p]
-354
-355    bdio_get_ruinfo = bdio.bdio_get_ruinfo
-356    bdio_get_ruinfo.restype = ctypes.c_int
-357    bdio_get_ruinfo.argtypes = [ctypes.c_void_p]
-358
-359    bdio_read = bdio.bdio_read
-360    bdio_read.restype = ctypes.c_size_t
-361    bdio_read.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
-362
-363    bdio_read_f64 = bdio.bdio_read_f64
-364    bdio_read_f64.restype = ctypes.c_size_t
-365    bdio_read_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
-366
-367    b_path = file_path.encode('utf-8')
-368    read = 'r'
-369    b_read = read.encode('utf-8')
-370    form = 'Generic Correlator Format 1.0'
-371    b_form = form.encode('utf-8')
-372
-373    ensemble_name = ''
-374    volume = []  # lattice volume
-375    boundary_conditions = []
-376    corr_name = []  # Contains correlator names
-377    corr_type = []  # Contains correlator data type (important for reading out numerical data)
-378    corr_props = []  # Contanis propagator types (Component of corr_kappa)
-379    d0 = 0  # tvals
-380    d1 = 0  # nnoise
-381    prop_kappa = []  # Contains propagator kappas (Component of corr_kappa)
-382    prop_source = []  # Contains propagator source positions
-383    # Check noise type for multiple replica?
-384    corr_no = -1
-385    data = []
-386    idl = []
-387
-388    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_read), ctypes.c_char_p(b_form))
+340    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+341
+342    bdio_open = bdio.bdio_open
+343    bdio_open.restype = ctypes.c_void_p
+344
+345    bdio_close = bdio.bdio_close
+346    bdio_close.restype = ctypes.c_int
+347    bdio_close.argtypes = [ctypes.c_void_p]
+348
+349    bdio_seek_record = bdio.bdio_seek_record
+350    bdio_seek_record.restype = ctypes.c_int
+351    bdio_seek_record.argtypes = [ctypes.c_void_p]
+352
+353    bdio_get_rlen = bdio.bdio_get_rlen
+354    bdio_get_rlen.restype = ctypes.c_int
+355    bdio_get_rlen.argtypes = [ctypes.c_void_p]
+356
+357    bdio_get_ruinfo = bdio.bdio_get_ruinfo
+358    bdio_get_ruinfo.restype = ctypes.c_int
+359    bdio_get_ruinfo.argtypes = [ctypes.c_void_p]
+360
+361    bdio_read = bdio.bdio_read
+362    bdio_read.restype = ctypes.c_size_t
+363    bdio_read.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
+364
+365    bdio_read_f64 = bdio.bdio_read_f64
+366    bdio_read_f64.restype = ctypes.c_size_t
+367    bdio_read_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
+368
+369    b_path = file_path.encode('utf-8')
+370    read = 'r'
+371    b_read = read.encode('utf-8')
+372    form = 'Generic Correlator Format 1.0'
+373    b_form = form.encode('utf-8')
+374
+375    ensemble_name = ''
+376    volume = []  # lattice volume
+377    boundary_conditions = []
+378    corr_name = []  # Contains correlator names
+379    corr_type = []  # Contains correlator data type (important for reading out numerical data)
+380    corr_props = []  # Contanis propagator types (Component of corr_kappa)
+381    d0 = 0  # tvals
+382    d1 = 0  # nnoise
+383    prop_kappa = []  # Contains propagator kappas (Component of corr_kappa)
+384    prop_source = []  # Contains propagator source positions
+385    # Check noise type for multiple replica?
+386    corr_no = -1
+387    data = []
+388    idl = []
 389
-390    print('Reading of bdio file started')
-391    while True:
-392        bdio_seek_record(fbdio)
-393        ruinfo = bdio_get_ruinfo(fbdio)
-394        if ruinfo < 0:
-395            # EOF reached
-396            break
-397        rlen = bdio_get_rlen(fbdio)
-398        if ruinfo == 5:
-399            d_buf = ctypes.c_double * (2 + d0 * d1 * 2)
-400            pd_buf = d_buf()
-401            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
-402            bdio_read_f64(ppd_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
-403            if corr_type[corr_no] == 'complex':
-404                tmp_mean = np.mean(np.asarray(np.split(np.asarray(pd_buf[2 + 2 * d1:-2 * d1:2]), d0 - 2)), axis=1)
-405            else:
-406                tmp_mean = np.mean(np.asarray(np.split(np.asarray(pd_buf[2 + d1:-d0 * d1 - d1]), d0 - 2)), axis=1)
-407
-408            data[corr_no].append(tmp_mean)
-409            corr_no += 1
-410        else:
-411            alt_buf = ctypes.create_string_buffer(1024)
-412            palt_buf = ctypes.c_char_p(ctypes.addressof(alt_buf))
-413            iread = bdio_read(palt_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
-414            if rlen != iread:
-415                print('Error')
-416            for i, item in enumerate(alt_buf):
-417                if item == b'\x00':
-418                    alt_buf[i] = b' '
-419            tmp_string = (alt_buf[:].decode("utf-8")).rstrip()
-420            if ruinfo == 0:
-421                ensemble_name = _get_kwd(tmp_string, 'ENSEMBLE=')
-422                volume.append(int(_get_kwd(tmp_string, 'L0=')))
-423                volume.append(int(_get_kwd(tmp_string, 'L1=')))
-424                volume.append(int(_get_kwd(tmp_string, 'L2=')))
-425                volume.append(int(_get_kwd(tmp_string, 'L3=')))
-426                boundary_conditions.append(_get_kwd(tmp_string, 'BC0='))
-427                boundary_conditions.append(_get_kwd(tmp_string, 'BC1='))
-428                boundary_conditions.append(_get_kwd(tmp_string, 'BC2='))
-429                boundary_conditions.append(_get_kwd(tmp_string, 'BC3='))
-430
-431            if ruinfo == 1:
-432                corr_name.append(_get_corr_name(tmp_string, 'CORR_NAME='))
-433                corr_type.append(_get_kwd(tmp_string, 'DATATYPE='))
-434                corr_props.append([_get_kwd(tmp_string, 'PROP0='), _get_kwd(tmp_string, 'PROP1=')])
-435                if d0 == 0:
-436                    d0 = int(_get_kwd(tmp_string, 'D0='))
-437                else:
-438                    if d0 != int(_get_kwd(tmp_string, 'D0=')):
-439                        print('Error: Varying number of time values')
-440                if d1 == 0:
-441                    d1 = int(_get_kwd(tmp_string, 'D1='))
-442                else:
-443                    if d1 != int(_get_kwd(tmp_string, 'D1=')):
-444                        print('Error: Varying number of random sources')
-445            if ruinfo == 2:
-446                prop_kappa.append(_get_kwd(tmp_string, 'KAPPA='))
-447                prop_source.append(_get_kwd(tmp_string, 'x0='))
-448            if ruinfo == 4:
-449                cnfg_no = int(_get_kwd(tmp_string, 'CNFG_ID='))
-450                if stop:
-451                    if cnfg_no > kwargs.get('stop'):
-452                        break
-453                idl.append(cnfg_no)
-454                print('\r%s %i' % ('Reading configuration', cnfg_no), end='\r')
-455                if len(idl) == 1:
-456                    no_corrs = len(corr_name)
-457                    data = []
-458                    for c in range(no_corrs):
-459                        data.append([])
-460
-461                corr_no = 0
+390    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_read), ctypes.c_char_p(b_form))
+391
+392    print('Reading of bdio file started')
+393    while True:
+394        bdio_seek_record(fbdio)
+395        ruinfo = bdio_get_ruinfo(fbdio)
+396        if ruinfo < 0:
+397            # EOF reached
+398            break
+399        rlen = bdio_get_rlen(fbdio)
+400        if ruinfo == 5:
+401            d_buf = ctypes.c_double * (2 + d0 * d1 * 2)
+402            pd_buf = d_buf()
+403            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+404            bdio_read_f64(ppd_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
+405            if corr_type[corr_no] == 'complex':
+406                tmp_mean = np.mean(np.asarray(np.split(np.asarray(pd_buf[2 + 2 * d1:-2 * d1:2]), d0 - 2)), axis=1)
+407            else:
+408                tmp_mean = np.mean(np.asarray(np.split(np.asarray(pd_buf[2 + d1:-d0 * d1 - d1]), d0 - 2)), axis=1)
+409
+410            data[corr_no].append(tmp_mean)
+411            corr_no += 1
+412        else:
+413            alt_buf = ctypes.create_string_buffer(1024)
+414            palt_buf = ctypes.c_char_p(ctypes.addressof(alt_buf))
+415            iread = bdio_read(palt_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
+416            if rlen != iread:
+417                print('Error')
+418            for i, item in enumerate(alt_buf):
+419                if item == b'\x00':
+420                    alt_buf[i] = b' '
+421            tmp_string = (alt_buf[:].decode("utf-8")).rstrip()
+422            if ruinfo == 0:
+423                ensemble_name = _get_kwd(tmp_string, 'ENSEMBLE=')
+424                volume.append(int(_get_kwd(tmp_string, 'L0=')))
+425                volume.append(int(_get_kwd(tmp_string, 'L1=')))
+426                volume.append(int(_get_kwd(tmp_string, 'L2=')))
+427                volume.append(int(_get_kwd(tmp_string, 'L3=')))
+428                boundary_conditions.append(_get_kwd(tmp_string, 'BC0='))
+429                boundary_conditions.append(_get_kwd(tmp_string, 'BC1='))
+430                boundary_conditions.append(_get_kwd(tmp_string, 'BC2='))
+431                boundary_conditions.append(_get_kwd(tmp_string, 'BC3='))
+432
+433            if ruinfo == 1:
+434                corr_name.append(_get_corr_name(tmp_string, 'CORR_NAME='))
+435                corr_type.append(_get_kwd(tmp_string, 'DATATYPE='))
+436                corr_props.append([_get_kwd(tmp_string, 'PROP0='), _get_kwd(tmp_string, 'PROP1=')])
+437                if d0 == 0:
+438                    d0 = int(_get_kwd(tmp_string, 'D0='))
+439                else:
+440                    if d0 != int(_get_kwd(tmp_string, 'D0=')):
+441                        print('Error: Varying number of time values')
+442                if d1 == 0:
+443                    d1 = int(_get_kwd(tmp_string, 'D1='))
+444                else:
+445                    if d1 != int(_get_kwd(tmp_string, 'D1=')):
+446                        print('Error: Varying number of random sources')
+447            if ruinfo == 2:
+448                prop_kappa.append(_get_kwd(tmp_string, 'KAPPA='))
+449                prop_source.append(_get_kwd(tmp_string, 'x0='))
+450            if ruinfo == 4:
+451                cnfg_no = int(_get_kwd(tmp_string, 'CNFG_ID='))
+452                if stop:
+453                    if cnfg_no > kwargs.get('stop'):
+454                        break
+455                idl.append(cnfg_no)
+456                print(f'\rReading configuration {cnfg_no}', end='\r')
+457                if len(idl) == 1:
+458                    no_corrs = len(corr_name)
+459                    data = []
+460                    for _ in range(no_corrs):
+461                        data.append([])
 462
-463    bdio_close(fbdio)
+463                corr_no = 0
 464
-465    print('\nEnsemble: ', ensemble_name)
-466    if 'alternative_ensemble_name' in kwargs:
-467        ensemble_name = kwargs.get('alternative_ensemble_name')
-468        print('Ensemble name overwritten to', ensemble_name)
-469    print('Lattice volume: ', volume)
-470    print('Boundary conditions: ', boundary_conditions)
-471    print('Number of time values: ', d0)
-472    print('Number of random sources: ', d1)
-473    print('Number of corrs: ', len(corr_name))
-474    print('Number of configurations: ', len(idl))
-475
-476    corr_kappa = []  # Contains kappa values for both propagators of given correlation function
-477    corr_source = []
-478    for item in corr_props:
-479        corr_kappa.append([float(prop_kappa[int(item[0])]), float(prop_kappa[int(item[1])])])
-480        if prop_source[int(item[0])] != prop_source[int(item[1])]:
-481            raise Exception('Source position do not match for correlator' + str(item))
-482        else:
-483            corr_source.append(int(prop_source[int(item[0])]))
-484
-485    if stop is None:
-486        stop = idl[-1]
-487    idl_target = range(start, stop + 1, step)
-488
-489    if set(idl) != set(idl_target):
-490        try:
-491            indices = [idl.index(i) for i in idl_target]
-492        except ValueError as err:
-493            raise Exception('Configurations in file do no match target list!', err)
-494    else:
-495        indices = None
-496
-497    result = {}
-498    for c in range(no_corrs):
-499        tmp_corr = []
-500        tmp_data = np.asarray(data[c])
-501        for t in range(d0 - 2):
-502            if indices:
-503                deltas = [tmp_data[:, t][index] for index in indices]
-504            else:
-505                deltas = tmp_data[:, t]
-506            tmp_corr.append(Obs([deltas], [ensemble_name], idl=[idl_target]))
-507        result[(corr_name[c], corr_source[c]) + tuple(corr_kappa[c])] = tmp_corr
-508
-509    # Check that all data entries have the same number of configurations
-510    if len(set([o[0].N for o in list(result.values())])) != 1:
-511        raise Exception('Error: Not all correlators have the same number of configurations. bdio file is possibly corrupted.')
-512
-513    return result
+465    bdio_close(fbdio)
+466
+467    print('\nEnsemble: ', ensemble_name)
+468    if 'alternative_ensemble_name' in kwargs:
+469        ensemble_name = kwargs.get('alternative_ensemble_name')
+470        print('Ensemble name overwritten to', ensemble_name)
+471    print('Lattice volume: ', volume)
+472    print('Boundary conditions: ', boundary_conditions)
+473    print('Number of time values: ', d0)
+474    print('Number of random sources: ', d1)
+475    print('Number of corrs: ', len(corr_name))
+476    print('Number of configurations: ', len(idl))
+477
+478    corr_kappa = []  # Contains kappa values for both propagators of given correlation function
+479    corr_source = []
+480    for item in corr_props:
+481        corr_kappa.append([float(prop_kappa[int(item[0])]), float(prop_kappa[int(item[1])])])
+482        if prop_source[int(item[0])] != prop_source[int(item[1])]:
+483            raise Exception('Source position do not match for correlator' + str(item))
+484        else:
+485            corr_source.append(int(prop_source[int(item[0])]))
+486
+487    if stop is None:
+488        stop = idl[-1]
+489    idl_target = range(start, stop + 1, step)
+490
+491    if set(idl) != set(idl_target):
+492        try:
+493            indices = [idl.index(i) for i in idl_target]
+494        except ValueError as err:
+495            raise Exception('Configurations in file do no match target list!', err) from err
+496    else:
+497        indices = None
+498
+499    result = {}
+500    for c in range(no_corrs):
+501        tmp_corr = []
+502        tmp_data = np.asarray(data[c])
+503        for t in range(d0 - 2):
+504            if indices:
+505                deltas = [tmp_data[:, t][index] for index in indices]
+506            else:
+507                deltas = tmp_data[:, t]
+508            tmp_corr.append(Obs([deltas], [ensemble_name], idl=[idl_target]))
+509        result[(corr_name[c], corr_source[c], *corr_kappa[c])] = tmp_corr
+510
+511    # Check that all data entries have the same number of configurations
+512    if len(set([o[0].N for o in list(result.values())])) != 1:
+513        raise Exception('Error: Not all correlators have the same number of configurations. bdio file is possibly corrupted.')
 514
-515
-516def read_dSdm(file_path, bdio_path='./libbdio.so', **kwargs):
-517    """ Extract dSdm data from a bdio file and return it as a dictionary
-518
-519    The dictionary can be accessed with a tuple consisting of (type, kappa)
+515    return result
+516
+517
+518def read_dSdm(file_path, bdio_path='./libbdio.so', **kwargs):
+519    """ Extract dSdm data from a bdio file and return it as a dictionary
 520
-521    read_dSdm requires bdio to be compiled into a shared library. This can be achieved by
-522    adding the flag -fPIC to CC and changing the all target to
-523
-524    all:		bdio.o $(LIBDIR)
-525                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
-526                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
-527
-528    Parameters
-529    ----------
-530    file_path : str
-531        path to the bdio file
-532    bdio_path : str
-533        path to the shared bdio library libbdio.so (default ./libbdio.so)
-534    start : int
-535        The first configuration to be read (default 1)
-536    stop : int
-537        The last configuration to be read (default None)
-538    step : int
-539        Fixed step size between two measurements (default 1)
-540    alternative_ensemble_name : str
-541        Manually overwrite ensemble name
-542    """
-543
-544    start = kwargs.get('start', 1)
-545    stop = kwargs.get('stop', None)
-546    step = kwargs.get('step', 1)
-547
-548    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+521    The dictionary can be accessed with a tuple consisting of (type, kappa)
+522
+523    read_dSdm requires bdio to be compiled into a shared library. This can be achieved by
+524    adding the flag -fPIC to CC and changing the all target to
+525
+526    all:		bdio.o $(LIBDIR)
+527                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
+528                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
+529
+530    Parameters
+531    ----------
+532    file_path : str
+533        path to the bdio file
+534    bdio_path : str
+535        path to the shared bdio library libbdio.so (default ./libbdio.so)
+536    start : int
+537        The first configuration to be read (default 1)
+538    stop : int
+539        The last configuration to be read (default None)
+540    step : int
+541        Fixed step size between two measurements (default 1)
+542    alternative_ensemble_name : str
+543        Manually overwrite ensemble name
+544    """
+545
+546    start = kwargs.get('start', 1)
+547    stop = kwargs.get('stop', None)
+548    step = kwargs.get('step', 1)
 549
-550    bdio_open = bdio.bdio_open
-551    bdio_open.restype = ctypes.c_void_p
-552
-553    bdio_close = bdio.bdio_close
-554    bdio_close.restype = ctypes.c_int
-555    bdio_close.argtypes = [ctypes.c_void_p]
-556
-557    bdio_seek_record = bdio.bdio_seek_record
-558    bdio_seek_record.restype = ctypes.c_int
-559    bdio_seek_record.argtypes = [ctypes.c_void_p]
-560
-561    bdio_get_rlen = bdio.bdio_get_rlen
-562    bdio_get_rlen.restype = ctypes.c_int
-563    bdio_get_rlen.argtypes = [ctypes.c_void_p]
-564
-565    bdio_get_ruinfo = bdio.bdio_get_ruinfo
-566    bdio_get_ruinfo.restype = ctypes.c_int
-567    bdio_get_ruinfo.argtypes = [ctypes.c_void_p]
-568
-569    bdio_read = bdio.bdio_read
-570    bdio_read.restype = ctypes.c_size_t
-571    bdio_read.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
-572
-573    bdio_read_f64 = bdio.bdio_read_f64
-574    bdio_read_f64.restype = ctypes.c_size_t
-575    bdio_read_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
-576
-577    b_path = file_path.encode('utf-8')
-578    read = 'r'
-579    b_read = read.encode('utf-8')
-580    form = 'Generic Correlator Format 1.0'
-581    b_form = form.encode('utf-8')
-582
-583    ensemble_name = ''
-584    volume = []  # lattice volume
-585    boundary_conditions = []
-586    corr_name = []  # Contains correlator names
-587    corr_type = []  # Contains correlator data type (important for reading out numerical data)
-588    corr_props = []  # Contains propagator types (Component of corr_kappa)
-589    d0 = 0  # tvals
-590    # d1 = 0  # nnoise
-591    prop_kappa = []  # Contains propagator kappas (Component of corr_kappa)
-592    # Check noise type for multiple replica?
-593    corr_no = -1
-594    data = []
-595    idl = []
-596
-597    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_read), ctypes.c_char_p(b_form))
+550    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+551
+552    bdio_open = bdio.bdio_open
+553    bdio_open.restype = ctypes.c_void_p
+554
+555    bdio_close = bdio.bdio_close
+556    bdio_close.restype = ctypes.c_int
+557    bdio_close.argtypes = [ctypes.c_void_p]
+558
+559    bdio_seek_record = bdio.bdio_seek_record
+560    bdio_seek_record.restype = ctypes.c_int
+561    bdio_seek_record.argtypes = [ctypes.c_void_p]
+562
+563    bdio_get_rlen = bdio.bdio_get_rlen
+564    bdio_get_rlen.restype = ctypes.c_int
+565    bdio_get_rlen.argtypes = [ctypes.c_void_p]
+566
+567    bdio_get_ruinfo = bdio.bdio_get_ruinfo
+568    bdio_get_ruinfo.restype = ctypes.c_int
+569    bdio_get_ruinfo.argtypes = [ctypes.c_void_p]
+570
+571    bdio_read = bdio.bdio_read
+572    bdio_read.restype = ctypes.c_size_t
+573    bdio_read.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
+574
+575    bdio_read_f64 = bdio.bdio_read_f64
+576    bdio_read_f64.restype = ctypes.c_size_t
+577    bdio_read_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
+578
+579    b_path = file_path.encode('utf-8')
+580    read = 'r'
+581    b_read = read.encode('utf-8')
+582    form = 'Generic Correlator Format 1.0'
+583    b_form = form.encode('utf-8')
+584
+585    ensemble_name = ''
+586    volume = []  # lattice volume
+587    boundary_conditions = []
+588    corr_name = []  # Contains correlator names
+589    corr_type = []  # Contains correlator data type (important for reading out numerical data)
+590    corr_props = []  # Contains propagator types (Component of corr_kappa)
+591    d0 = 0  # tvals
+592    # d1 = 0  # nnoise
+593    prop_kappa = []  # Contains propagator kappas (Component of corr_kappa)
+594    # Check noise type for multiple replica?
+595    corr_no = -1
+596    data = []
+597    idl = []
 598
-599    print('Reading of bdio file started')
-600    while True:
-601        bdio_seek_record(fbdio)
-602        ruinfo = bdio_get_ruinfo(fbdio)
-603        if ruinfo < 0:
-604            # EOF reached
-605            break
-606        rlen = bdio_get_rlen(fbdio)
-607        if ruinfo == 5:
-608            d_buf = ctypes.c_double * (2 + d0)
-609            pd_buf = d_buf()
-610            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
-611            bdio_read_f64(ppd_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
-612            tmp_mean = np.mean(np.asarray(pd_buf[2:]))
-613
-614            data[corr_no].append(tmp_mean)
-615            corr_no += 1
-616        else:
-617            alt_buf = ctypes.create_string_buffer(1024)
-618            palt_buf = ctypes.c_char_p(ctypes.addressof(alt_buf))
-619            iread = bdio_read(palt_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
-620            if rlen != iread:
-621                print('Error')
-622            for i, item in enumerate(alt_buf):
-623                if item == b'\x00':
-624                    alt_buf[i] = b' '
-625            tmp_string = (alt_buf[:].decode("utf-8")).rstrip()
-626            if ruinfo == 0:
-627                creator = _get_kwd(tmp_string, 'CREATOR=')
-628                ensemble_name = _get_kwd(tmp_string, 'ENSEMBLE=')
-629                volume.append(int(_get_kwd(tmp_string, 'L0=')))
-630                volume.append(int(_get_kwd(tmp_string, 'L1=')))
-631                volume.append(int(_get_kwd(tmp_string, 'L2=')))
-632                volume.append(int(_get_kwd(tmp_string, 'L3=')))
-633                boundary_conditions.append(_get_kwd(tmp_string, 'BC0='))
-634                boundary_conditions.append(_get_kwd(tmp_string, 'BC1='))
-635                boundary_conditions.append(_get_kwd(tmp_string, 'BC2='))
-636                boundary_conditions.append(_get_kwd(tmp_string, 'BC3='))
-637
-638            if ruinfo == 1:
-639                corr_name.append(_get_corr_name(tmp_string, 'CORR_NAME='))
-640                corr_type.append(_get_kwd(tmp_string, 'DATATYPE='))
-641                corr_props.append(_get_kwd(tmp_string, 'PROP0='))
-642                if d0 == 0:
-643                    d0 = int(_get_kwd(tmp_string, 'D0='))
-644                else:
-645                    if d0 != int(_get_kwd(tmp_string, 'D0=')):
-646                        print('Error: Varying number of time values')
-647            if ruinfo == 2:
-648                prop_kappa.append(_get_kwd(tmp_string, 'KAPPA='))
-649            if ruinfo == 4:
-650                cnfg_no = int(_get_kwd(tmp_string, 'CNFG_ID='))
-651                if stop:
-652                    if cnfg_no > kwargs.get('stop'):
-653                        break
-654                idl.append(cnfg_no)
-655                print('\r%s %i' % ('Reading configuration', cnfg_no), end='\r')
-656                if len(idl) == 1:
-657                    no_corrs = len(corr_name)
-658                    data = []
-659                    for c in range(no_corrs):
-660                        data.append([])
-661
-662                corr_no = 0
-663    bdio_close(fbdio)
-664
-665    print('\nCreator: ', creator)
-666    print('Ensemble: ', ensemble_name)
-667    print('Lattice volume: ', volume)
-668    print('Boundary conditions: ', boundary_conditions)
-669    print('Number of random sources: ', d0)
-670    print('Number of corrs: ', len(corr_name))
-671    print('Number of configurations: ', cnfg_no + 1)
-672
-673    corr_kappa = []  # Contains kappa values for both propagators of given correlation function
-674    for item in corr_props:
-675        corr_kappa.append(float(prop_kappa[int(item)]))
-676
-677    if stop is None:
-678        stop = idl[-1]
-679    idl_target = range(start, stop + 1, step)
-680    try:
-681        indices = [idl.index(i) for i in idl_target]
-682    except ValueError as err:
-683        raise Exception('Configurations in file do no match target list!', err)
-684
-685    result = {}
-686    for c in range(no_corrs):
-687        deltas = [np.asarray(data[c])[index] for index in indices]
-688        result[(corr_name[c], str(corr_kappa[c]))] = Obs([deltas], [ensemble_name], idl=[idl_target])
-689
-690    # Check that all data entries have the same number of configurations
-691    if len(set([o.N for o in list(result.values())])) != 1:
-692        raise Exception('Error: Not all correlators have the same number of configurations. bdio file is possibly corrupted.')
-693
-694    return result
+599    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_read), ctypes.c_char_p(b_form))
+600
+601    print('Reading of bdio file started')
+602    while True:
+603        bdio_seek_record(fbdio)
+604        ruinfo = bdio_get_ruinfo(fbdio)
+605        if ruinfo < 0:
+606            # EOF reached
+607            break
+608        rlen = bdio_get_rlen(fbdio)
+609        if ruinfo == 5:
+610            d_buf = ctypes.c_double * (2 + d0)
+611            pd_buf = d_buf()
+612            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+613            bdio_read_f64(ppd_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
+614            tmp_mean = np.mean(np.asarray(pd_buf[2:]))
+615
+616            data[corr_no].append(tmp_mean)
+617            corr_no += 1
+618        else:
+619            alt_buf = ctypes.create_string_buffer(1024)
+620            palt_buf = ctypes.c_char_p(ctypes.addressof(alt_buf))
+621            iread = bdio_read(palt_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
+622            if rlen != iread:
+623                print('Error')
+624            for i, item in enumerate(alt_buf):
+625                if item == b'\x00':
+626                    alt_buf[i] = b' '
+627            tmp_string = (alt_buf[:].decode("utf-8")).rstrip()
+628            if ruinfo == 0:
+629                creator = _get_kwd(tmp_string, 'CREATOR=')
+630                ensemble_name = _get_kwd(tmp_string, 'ENSEMBLE=')
+631                volume.append(int(_get_kwd(tmp_string, 'L0=')))
+632                volume.append(int(_get_kwd(tmp_string, 'L1=')))
+633                volume.append(int(_get_kwd(tmp_string, 'L2=')))
+634                volume.append(int(_get_kwd(tmp_string, 'L3=')))
+635                boundary_conditions.append(_get_kwd(tmp_string, 'BC0='))
+636                boundary_conditions.append(_get_kwd(tmp_string, 'BC1='))
+637                boundary_conditions.append(_get_kwd(tmp_string, 'BC2='))
+638                boundary_conditions.append(_get_kwd(tmp_string, 'BC3='))
+639
+640            if ruinfo == 1:
+641                corr_name.append(_get_corr_name(tmp_string, 'CORR_NAME='))
+642                corr_type.append(_get_kwd(tmp_string, 'DATATYPE='))
+643                corr_props.append(_get_kwd(tmp_string, 'PROP0='))
+644                if d0 == 0:
+645                    d0 = int(_get_kwd(tmp_string, 'D0='))
+646                else:
+647                    if d0 != int(_get_kwd(tmp_string, 'D0=')):
+648                        print('Error: Varying number of time values')
+649            if ruinfo == 2:
+650                prop_kappa.append(_get_kwd(tmp_string, 'KAPPA='))
+651            if ruinfo == 4:
+652                cnfg_no = int(_get_kwd(tmp_string, 'CNFG_ID='))
+653                if stop:
+654                    if cnfg_no > kwargs.get('stop'):
+655                        break
+656                idl.append(cnfg_no)
+657                print(f'\rReading configuration {cnfg_no}', end='\r')
+658                if len(idl) == 1:
+659                    no_corrs = len(corr_name)
+660                    data = []
+661                    for _ in range(no_corrs):
+662                        data.append([])
+663
+664                corr_no = 0
+665    bdio_close(fbdio)
+666
+667    print('\nCreator: ', creator)
+668    print('Ensemble: ', ensemble_name)
+669    print('Lattice volume: ', volume)
+670    print('Boundary conditions: ', boundary_conditions)
+671    print('Number of random sources: ', d0)
+672    print('Number of corrs: ', len(corr_name))
+673    print('Number of configurations: ', cnfg_no + 1)
+674
+675    corr_kappa = []  # Contains kappa values for both propagators of given correlation function
+676    for item in corr_props:
+677        corr_kappa.append(float(prop_kappa[int(item)]))
+678
+679    if stop is None:
+680        stop = idl[-1]
+681    idl_target = range(start, stop + 1, step)
+682    try:
+683        indices = [idl.index(i) for i in idl_target]
+684    except ValueError as err:
+685        raise Exception('Configurations in file do no match target list!', err) from err
+686
+687    result = {}
+688    for c in range(no_corrs):
+689        deltas = [np.asarray(data[c])[index] for index in indices]
+690        result[(corr_name[c], str(corr_kappa[c]))] = Obs([deltas], [ensemble_name], idl=[idl_target])
+691
+692    # Check that all data entries have the same number of configurations
+693    if len(set([o.N for o in list(result.values())])) != 1:
+694        raise Exception('Error: Not all correlators have the same number of configurations. bdio file is possibly corrupted.')
+695
+696    return result
 
@@ -794,160 +796,160 @@
-
  8def read_ADerrors(file_path, bdio_path='./libbdio.so', **kwargs):
-  9    """ Extract generic MCMC data from a bdio file
- 10
- 11    read_ADerrors requires bdio to be compiled into a shared library. This can be achieved by
- 12    adding the flag -fPIC to CC and changing the all target to
- 13
- 14    all:		bdio.o $(LIBDIR)
- 15                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
- 16                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
- 17
- 18    Parameters
- 19    ----------
- 20    file_path -- path to the bdio file
- 21    bdio_path -- path to the shared bdio library libbdio.so (default ./libbdio.so)
- 22
- 23    Returns
- 24    -------
- 25    data : List[Obs]
- 26        Extracted data
- 27    """
- 28    bdio = ctypes.cdll.LoadLibrary(bdio_path)
- 29
- 30    bdio_open = bdio.bdio_open
- 31    bdio_open.restype = ctypes.c_void_p
- 32
- 33    bdio_close = bdio.bdio_close
- 34    bdio_close.restype = ctypes.c_int
- 35    bdio_close.argtypes = [ctypes.c_void_p]
- 36
- 37    bdio_seek_record = bdio.bdio_seek_record
- 38    bdio_seek_record.restype = ctypes.c_int
- 39    bdio_seek_record.argtypes = [ctypes.c_void_p]
- 40
- 41    bdio_get_rlen = bdio.bdio_get_rlen
- 42    bdio_get_rlen.restype = ctypes.c_int
- 43    bdio_get_rlen.argtypes = [ctypes.c_void_p]
- 44
- 45    bdio_get_ruinfo = bdio.bdio_get_ruinfo
- 46    bdio_get_ruinfo.restype = ctypes.c_int
- 47    bdio_get_ruinfo.argtypes = [ctypes.c_void_p]
- 48
- 49    bdio_read = bdio.bdio_read
- 50    bdio_read.restype = ctypes.c_size_t
- 51    bdio_read.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
- 52
- 53    bdio_read_f64 = bdio.bdio_read_f64
- 54    bdio_read_f64.restype = ctypes.c_size_t
- 55    bdio_read_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
- 56
- 57    bdio_read_int32 = bdio.bdio_read_int32
- 58    bdio_read_int32.restype = ctypes.c_size_t
- 59    bdio_read_int32.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
- 60
- 61    b_path = file_path.encode('utf-8')
- 62    read = 'r'
- 63    b_read = read.encode('utf-8')
- 64
- 65    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_read), None)
+            
 10def read_ADerrors(file_path, bdio_path='./libbdio.so', **kwargs):
+ 11    """ Extract generic MCMC data from a bdio file
+ 12
+ 13    read_ADerrors requires bdio to be compiled into a shared library. This can be achieved by
+ 14    adding the flag -fPIC to CC and changing the all target to
+ 15
+ 16    all:		bdio.o $(LIBDIR)
+ 17                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
+ 18                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
+ 19
+ 20    Parameters
+ 21    ----------
+ 22    file_path -- path to the bdio file
+ 23    bdio_path -- path to the shared bdio library libbdio.so (default ./libbdio.so)
+ 24
+ 25    Returns
+ 26    -------
+ 27    data : List[Obs]
+ 28        Extracted data
+ 29    """
+ 30    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+ 31
+ 32    bdio_open = bdio.bdio_open
+ 33    bdio_open.restype = ctypes.c_void_p
+ 34
+ 35    bdio_close = bdio.bdio_close
+ 36    bdio_close.restype = ctypes.c_int
+ 37    bdio_close.argtypes = [ctypes.c_void_p]
+ 38
+ 39    bdio_seek_record = bdio.bdio_seek_record
+ 40    bdio_seek_record.restype = ctypes.c_int
+ 41    bdio_seek_record.argtypes = [ctypes.c_void_p]
+ 42
+ 43    bdio_get_rlen = bdio.bdio_get_rlen
+ 44    bdio_get_rlen.restype = ctypes.c_int
+ 45    bdio_get_rlen.argtypes = [ctypes.c_void_p]
+ 46
+ 47    bdio_get_ruinfo = bdio.bdio_get_ruinfo
+ 48    bdio_get_ruinfo.restype = ctypes.c_int
+ 49    bdio_get_ruinfo.argtypes = [ctypes.c_void_p]
+ 50
+ 51    bdio_read = bdio.bdio_read
+ 52    bdio_read.restype = ctypes.c_size_t
+ 53    bdio_read.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
+ 54
+ 55    bdio_read_f64 = bdio.bdio_read_f64
+ 56    bdio_read_f64.restype = ctypes.c_size_t
+ 57    bdio_read_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
+ 58
+ 59    bdio_read_int32 = bdio.bdio_read_int32
+ 60    bdio_read_int32.restype = ctypes.c_size_t
+ 61    bdio_read_int32.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
+ 62
+ 63    b_path = file_path.encode('utf-8')
+ 64    read = 'r'
+ 65    b_read = read.encode('utf-8')
  66
- 67    return_list = []
+ 67    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_read), None)
  68
- 69    print('Reading of bdio file started')
- 70    while True:
- 71        bdio_seek_record(fbdio)
- 72        ruinfo = bdio_get_ruinfo(fbdio)
- 73
- 74        if ruinfo == 7:
- 75            print('MD5sum found')  # For now we just ignore these entries and do not perform any checks on them
- 76            continue
- 77
- 78        if ruinfo < 0:
- 79            # EOF reached
- 80            break
- 81        bdio_get_rlen(fbdio)
- 82
- 83        def read_c_double():
- 84            d_buf = ctypes.c_double
- 85            pd_buf = d_buf()
- 86            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
- 87            bdio_read_f64(ppd_buf, ctypes.c_size_t(8), ctypes.c_void_p(fbdio))
- 88            return pd_buf.value
- 89
- 90        mean = read_c_double()
- 91        print('mean', mean)
- 92
- 93        def read_c_size_t():
- 94            d_buf = ctypes.c_size_t
- 95            pd_buf = d_buf()
- 96            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
- 97            bdio_read_int32(ppd_buf, ctypes.c_size_t(4), ctypes.c_void_p(fbdio))
- 98            return pd_buf.value
- 99
-100        neid = read_c_size_t()
-101        print('neid', neid)
-102
-103        ndata = []
-104        for index in range(neid):
-105            ndata.append(read_c_size_t())
-106        print('ndata', ndata)
-107
-108        nrep = []
-109        for index in range(neid):
-110            nrep.append(read_c_size_t())
-111        print('nrep', nrep)
-112
-113        vrep = []
-114        for index in range(neid):
-115            vrep.append([])
-116            for jndex in range(nrep[index]):
-117                vrep[-1].append(read_c_size_t())
-118        print('vrep', vrep)
-119
-120        ids = []
-121        for index in range(neid):
-122            ids.append(read_c_size_t())
-123        print('ids', ids)
-124
-125        nt = []
-126        for index in range(neid):
-127            nt.append(read_c_size_t())
-128        print('nt', nt)
-129
-130        zero = []
-131        for index in range(neid):
-132            zero.append(read_c_double())
-133        print('zero', zero)
-134
-135        four = []
-136        for index in range(neid):
-137            four.append(read_c_double())
-138        print('four', four)
-139
-140        d_buf = ctypes.c_double * np.sum(ndata)
-141        pd_buf = d_buf()
-142        ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
-143        bdio_read_f64(ppd_buf, ctypes.c_size_t(8 * np.sum(ndata)), ctypes.c_void_p(fbdio))
-144        delta = pd_buf[:]
-145
-146        samples = np.split(np.asarray(delta) + mean, np.cumsum([a for su in vrep for a in su])[:-1])
-147        no_reps = [len(o) for o in vrep]
-148        assert len(ids) == len(no_reps)
-149        tmp_names = []
-150        ens_length = max([len(str(o)) for o in ids])
-151        for loc_id, reps in zip(ids, no_reps):
-152            for index in range(reps):
-153                missing_chars = ens_length - len(str(loc_id))
-154                tmp_names.append(str(loc_id) + ' ' * missing_chars + '|r' + '{0:03d}'.format(index))
-155
-156        return_list.append(Obs(samples, tmp_names))
+ 69    return_list = []
+ 70
+ 71    print('Reading of bdio file started')
+ 72    while True:
+ 73        bdio_seek_record(fbdio)
+ 74        ruinfo = bdio_get_ruinfo(fbdio)
+ 75
+ 76        if ruinfo == 7:
+ 77            print('MD5sum found')  # For now we just ignore these entries and do not perform any checks on them
+ 78            continue
+ 79
+ 80        if ruinfo < 0:
+ 81            # EOF reached
+ 82            break
+ 83        bdio_get_rlen(fbdio)
+ 84
+ 85        def read_c_double():
+ 86            d_buf = ctypes.c_double
+ 87            pd_buf = d_buf()
+ 88            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+ 89            bdio_read_f64(ppd_buf, ctypes.c_size_t(8), ctypes.c_void_p(fbdio))
+ 90            return pd_buf.value
+ 91
+ 92        mean = read_c_double()
+ 93        print('mean', mean)
+ 94
+ 95        def read_c_size_t():
+ 96            d_buf = ctypes.c_size_t
+ 97            pd_buf = d_buf()
+ 98            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+ 99            bdio_read_int32(ppd_buf, ctypes.c_size_t(4), ctypes.c_void_p(fbdio))
+100            return pd_buf.value
+101
+102        neid = read_c_size_t()
+103        print('neid', neid)
+104
+105        ndata = []
+106        for _ in range(neid):
+107            ndata.append(read_c_size_t())
+108        print('ndata', ndata)
+109
+110        nrep = []
+111        for _ in range(neid):
+112            nrep.append(read_c_size_t())
+113        print('nrep', nrep)
+114
+115        vrep = []
+116        for index in range(neid):
+117            vrep.append([])
+118            for _jndex in range(nrep[index]):
+119                vrep[-1].append(read_c_size_t())
+120        print('vrep', vrep)
+121
+122        ids = []
+123        for _ in range(neid):
+124            ids.append(read_c_size_t())
+125        print('ids', ids)
+126
+127        nt = []
+128        for _ in range(neid):
+129            nt.append(read_c_size_t())
+130        print('nt', nt)
+131
+132        zero = []
+133        for _ in range(neid):
+134            zero.append(read_c_double())
+135        print('zero', zero)
+136
+137        four = []
+138        for _ in range(neid):
+139            four.append(read_c_double())
+140        print('four', four)
+141
+142        d_buf = ctypes.c_double * np.sum(ndata)
+143        pd_buf = d_buf()
+144        ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+145        bdio_read_f64(ppd_buf, ctypes.c_size_t(8 * np.sum(ndata)), ctypes.c_void_p(fbdio))
+146        delta = pd_buf[:]
+147
+148        samples = np.split(np.asarray(delta) + mean, np.cumsum([a for su in vrep for a in su])[:-1])
+149        no_reps = [len(o) for o in vrep]
+150        assert len(ids) == len(no_reps)
+151        tmp_names = []
+152        ens_length = max([len(str(o)) for o in ids])
+153        for loc_id, reps in zip(ids, no_reps, strict=True):
+154            for index in range(reps):
+155                missing_chars = ens_length - len(str(loc_id))
+156                tmp_names.append(str(loc_id) + ' ' * missing_chars + '|r' + f'{index:03d}')
 157
-158    bdio_close(fbdio)
-159    print()
-160    print(len(return_list), 'observable(s) extracted.')
-161    return return_list
+158        return_list.append(Obs(samples, tmp_names))
+159
+160    bdio_close(fbdio)
+161    print()
+162    print(len(return_list), 'observable(s) extracted.')
+163    return return_list
 
@@ -988,134 +990,134 @@ Extracted data
-
164def write_ADerrors(obs_list, file_path, bdio_path='./libbdio.so', **kwargs):
-165    """ Write Obs to a bdio file according to ADerrors conventions
-166
-167    read_mesons requires bdio to be compiled into a shared library. This can be achieved by
-168    adding the flag -fPIC to CC and changing the all target to
-169
-170    all:		bdio.o $(LIBDIR)
-171                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
-172                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
-173
-174    Parameters
-175    ----------
-176    file_path -- path to the bdio file
-177    bdio_path -- path to the shared bdio library libbdio.so (default ./libbdio.so)
-178
-179    Returns
-180    -------
-181    success : int
-182        returns 0 is successful
-183    """
-184
-185    for obs in obs_list:
-186        if not hasattr(obs, 'e_names'):
-187            raise Exception('Run the gamma method first for all obs.')
-188
-189    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+            
166def write_ADerrors(obs_list, file_path, bdio_path='./libbdio.so', **kwargs):
+167    """ Write Obs to a bdio file according to ADerrors conventions
+168
+169    read_mesons requires bdio to be compiled into a shared library. This can be achieved by
+170    adding the flag -fPIC to CC and changing the all target to
+171
+172    all:		bdio.o $(LIBDIR)
+173                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
+174                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
+175
+176    Parameters
+177    ----------
+178    file_path -- path to the bdio file
+179    bdio_path -- path to the shared bdio library libbdio.so (default ./libbdio.so)
+180
+181    Returns
+182    -------
+183    success : int
+184        returns 0 is successful
+185    """
+186
+187    for obs in obs_list:
+188        if not hasattr(obs, 'e_names'):
+189            raise Exception('Run the gamma method first for all obs.')
 190
-191    bdio_open = bdio.bdio_open
-192    bdio_open.restype = ctypes.c_void_p
-193
-194    bdio_close = bdio.bdio_close
-195    bdio_close.restype = ctypes.c_int
-196    bdio_close.argtypes = [ctypes.c_void_p]
-197
-198    bdio_start_record = bdio.bdio_start_record
-199    bdio_start_record.restype = ctypes.c_int
-200    bdio_start_record.argtypes = [ctypes.c_size_t, ctypes.c_size_t, ctypes.c_void_p]
-201
-202    bdio_flush_record = bdio.bdio_flush_record
-203    bdio_flush_record.restype = ctypes.c_int
-204    bdio_flush_record.argytpes = [ctypes.c_void_p]
-205
-206    bdio_write_f64 = bdio.bdio_write_f64
-207    bdio_write_f64.restype = ctypes.c_size_t
-208    bdio_write_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
-209
-210    bdio_write_int32 = bdio.bdio_write_int32
-211    bdio_write_int32.restype = ctypes.c_size_t
-212    bdio_write_int32.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
-213
-214    b_path = file_path.encode('utf-8')
-215    write = 'w'
-216    b_write = write.encode('utf-8')
-217    form = 'pyerrors ADerror export'
-218    b_form = form.encode('utf-8')
-219
-220    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_write), b_form)
+191    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+192
+193    bdio_open = bdio.bdio_open
+194    bdio_open.restype = ctypes.c_void_p
+195
+196    bdio_close = bdio.bdio_close
+197    bdio_close.restype = ctypes.c_int
+198    bdio_close.argtypes = [ctypes.c_void_p]
+199
+200    bdio_start_record = bdio.bdio_start_record
+201    bdio_start_record.restype = ctypes.c_int
+202    bdio_start_record.argtypes = [ctypes.c_size_t, ctypes.c_size_t, ctypes.c_void_p]
+203
+204    bdio_flush_record = bdio.bdio_flush_record
+205    bdio_flush_record.restype = ctypes.c_int
+206    bdio_flush_record.argytpes = [ctypes.c_void_p]
+207
+208    bdio_write_f64 = bdio.bdio_write_f64
+209    bdio_write_f64.restype = ctypes.c_size_t
+210    bdio_write_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
+211
+212    bdio_write_int32 = bdio.bdio_write_int32
+213    bdio_write_int32.restype = ctypes.c_size_t
+214    bdio_write_int32.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
+215
+216    b_path = file_path.encode('utf-8')
+217    write = 'w'
+218    b_write = write.encode('utf-8')
+219    form = 'pyerrors ADerror export'
+220    b_form = form.encode('utf-8')
 221
-222    for obs in obs_list:
-223        # mean = obs.value
-224        neid = len(obs.e_names)
-225        vrep = [[obs.shape[o] for o in sl] for sl in list(obs.e_content.values())]
-226        vrep_write = [item for sublist in vrep for item in sublist]
-227        ndata = [np.sum(o) for o in vrep]
-228        nrep = [len(o) for o in vrep]
-229        print('ndata', ndata)
-230        print('nrep', nrep)
-231        print('vrep', vrep)
-232        keys = list(obs.e_content.keys())
-233        ids = []
-234        for key in keys:
-235            try:  # Try to convert key to integer
-236                ids.append(int(key))
-237            except Exception:  # If not possible construct a hash
-238                ids.append(int(hashlib.sha256(key.encode('utf-8')).hexdigest(), 16) % 10 ** 8)
-239        print('ids', ids)
-240        nt = []
-241        for e, e_name in enumerate(obs.e_names):
-242
-243            r_length = []
-244            for r_name in obs.e_content[e_name]:
-245                r_length.append(len(obs.deltas[r_name]))
-246
-247            # e_N = np.sum(r_length)
-248            nt.append(max(r_length) // 2)
-249        print('nt', nt)
-250        zero = neid * [0.0]
-251        four = neid * [4.0]
-252        print('zero', zero)
-253        print('four', four)
-254        delta = np.concatenate([item for sublist in [[obs.deltas[o] for o in sl] for sl in list(obs.e_content.values())] for item in sublist])
-255
-256        bdio_start_record(0x00, 8, fbdio)
+222    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_write), b_form)
+223
+224    for obs in obs_list:
+225        # mean = obs.value
+226        neid = len(obs.e_names)
+227        vrep = [[obs.shape[o] for o in sl] for sl in list(obs.e_content.values())]
+228        vrep_write = [item for sublist in vrep for item in sublist]
+229        ndata = [np.sum(o) for o in vrep]
+230        nrep = [len(o) for o in vrep]
+231        print('ndata', ndata)
+232        print('nrep', nrep)
+233        print('vrep', vrep)
+234        keys = list(obs.e_content.keys())
+235        ids = []
+236        for key in keys:
+237            try:  # Try to convert key to integer
+238                ids.append(int(key))
+239            except Exception:  # If not possible construct a hash
+240                ids.append(int(hashlib.sha256(key.encode('utf-8')).hexdigest(), 16) % 10 ** 8)
+241        print('ids', ids)
+242        nt = []
+243        for _e, e_name in enumerate(obs.e_names):
+244
+245            r_length = []
+246            for r_name in obs.e_content[e_name]:
+247                r_length.append(len(obs.deltas[r_name]))
+248
+249            # e_N = np.sum(r_length)
+250            nt.append(max(r_length) // 2)
+251        print('nt', nt)
+252        zero = neid * [0.0]
+253        four = neid * [4.0]
+254        print('zero', zero)
+255        print('four', four)
+256        delta = np.concatenate([item for sublist in [[obs.deltas[o] for o in sl] for sl in list(obs.e_content.values())] for item in sublist])
 257
-258        def write_c_double(double):
-259            pd_buf = ctypes.c_double(double)
-260            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
-261            bdio_write_f64(ppd_buf, ctypes.c_size_t(8), ctypes.c_void_p(fbdio))
-262
-263        def write_c_size_t(int32):
-264            pd_buf = ctypes.c_size_t(int32)
-265            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
-266            bdio_write_int32(ppd_buf, ctypes.c_size_t(4), ctypes.c_void_p(fbdio))
-267
-268        write_c_double(obs.value)
-269        write_c_size_t(neid)
-270
-271        for element in ndata:
-272            write_c_size_t(element)
-273        for element in nrep:
+258        bdio_start_record(0x00, 8, fbdio)
+259
+260        def write_c_double(double):
+261            pd_buf = ctypes.c_double(double)
+262            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+263            bdio_write_f64(ppd_buf, ctypes.c_size_t(8), ctypes.c_void_p(fbdio))
+264
+265        def write_c_size_t(int32):
+266            pd_buf = ctypes.c_size_t(int32)
+267            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+268            bdio_write_int32(ppd_buf, ctypes.c_size_t(4), ctypes.c_void_p(fbdio))
+269
+270        write_c_double(obs.value)
+271        write_c_size_t(neid)
+272
+273        for element in ndata:
 274            write_c_size_t(element)
-275        for element in vrep_write:
+275        for element in nrep:
 276            write_c_size_t(element)
-277        for element in ids:
+277        for element in vrep_write:
 278            write_c_size_t(element)
-279        for element in nt:
+279        for element in ids:
 280            write_c_size_t(element)
-281
-282        for element in zero:
-283            write_c_double(element)
-284        for element in four:
+281        for element in nt:
+282            write_c_size_t(element)
+283
+284        for element in zero:
 285            write_c_double(element)
-286
-287        for element in delta:
-288            write_c_double(element)
-289
-290    bdio_close(fbdio)
-291    return 0
+286        for element in four:
+287            write_c_double(element)
+288
+289        for element in delta:
+290            write_c_double(element)
+291
+292    bdio_close(fbdio)
+293    return 0
 
@@ -1156,219 +1158,219 @@ returns 0 is successful
-
302def read_mesons(file_path, bdio_path='./libbdio.so', **kwargs):
-303    """ Extract mesons data from a bdio file and return it as a dictionary
-304
-305    The dictionary can be accessed with a tuple consisting of (type, source_position, kappa1, kappa2)
+            
304def read_mesons(file_path, bdio_path='./libbdio.so', **kwargs):
+305    """ Extract mesons data from a bdio file and return it as a dictionary
 306
-307    read_mesons requires bdio to be compiled into a shared library. This can be achieved by
-308    adding the flag -fPIC to CC and changing the all target to
-309
-310    all:		bdio.o $(LIBDIR)
-311                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
-312                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
-313
-314    Parameters
-315    ----------
-316    file_path : str
-317        path to the bdio file
-318    bdio_path : str
-319        path to the shared bdio library libbdio.so (default ./libbdio.so)
-320    start : int
-321        The first configuration to be read (default 1)
-322    stop : int
-323        The last configuration to be read (default None)
-324    step : int
-325        Fixed step size between two measurements (default 1)
-326    alternative_ensemble_name : str
-327        Manually overwrite ensemble name
-328
-329    Returns
-330    -------
-331    data : dict
-332        Extracted meson data
-333    """
-334
-335    start = kwargs.get('start', 1)
-336    stop = kwargs.get('stop', None)
-337    step = kwargs.get('step', 1)
-338
-339    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+307    The dictionary can be accessed with a tuple consisting of (type, source_position, kappa1, kappa2)
+308
+309    read_mesons requires bdio to be compiled into a shared library. This can be achieved by
+310    adding the flag -fPIC to CC and changing the all target to
+311
+312    all:		bdio.o $(LIBDIR)
+313                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
+314                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
+315
+316    Parameters
+317    ----------
+318    file_path : str
+319        path to the bdio file
+320    bdio_path : str
+321        path to the shared bdio library libbdio.so (default ./libbdio.so)
+322    start : int
+323        The first configuration to be read (default 1)
+324    stop : int
+325        The last configuration to be read (default None)
+326    step : int
+327        Fixed step size between two measurements (default 1)
+328    alternative_ensemble_name : str
+329        Manually overwrite ensemble name
+330
+331    Returns
+332    -------
+333    data : dict
+334        Extracted meson data
+335    """
+336
+337    start = kwargs.get('start', 1)
+338    stop = kwargs.get('stop', None)
+339    step = kwargs.get('step', 1)
 340
-341    bdio_open = bdio.bdio_open
-342    bdio_open.restype = ctypes.c_void_p
-343
-344    bdio_close = bdio.bdio_close
-345    bdio_close.restype = ctypes.c_int
-346    bdio_close.argtypes = [ctypes.c_void_p]
-347
-348    bdio_seek_record = bdio.bdio_seek_record
-349    bdio_seek_record.restype = ctypes.c_int
-350    bdio_seek_record.argtypes = [ctypes.c_void_p]
-351
-352    bdio_get_rlen = bdio.bdio_get_rlen
-353    bdio_get_rlen.restype = ctypes.c_int
-354    bdio_get_rlen.argtypes = [ctypes.c_void_p]
-355
-356    bdio_get_ruinfo = bdio.bdio_get_ruinfo
-357    bdio_get_ruinfo.restype = ctypes.c_int
-358    bdio_get_ruinfo.argtypes = [ctypes.c_void_p]
-359
-360    bdio_read = bdio.bdio_read
-361    bdio_read.restype = ctypes.c_size_t
-362    bdio_read.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
-363
-364    bdio_read_f64 = bdio.bdio_read_f64
-365    bdio_read_f64.restype = ctypes.c_size_t
-366    bdio_read_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
-367
-368    b_path = file_path.encode('utf-8')
-369    read = 'r'
-370    b_read = read.encode('utf-8')
-371    form = 'Generic Correlator Format 1.0'
-372    b_form = form.encode('utf-8')
-373
-374    ensemble_name = ''
-375    volume = []  # lattice volume
-376    boundary_conditions = []
-377    corr_name = []  # Contains correlator names
-378    corr_type = []  # Contains correlator data type (important for reading out numerical data)
-379    corr_props = []  # Contanis propagator types (Component of corr_kappa)
-380    d0 = 0  # tvals
-381    d1 = 0  # nnoise
-382    prop_kappa = []  # Contains propagator kappas (Component of corr_kappa)
-383    prop_source = []  # Contains propagator source positions
-384    # Check noise type for multiple replica?
-385    corr_no = -1
-386    data = []
-387    idl = []
-388
-389    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_read), ctypes.c_char_p(b_form))
+341    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+342
+343    bdio_open = bdio.bdio_open
+344    bdio_open.restype = ctypes.c_void_p
+345
+346    bdio_close = bdio.bdio_close
+347    bdio_close.restype = ctypes.c_int
+348    bdio_close.argtypes = [ctypes.c_void_p]
+349
+350    bdio_seek_record = bdio.bdio_seek_record
+351    bdio_seek_record.restype = ctypes.c_int
+352    bdio_seek_record.argtypes = [ctypes.c_void_p]
+353
+354    bdio_get_rlen = bdio.bdio_get_rlen
+355    bdio_get_rlen.restype = ctypes.c_int
+356    bdio_get_rlen.argtypes = [ctypes.c_void_p]
+357
+358    bdio_get_ruinfo = bdio.bdio_get_ruinfo
+359    bdio_get_ruinfo.restype = ctypes.c_int
+360    bdio_get_ruinfo.argtypes = [ctypes.c_void_p]
+361
+362    bdio_read = bdio.bdio_read
+363    bdio_read.restype = ctypes.c_size_t
+364    bdio_read.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
+365
+366    bdio_read_f64 = bdio.bdio_read_f64
+367    bdio_read_f64.restype = ctypes.c_size_t
+368    bdio_read_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
+369
+370    b_path = file_path.encode('utf-8')
+371    read = 'r'
+372    b_read = read.encode('utf-8')
+373    form = 'Generic Correlator Format 1.0'
+374    b_form = form.encode('utf-8')
+375
+376    ensemble_name = ''
+377    volume = []  # lattice volume
+378    boundary_conditions = []
+379    corr_name = []  # Contains correlator names
+380    corr_type = []  # Contains correlator data type (important for reading out numerical data)
+381    corr_props = []  # Contanis propagator types (Component of corr_kappa)
+382    d0 = 0  # tvals
+383    d1 = 0  # nnoise
+384    prop_kappa = []  # Contains propagator kappas (Component of corr_kappa)
+385    prop_source = []  # Contains propagator source positions
+386    # Check noise type for multiple replica?
+387    corr_no = -1
+388    data = []
+389    idl = []
 390
-391    print('Reading of bdio file started')
-392    while True:
-393        bdio_seek_record(fbdio)
-394        ruinfo = bdio_get_ruinfo(fbdio)
-395        if ruinfo < 0:
-396            # EOF reached
-397            break
-398        rlen = bdio_get_rlen(fbdio)
-399        if ruinfo == 5:
-400            d_buf = ctypes.c_double * (2 + d0 * d1 * 2)
-401            pd_buf = d_buf()
-402            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
-403            bdio_read_f64(ppd_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
-404            if corr_type[corr_no] == 'complex':
-405                tmp_mean = np.mean(np.asarray(np.split(np.asarray(pd_buf[2 + 2 * d1:-2 * d1:2]), d0 - 2)), axis=1)
-406            else:
-407                tmp_mean = np.mean(np.asarray(np.split(np.asarray(pd_buf[2 + d1:-d0 * d1 - d1]), d0 - 2)), axis=1)
-408
-409            data[corr_no].append(tmp_mean)
-410            corr_no += 1
-411        else:
-412            alt_buf = ctypes.create_string_buffer(1024)
-413            palt_buf = ctypes.c_char_p(ctypes.addressof(alt_buf))
-414            iread = bdio_read(palt_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
-415            if rlen != iread:
-416                print('Error')
-417            for i, item in enumerate(alt_buf):
-418                if item == b'\x00':
-419                    alt_buf[i] = b' '
-420            tmp_string = (alt_buf[:].decode("utf-8")).rstrip()
-421            if ruinfo == 0:
-422                ensemble_name = _get_kwd(tmp_string, 'ENSEMBLE=')
-423                volume.append(int(_get_kwd(tmp_string, 'L0=')))
-424                volume.append(int(_get_kwd(tmp_string, 'L1=')))
-425                volume.append(int(_get_kwd(tmp_string, 'L2=')))
-426                volume.append(int(_get_kwd(tmp_string, 'L3=')))
-427                boundary_conditions.append(_get_kwd(tmp_string, 'BC0='))
-428                boundary_conditions.append(_get_kwd(tmp_string, 'BC1='))
-429                boundary_conditions.append(_get_kwd(tmp_string, 'BC2='))
-430                boundary_conditions.append(_get_kwd(tmp_string, 'BC3='))
-431
-432            if ruinfo == 1:
-433                corr_name.append(_get_corr_name(tmp_string, 'CORR_NAME='))
-434                corr_type.append(_get_kwd(tmp_string, 'DATATYPE='))
-435                corr_props.append([_get_kwd(tmp_string, 'PROP0='), _get_kwd(tmp_string, 'PROP1=')])
-436                if d0 == 0:
-437                    d0 = int(_get_kwd(tmp_string, 'D0='))
-438                else:
-439                    if d0 != int(_get_kwd(tmp_string, 'D0=')):
-440                        print('Error: Varying number of time values')
-441                if d1 == 0:
-442                    d1 = int(_get_kwd(tmp_string, 'D1='))
-443                else:
-444                    if d1 != int(_get_kwd(tmp_string, 'D1=')):
-445                        print('Error: Varying number of random sources')
-446            if ruinfo == 2:
-447                prop_kappa.append(_get_kwd(tmp_string, 'KAPPA='))
-448                prop_source.append(_get_kwd(tmp_string, 'x0='))
-449            if ruinfo == 4:
-450                cnfg_no = int(_get_kwd(tmp_string, 'CNFG_ID='))
-451                if stop:
-452                    if cnfg_no > kwargs.get('stop'):
-453                        break
-454                idl.append(cnfg_no)
-455                print('\r%s %i' % ('Reading configuration', cnfg_no), end='\r')
-456                if len(idl) == 1:
-457                    no_corrs = len(corr_name)
-458                    data = []
-459                    for c in range(no_corrs):
-460                        data.append([])
-461
-462                corr_no = 0
+391    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_read), ctypes.c_char_p(b_form))
+392
+393    print('Reading of bdio file started')
+394    while True:
+395        bdio_seek_record(fbdio)
+396        ruinfo = bdio_get_ruinfo(fbdio)
+397        if ruinfo < 0:
+398            # EOF reached
+399            break
+400        rlen = bdio_get_rlen(fbdio)
+401        if ruinfo == 5:
+402            d_buf = ctypes.c_double * (2 + d0 * d1 * 2)
+403            pd_buf = d_buf()
+404            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+405            bdio_read_f64(ppd_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
+406            if corr_type[corr_no] == 'complex':
+407                tmp_mean = np.mean(np.asarray(np.split(np.asarray(pd_buf[2 + 2 * d1:-2 * d1:2]), d0 - 2)), axis=1)
+408            else:
+409                tmp_mean = np.mean(np.asarray(np.split(np.asarray(pd_buf[2 + d1:-d0 * d1 - d1]), d0 - 2)), axis=1)
+410
+411            data[corr_no].append(tmp_mean)
+412            corr_no += 1
+413        else:
+414            alt_buf = ctypes.create_string_buffer(1024)
+415            palt_buf = ctypes.c_char_p(ctypes.addressof(alt_buf))
+416            iread = bdio_read(palt_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
+417            if rlen != iread:
+418                print('Error')
+419            for i, item in enumerate(alt_buf):
+420                if item == b'\x00':
+421                    alt_buf[i] = b' '
+422            tmp_string = (alt_buf[:].decode("utf-8")).rstrip()
+423            if ruinfo == 0:
+424                ensemble_name = _get_kwd(tmp_string, 'ENSEMBLE=')
+425                volume.append(int(_get_kwd(tmp_string, 'L0=')))
+426                volume.append(int(_get_kwd(tmp_string, 'L1=')))
+427                volume.append(int(_get_kwd(tmp_string, 'L2=')))
+428                volume.append(int(_get_kwd(tmp_string, 'L3=')))
+429                boundary_conditions.append(_get_kwd(tmp_string, 'BC0='))
+430                boundary_conditions.append(_get_kwd(tmp_string, 'BC1='))
+431                boundary_conditions.append(_get_kwd(tmp_string, 'BC2='))
+432                boundary_conditions.append(_get_kwd(tmp_string, 'BC3='))
+433
+434            if ruinfo == 1:
+435                corr_name.append(_get_corr_name(tmp_string, 'CORR_NAME='))
+436                corr_type.append(_get_kwd(tmp_string, 'DATATYPE='))
+437                corr_props.append([_get_kwd(tmp_string, 'PROP0='), _get_kwd(tmp_string, 'PROP1=')])
+438                if d0 == 0:
+439                    d0 = int(_get_kwd(tmp_string, 'D0='))
+440                else:
+441                    if d0 != int(_get_kwd(tmp_string, 'D0=')):
+442                        print('Error: Varying number of time values')
+443                if d1 == 0:
+444                    d1 = int(_get_kwd(tmp_string, 'D1='))
+445                else:
+446                    if d1 != int(_get_kwd(tmp_string, 'D1=')):
+447                        print('Error: Varying number of random sources')
+448            if ruinfo == 2:
+449                prop_kappa.append(_get_kwd(tmp_string, 'KAPPA='))
+450                prop_source.append(_get_kwd(tmp_string, 'x0='))
+451            if ruinfo == 4:
+452                cnfg_no = int(_get_kwd(tmp_string, 'CNFG_ID='))
+453                if stop:
+454                    if cnfg_no > kwargs.get('stop'):
+455                        break
+456                idl.append(cnfg_no)
+457                print(f'\rReading configuration {cnfg_no}', end='\r')
+458                if len(idl) == 1:
+459                    no_corrs = len(corr_name)
+460                    data = []
+461                    for _ in range(no_corrs):
+462                        data.append([])
 463
-464    bdio_close(fbdio)
+464                corr_no = 0
 465
-466    print('\nEnsemble: ', ensemble_name)
-467    if 'alternative_ensemble_name' in kwargs:
-468        ensemble_name = kwargs.get('alternative_ensemble_name')
-469        print('Ensemble name overwritten to', ensemble_name)
-470    print('Lattice volume: ', volume)
-471    print('Boundary conditions: ', boundary_conditions)
-472    print('Number of time values: ', d0)
-473    print('Number of random sources: ', d1)
-474    print('Number of corrs: ', len(corr_name))
-475    print('Number of configurations: ', len(idl))
-476
-477    corr_kappa = []  # Contains kappa values for both propagators of given correlation function
-478    corr_source = []
-479    for item in corr_props:
-480        corr_kappa.append([float(prop_kappa[int(item[0])]), float(prop_kappa[int(item[1])])])
-481        if prop_source[int(item[0])] != prop_source[int(item[1])]:
-482            raise Exception('Source position do not match for correlator' + str(item))
-483        else:
-484            corr_source.append(int(prop_source[int(item[0])]))
-485
-486    if stop is None:
-487        stop = idl[-1]
-488    idl_target = range(start, stop + 1, step)
-489
-490    if set(idl) != set(idl_target):
-491        try:
-492            indices = [idl.index(i) for i in idl_target]
-493        except ValueError as err:
-494            raise Exception('Configurations in file do no match target list!', err)
-495    else:
-496        indices = None
-497
-498    result = {}
-499    for c in range(no_corrs):
-500        tmp_corr = []
-501        tmp_data = np.asarray(data[c])
-502        for t in range(d0 - 2):
-503            if indices:
-504                deltas = [tmp_data[:, t][index] for index in indices]
-505            else:
-506                deltas = tmp_data[:, t]
-507            tmp_corr.append(Obs([deltas], [ensemble_name], idl=[idl_target]))
-508        result[(corr_name[c], corr_source[c]) + tuple(corr_kappa[c])] = tmp_corr
-509
-510    # Check that all data entries have the same number of configurations
-511    if len(set([o[0].N for o in list(result.values())])) != 1:
-512        raise Exception('Error: Not all correlators have the same number of configurations. bdio file is possibly corrupted.')
-513
-514    return result
+466    bdio_close(fbdio)
+467
+468    print('\nEnsemble: ', ensemble_name)
+469    if 'alternative_ensemble_name' in kwargs:
+470        ensemble_name = kwargs.get('alternative_ensemble_name')
+471        print('Ensemble name overwritten to', ensemble_name)
+472    print('Lattice volume: ', volume)
+473    print('Boundary conditions: ', boundary_conditions)
+474    print('Number of time values: ', d0)
+475    print('Number of random sources: ', d1)
+476    print('Number of corrs: ', len(corr_name))
+477    print('Number of configurations: ', len(idl))
+478
+479    corr_kappa = []  # Contains kappa values for both propagators of given correlation function
+480    corr_source = []
+481    for item in corr_props:
+482        corr_kappa.append([float(prop_kappa[int(item[0])]), float(prop_kappa[int(item[1])])])
+483        if prop_source[int(item[0])] != prop_source[int(item[1])]:
+484            raise Exception('Source position do not match for correlator' + str(item))
+485        else:
+486            corr_source.append(int(prop_source[int(item[0])]))
+487
+488    if stop is None:
+489        stop = idl[-1]
+490    idl_target = range(start, stop + 1, step)
+491
+492    if set(idl) != set(idl_target):
+493        try:
+494            indices = [idl.index(i) for i in idl_target]
+495        except ValueError as err:
+496            raise Exception('Configurations in file do no match target list!', err) from err
+497    else:
+498        indices = None
+499
+500    result = {}
+501    for c in range(no_corrs):
+502        tmp_corr = []
+503        tmp_data = np.asarray(data[c])
+504        for t in range(d0 - 2):
+505            if indices:
+506                deltas = [tmp_data[:, t][index] for index in indices]
+507            else:
+508                deltas = tmp_data[:, t]
+509            tmp_corr.append(Obs([deltas], [ensemble_name], idl=[idl_target]))
+510        result[(corr_name[c], corr_source[c], *corr_kappa[c])] = tmp_corr
+511
+512    # Check that all data entries have the same number of configurations
+513    if len(set([o[0].N for o in list(result.values())])) != 1:
+514        raise Exception('Error: Not all correlators have the same number of configurations. bdio file is possibly corrupted.')
+515
+516    return result
 
@@ -1421,185 +1423,185 @@ Extracted meson data
-
517def read_dSdm(file_path, bdio_path='./libbdio.so', **kwargs):
-518    """ Extract dSdm data from a bdio file and return it as a dictionary
-519
-520    The dictionary can be accessed with a tuple consisting of (type, kappa)
+            
519def read_dSdm(file_path, bdio_path='./libbdio.so', **kwargs):
+520    """ Extract dSdm data from a bdio file and return it as a dictionary
 521
-522    read_dSdm requires bdio to be compiled into a shared library. This can be achieved by
-523    adding the flag -fPIC to CC and changing the all target to
-524
-525    all:		bdio.o $(LIBDIR)
-526                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
-527                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
-528
-529    Parameters
-530    ----------
-531    file_path : str
-532        path to the bdio file
-533    bdio_path : str
-534        path to the shared bdio library libbdio.so (default ./libbdio.so)
-535    start : int
-536        The first configuration to be read (default 1)
-537    stop : int
-538        The last configuration to be read (default None)
-539    step : int
-540        Fixed step size between two measurements (default 1)
-541    alternative_ensemble_name : str
-542        Manually overwrite ensemble name
-543    """
-544
-545    start = kwargs.get('start', 1)
-546    stop = kwargs.get('stop', None)
-547    step = kwargs.get('step', 1)
-548
-549    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+522    The dictionary can be accessed with a tuple consisting of (type, kappa)
+523
+524    read_dSdm requires bdio to be compiled into a shared library. This can be achieved by
+525    adding the flag -fPIC to CC and changing the all target to
+526
+527    all:		bdio.o $(LIBDIR)
+528                gcc -shared -Wl,-soname,libbdio.so -o $(BUILDDIR)/libbdio.so $(BUILDDIR)/bdio.o
+529                cp $(BUILDDIR)/libbdio.so $(LIBDIR)/
+530
+531    Parameters
+532    ----------
+533    file_path : str
+534        path to the bdio file
+535    bdio_path : str
+536        path to the shared bdio library libbdio.so (default ./libbdio.so)
+537    start : int
+538        The first configuration to be read (default 1)
+539    stop : int
+540        The last configuration to be read (default None)
+541    step : int
+542        Fixed step size between two measurements (default 1)
+543    alternative_ensemble_name : str
+544        Manually overwrite ensemble name
+545    """
+546
+547    start = kwargs.get('start', 1)
+548    stop = kwargs.get('stop', None)
+549    step = kwargs.get('step', 1)
 550
-551    bdio_open = bdio.bdio_open
-552    bdio_open.restype = ctypes.c_void_p
-553
-554    bdio_close = bdio.bdio_close
-555    bdio_close.restype = ctypes.c_int
-556    bdio_close.argtypes = [ctypes.c_void_p]
-557
-558    bdio_seek_record = bdio.bdio_seek_record
-559    bdio_seek_record.restype = ctypes.c_int
-560    bdio_seek_record.argtypes = [ctypes.c_void_p]
-561
-562    bdio_get_rlen = bdio.bdio_get_rlen
-563    bdio_get_rlen.restype = ctypes.c_int
-564    bdio_get_rlen.argtypes = [ctypes.c_void_p]
-565
-566    bdio_get_ruinfo = bdio.bdio_get_ruinfo
-567    bdio_get_ruinfo.restype = ctypes.c_int
-568    bdio_get_ruinfo.argtypes = [ctypes.c_void_p]
-569
-570    bdio_read = bdio.bdio_read
-571    bdio_read.restype = ctypes.c_size_t
-572    bdio_read.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
-573
-574    bdio_read_f64 = bdio.bdio_read_f64
-575    bdio_read_f64.restype = ctypes.c_size_t
-576    bdio_read_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
-577
-578    b_path = file_path.encode('utf-8')
-579    read = 'r'
-580    b_read = read.encode('utf-8')
-581    form = 'Generic Correlator Format 1.0'
-582    b_form = form.encode('utf-8')
-583
-584    ensemble_name = ''
-585    volume = []  # lattice volume
-586    boundary_conditions = []
-587    corr_name = []  # Contains correlator names
-588    corr_type = []  # Contains correlator data type (important for reading out numerical data)
-589    corr_props = []  # Contains propagator types (Component of corr_kappa)
-590    d0 = 0  # tvals
-591    # d1 = 0  # nnoise
-592    prop_kappa = []  # Contains propagator kappas (Component of corr_kappa)
-593    # Check noise type for multiple replica?
-594    corr_no = -1
-595    data = []
-596    idl = []
-597
-598    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_read), ctypes.c_char_p(b_form))
+551    bdio = ctypes.cdll.LoadLibrary(bdio_path)
+552
+553    bdio_open = bdio.bdio_open
+554    bdio_open.restype = ctypes.c_void_p
+555
+556    bdio_close = bdio.bdio_close
+557    bdio_close.restype = ctypes.c_int
+558    bdio_close.argtypes = [ctypes.c_void_p]
+559
+560    bdio_seek_record = bdio.bdio_seek_record
+561    bdio_seek_record.restype = ctypes.c_int
+562    bdio_seek_record.argtypes = [ctypes.c_void_p]
+563
+564    bdio_get_rlen = bdio.bdio_get_rlen
+565    bdio_get_rlen.restype = ctypes.c_int
+566    bdio_get_rlen.argtypes = [ctypes.c_void_p]
+567
+568    bdio_get_ruinfo = bdio.bdio_get_ruinfo
+569    bdio_get_ruinfo.restype = ctypes.c_int
+570    bdio_get_ruinfo.argtypes = [ctypes.c_void_p]
+571
+572    bdio_read = bdio.bdio_read
+573    bdio_read.restype = ctypes.c_size_t
+574    bdio_read.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
+575
+576    bdio_read_f64 = bdio.bdio_read_f64
+577    bdio_read_f64.restype = ctypes.c_size_t
+578    bdio_read_f64.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
+579
+580    b_path = file_path.encode('utf-8')
+581    read = 'r'
+582    b_read = read.encode('utf-8')
+583    form = 'Generic Correlator Format 1.0'
+584    b_form = form.encode('utf-8')
+585
+586    ensemble_name = ''
+587    volume = []  # lattice volume
+588    boundary_conditions = []
+589    corr_name = []  # Contains correlator names
+590    corr_type = []  # Contains correlator data type (important for reading out numerical data)
+591    corr_props = []  # Contains propagator types (Component of corr_kappa)
+592    d0 = 0  # tvals
+593    # d1 = 0  # nnoise
+594    prop_kappa = []  # Contains propagator kappas (Component of corr_kappa)
+595    # Check noise type for multiple replica?
+596    corr_no = -1
+597    data = []
+598    idl = []
 599
-600    print('Reading of bdio file started')
-601    while True:
-602        bdio_seek_record(fbdio)
-603        ruinfo = bdio_get_ruinfo(fbdio)
-604        if ruinfo < 0:
-605            # EOF reached
-606            break
-607        rlen = bdio_get_rlen(fbdio)
-608        if ruinfo == 5:
-609            d_buf = ctypes.c_double * (2 + d0)
-610            pd_buf = d_buf()
-611            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
-612            bdio_read_f64(ppd_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
-613            tmp_mean = np.mean(np.asarray(pd_buf[2:]))
-614
-615            data[corr_no].append(tmp_mean)
-616            corr_no += 1
-617        else:
-618            alt_buf = ctypes.create_string_buffer(1024)
-619            palt_buf = ctypes.c_char_p(ctypes.addressof(alt_buf))
-620            iread = bdio_read(palt_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
-621            if rlen != iread:
-622                print('Error')
-623            for i, item in enumerate(alt_buf):
-624                if item == b'\x00':
-625                    alt_buf[i] = b' '
-626            tmp_string = (alt_buf[:].decode("utf-8")).rstrip()
-627            if ruinfo == 0:
-628                creator = _get_kwd(tmp_string, 'CREATOR=')
-629                ensemble_name = _get_kwd(tmp_string, 'ENSEMBLE=')
-630                volume.append(int(_get_kwd(tmp_string, 'L0=')))
-631                volume.append(int(_get_kwd(tmp_string, 'L1=')))
-632                volume.append(int(_get_kwd(tmp_string, 'L2=')))
-633                volume.append(int(_get_kwd(tmp_string, 'L3=')))
-634                boundary_conditions.append(_get_kwd(tmp_string, 'BC0='))
-635                boundary_conditions.append(_get_kwd(tmp_string, 'BC1='))
-636                boundary_conditions.append(_get_kwd(tmp_string, 'BC2='))
-637                boundary_conditions.append(_get_kwd(tmp_string, 'BC3='))
-638
-639            if ruinfo == 1:
-640                corr_name.append(_get_corr_name(tmp_string, 'CORR_NAME='))
-641                corr_type.append(_get_kwd(tmp_string, 'DATATYPE='))
-642                corr_props.append(_get_kwd(tmp_string, 'PROP0='))
-643                if d0 == 0:
-644                    d0 = int(_get_kwd(tmp_string, 'D0='))
-645                else:
-646                    if d0 != int(_get_kwd(tmp_string, 'D0=')):
-647                        print('Error: Varying number of time values')
-648            if ruinfo == 2:
-649                prop_kappa.append(_get_kwd(tmp_string, 'KAPPA='))
-650            if ruinfo == 4:
-651                cnfg_no = int(_get_kwd(tmp_string, 'CNFG_ID='))
-652                if stop:
-653                    if cnfg_no > kwargs.get('stop'):
-654                        break
-655                idl.append(cnfg_no)
-656                print('\r%s %i' % ('Reading configuration', cnfg_no), end='\r')
-657                if len(idl) == 1:
-658                    no_corrs = len(corr_name)
-659                    data = []
-660                    for c in range(no_corrs):
-661                        data.append([])
-662
-663                corr_no = 0
-664    bdio_close(fbdio)
-665
-666    print('\nCreator: ', creator)
-667    print('Ensemble: ', ensemble_name)
-668    print('Lattice volume: ', volume)
-669    print('Boundary conditions: ', boundary_conditions)
-670    print('Number of random sources: ', d0)
-671    print('Number of corrs: ', len(corr_name))
-672    print('Number of configurations: ', cnfg_no + 1)
-673
-674    corr_kappa = []  # Contains kappa values for both propagators of given correlation function
-675    for item in corr_props:
-676        corr_kappa.append(float(prop_kappa[int(item)]))
-677
-678    if stop is None:
-679        stop = idl[-1]
-680    idl_target = range(start, stop + 1, step)
-681    try:
-682        indices = [idl.index(i) for i in idl_target]
-683    except ValueError as err:
-684        raise Exception('Configurations in file do no match target list!', err)
-685
-686    result = {}
-687    for c in range(no_corrs):
-688        deltas = [np.asarray(data[c])[index] for index in indices]
-689        result[(corr_name[c], str(corr_kappa[c]))] = Obs([deltas], [ensemble_name], idl=[idl_target])
-690
-691    # Check that all data entries have the same number of configurations
-692    if len(set([o.N for o in list(result.values())])) != 1:
-693        raise Exception('Error: Not all correlators have the same number of configurations. bdio file is possibly corrupted.')
-694
-695    return result
+600    fbdio = bdio_open(ctypes.c_char_p(b_path), ctypes.c_char_p(b_read), ctypes.c_char_p(b_form))
+601
+602    print('Reading of bdio file started')
+603    while True:
+604        bdio_seek_record(fbdio)
+605        ruinfo = bdio_get_ruinfo(fbdio)
+606        if ruinfo < 0:
+607            # EOF reached
+608            break
+609        rlen = bdio_get_rlen(fbdio)
+610        if ruinfo == 5:
+611            d_buf = ctypes.c_double * (2 + d0)
+612            pd_buf = d_buf()
+613            ppd_buf = ctypes.c_void_p(ctypes.addressof(pd_buf))
+614            bdio_read_f64(ppd_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
+615            tmp_mean = np.mean(np.asarray(pd_buf[2:]))
+616
+617            data[corr_no].append(tmp_mean)
+618            corr_no += 1
+619        else:
+620            alt_buf = ctypes.create_string_buffer(1024)
+621            palt_buf = ctypes.c_char_p(ctypes.addressof(alt_buf))
+622            iread = bdio_read(palt_buf, ctypes.c_size_t(rlen), ctypes.c_void_p(fbdio))
+623            if rlen != iread:
+624                print('Error')
+625            for i, item in enumerate(alt_buf):
+626                if item == b'\x00':
+627                    alt_buf[i] = b' '
+628            tmp_string = (alt_buf[:].decode("utf-8")).rstrip()
+629            if ruinfo == 0:
+630                creator = _get_kwd(tmp_string, 'CREATOR=')
+631                ensemble_name = _get_kwd(tmp_string, 'ENSEMBLE=')
+632                volume.append(int(_get_kwd(tmp_string, 'L0=')))
+633                volume.append(int(_get_kwd(tmp_string, 'L1=')))
+634                volume.append(int(_get_kwd(tmp_string, 'L2=')))
+635                volume.append(int(_get_kwd(tmp_string, 'L3=')))
+636                boundary_conditions.append(_get_kwd(tmp_string, 'BC0='))
+637                boundary_conditions.append(_get_kwd(tmp_string, 'BC1='))
+638                boundary_conditions.append(_get_kwd(tmp_string, 'BC2='))
+639                boundary_conditions.append(_get_kwd(tmp_string, 'BC3='))
+640
+641            if ruinfo == 1:
+642                corr_name.append(_get_corr_name(tmp_string, 'CORR_NAME='))
+643                corr_type.append(_get_kwd(tmp_string, 'DATATYPE='))
+644                corr_props.append(_get_kwd(tmp_string, 'PROP0='))
+645                if d0 == 0:
+646                    d0 = int(_get_kwd(tmp_string, 'D0='))
+647                else:
+648                    if d0 != int(_get_kwd(tmp_string, 'D0=')):
+649                        print('Error: Varying number of time values')
+650            if ruinfo == 2:
+651                prop_kappa.append(_get_kwd(tmp_string, 'KAPPA='))
+652            if ruinfo == 4:
+653                cnfg_no = int(_get_kwd(tmp_string, 'CNFG_ID='))
+654                if stop:
+655                    if cnfg_no > kwargs.get('stop'):
+656                        break
+657                idl.append(cnfg_no)
+658                print(f'\rReading configuration {cnfg_no}', end='\r')
+659                if len(idl) == 1:
+660                    no_corrs = len(corr_name)
+661                    data = []
+662                    for _ in range(no_corrs):
+663                        data.append([])
+664
+665                corr_no = 0
+666    bdio_close(fbdio)
+667
+668    print('\nCreator: ', creator)
+669    print('Ensemble: ', ensemble_name)
+670    print('Lattice volume: ', volume)
+671    print('Boundary conditions: ', boundary_conditions)
+672    print('Number of random sources: ', d0)
+673    print('Number of corrs: ', len(corr_name))
+674    print('Number of configurations: ', cnfg_no + 1)
+675
+676    corr_kappa = []  # Contains kappa values for both propagators of given correlation function
+677    for item in corr_props:
+678        corr_kappa.append(float(prop_kappa[int(item)]))
+679
+680    if stop is None:
+681        stop = idl[-1]
+682    idl_target = range(start, stop + 1, step)
+683    try:
+684        indices = [idl.index(i) for i in idl_target]
+685    except ValueError as err:
+686        raise Exception('Configurations in file do no match target list!', err) from err
+687
+688    result = {}
+689    for c in range(no_corrs):
+690        deltas = [np.asarray(data[c])[index] for index in indices]
+691        result[(corr_name[c], str(corr_kappa[c]))] = Obs([deltas], [ensemble_name], idl=[idl_target])
+692
+693    # Check that all data entries have the same number of configurations
+694    if len(set([o.N for o in list(result.values())])) != 1:
+695        raise Exception('Error: Not all correlators have the same number of configurations. bdio file is possibly corrupted.')
+696
+697    return result
 
diff --git a/docs/pyerrors/input/dobs.html b/docs/pyerrors/input/dobs.html index 7159843e..99bd99a2 100644 --- a/docs/pyerrors/input/dobs.html +++ b/docs/pyerrors/input/dobs.html @@ -94,927 +94,933 @@ -
  1from collections import defaultdict
-  2import gzip
-  3import lxml.etree as et
-  4import getpass
+                        
  1import datetime
+  2import getpass
+  3import gzip
+  4import json
   5import socket
-  6import datetime
-  7import json
-  8import warnings
-  9import numpy as np
- 10from ..obs import Obs
- 11from ..obs import _merge_idx
- 12from ..covobs import Covobs
- 13from .. import version as pyerrorsversion
- 14
+  6import warnings
+  7from collections import defaultdict
+  8
+  9import lxml.etree as et
+ 10import numpy as np
+ 11
+ 12from .. import version as pyerrorsversion
+ 13from ..covobs import Covobs
+ 14from ..obs import Obs, _merge_idx
  15
- 16# Based on https://stackoverflow.com/a/10076823
- 17def _etree_to_dict(t):
- 18    """ Convert the content of an XML file to a python dict"""
- 19    d = {t.tag: {} if t.attrib else None}
- 20    children = list(t)
- 21    if children:
- 22        dd = defaultdict(list)
- 23        for dc in map(_etree_to_dict, children):
- 24            for k, v in dc.items():
- 25                dd[k].append(v)
- 26        d = {t.tag: {k: v[0] if len(v) == 1 else v
- 27                     for k, v in dd.items()}}
- 28    if t.attrib:
- 29        d[t.tag].update(('@' + k, v)
- 30                        for k, v in t.attrib.items())
- 31    if t.text:
- 32        text = t.text.strip()
- 33        if children or t.attrib:
- 34            if text:
- 35                d[t.tag]['#data'] = [text]
- 36        else:
- 37            d[t.tag] = text
- 38    return d
- 39
+ 16
+ 17# Based on https://stackoverflow.com/a/10076823
+ 18def _etree_to_dict(t):
+ 19    """ Convert the content of an XML file to a python dict"""
+ 20    d = {t.tag: {} if t.attrib else None}
+ 21    children = list(t)
+ 22    if children:
+ 23        dd = defaultdict(list)
+ 24        for dc in map(_etree_to_dict, children):
+ 25            for k, v in dc.items():
+ 26                dd[k].append(v)
+ 27        d = {t.tag: {k: v[0] if len(v) == 1 else v
+ 28                     for k, v in dd.items()}}
+ 29    if t.attrib:
+ 30        d[t.tag].update(('@' + k, v)
+ 31                        for k, v in t.attrib.items())
+ 32    if t.text:
+ 33        text = t.text.strip()
+ 34        if children or t.attrib:
+ 35            if text:
+ 36                d[t.tag]['#data'] = [text]
+ 37        else:
+ 38            d[t.tag] = text
+ 39    return d
  40
- 41def _dict_to_xmlstring(d):
- 42    if isinstance(d, dict):
- 43        iters = ''
- 44        for k in d:
- 45            if k.startswith('#'):
- 46                for la in d[k]:
- 47                    iters += la
- 48                iters = '<array>\n' + iters + '<%sarray>\n' % ('/')
- 49                return iters
- 50            if isinstance(d[k], dict):
- 51                iters += '<%s>\n' % (k) + _dict_to_xmlstring(d[k]) + '<%s%s>\n' % ('/', k)
- 52            elif isinstance(d[k], str):
- 53                if len(d[k]) > 100:
- 54                    iters += '<%s>\n ' % (k) + d[k] + ' \n<%s%s>\n' % ('/', k)
- 55                else:
- 56                    iters += '<%s> ' % (k) + d[k] + ' <%s%s>\n' % ('/', k)
- 57            elif isinstance(d[k], list):
- 58                for i in range(len(d[k])):
- 59                    iters += _dict_to_xmlstring(d[k][i])
- 60            elif not d[k]:
- 61                return '\n'
- 62            else:
- 63                raise Exception('Type', type(d[k]), 'not supported in export!')
- 64    else:
- 65        raise Exception('Type', type(d), 'not supported in export!')
- 66    return iters
- 67
+ 41
+ 42def _dict_to_xmlstring(d):
+ 43    if isinstance(d, dict):
+ 44        iters = ''
+ 45        for k in d:
+ 46            if k.startswith('#'):
+ 47                for la in d[k]:
+ 48                    iters += la
+ 49                iters = '<array>\n' + iters + '<{}array>\n'.format('/')
+ 50                return iters
+ 51            if isinstance(d[k], dict):
+ 52                iters += f'<{k}>\n' + _dict_to_xmlstring(d[k]) + '<{}{}>\n'.format('/', k)
+ 53            elif isinstance(d[k], str):
+ 54                if len(d[k]) > 100:
+ 55                    iters += f'<{k}>\n ' + d[k] + ' \n<{}{}>\n'.format('/', k)
+ 56                else:
+ 57                    iters += f'<{k}> ' + d[k] + ' <{}{}>\n'.format('/', k)
+ 58            elif isinstance(d[k], list):
+ 59                for i in range(len(d[k])):
+ 60                    iters += _dict_to_xmlstring(d[k][i])
+ 61            elif not d[k]:
+ 62                return '\n'
+ 63            else:
+ 64                raise Exception('Type', type(d[k]), 'not supported in export!')
+ 65    else:
+ 66        raise Exception('Type', type(d), 'not supported in export!')
+ 67    return iters
  68
- 69def _dict_to_xmlstring_spaces(d, space='  '):
- 70    s = _dict_to_xmlstring(d)
- 71    o = ''
- 72    c = 0
- 73    cm = False
- 74    for li in s.split('\n'):
- 75        if li.startswith('<%s' % ('/')):
- 76            c -= 1
- 77            cm = True
- 78        for i in range(c):
- 79            o += space
- 80        o += li + '\n'
- 81        if li.startswith('<') and not cm:
- 82            if '<%s' % ('/') not in li:
- 83                c += 1
- 84        cm = False
- 85    return o
- 86
+ 69
+ 70def _dict_to_xmlstring_spaces(d, space='  '):
+ 71    s = _dict_to_xmlstring(d)
+ 72    o = ''
+ 73    c = 0
+ 74    cm = False
+ 75    for li in s.split('\n'):
+ 76        if li.startswith('<{}'.format('/')):
+ 77            c -= 1
+ 78            cm = True
+ 79        for _i in range(c):
+ 80            o += space
+ 81        o += li + '\n'
+ 82        if li.startswith('<') and not cm:
+ 83            if '<{}'.format('/') not in li:
+ 84                c += 1
+ 85        cm = False
+ 86    return o
  87
- 88def create_pobs_string(obsl, name, spec='', origin='', symbol=[], enstag=None):
- 89    """Export a list of Obs or structures containing Obs to an xml string
- 90    according to the Zeuthen pobs format.
- 91
- 92    Tags are not written or recovered automatically. The separator | is removed from the replica names.
- 93
- 94    Parameters
- 95    ----------
- 96    obsl : list
- 97        List of Obs that will be exported.
- 98        The Obs inside a structure have to be defined on the same ensemble.
- 99    name : str
-100        The name of the observable.
-101    spec : str
-102        Optional string that describes the contents of the file.
-103    origin : str
-104        Specify where the data has its origin.
-105    symbol : list
-106        A list of symbols that describe the observables to be written. May be empty.
-107    enstag : str
-108        Enstag that is written to pobs. If None, the ensemble name is used.
-109
-110    Returns
-111    -------
-112    xml_str : str
-113        XML formatted string of the input data
-114    """
-115
-116    od = {}
-117    ename = obsl[0].e_names[0]
-118    names = list(obsl[0].deltas.keys())
-119    nr = len(names)
-120    onames = [name.replace('|', '') for name in names]
-121    for o in obsl:
-122        if len(o.e_names) != 1:
-123            raise Exception('You try to export dobs to obs!')
-124        if o.e_names[0] != ename:
-125            raise Exception('You try to export dobs to obs!')
-126        if len(o.deltas.keys()) != nr:
-127            raise Exception('Incompatible obses in list')
-128    od['observables'] = {}
-129    od['observables']['schema'] = {'name': 'lattobs', 'version': '1.0'}
-130    od['observables']['origin'] = {
-131        'who': getpass.getuser(),
-132        'date': str(datetime.datetime.now())[:-7],
-133        'host': socket.gethostname(),
-134        'tool': {'name': 'pyerrors', 'version': pyerrorsversion.__version__}}
-135    od['observables']['pobs'] = {}
-136    pd = od['observables']['pobs']
-137    pd['spec'] = spec
-138    pd['origin'] = origin
-139    pd['name'] = name
-140    if enstag:
-141        if not isinstance(enstag, str):
-142            raise Exception('enstag has to be a string!')
-143        pd['enstag'] = enstag
-144    else:
-145        pd['enstag'] = ename
-146    pd['nr'] = '%d' % (nr)
-147    pd['array'] = []
-148    osymbol = 'cfg'
-149    if not isinstance(symbol, list):
-150        raise Exception('Symbol has to be a list!')
-151    if not (len(symbol) == 0 or len(symbol) == len(obsl)):
-152        raise Exception('Symbol has to be a list of lenght 0 or %d!' % (len(obsl)))
-153    for s in symbol:
-154        osymbol += ' %s' % s
-155    for r in range(nr):
-156        ad = {}
-157        ad['id'] = onames[r]
-158        Nconf = len(obsl[0].deltas[names[r]])
-159        layout = '%d i f%d' % (Nconf, len(obsl))
-160        ad['layout'] = layout
-161        ad['symbol'] = osymbol
-162        data = ''
-163        for c in range(Nconf):
-164            data += '%d ' % obsl[0].idl[names[r]][c]
-165            for o in obsl:
-166                num = o.deltas[names[r]][c] + o.r_values[names[r]]
-167                if num == 0:
-168                    data += '0 '
-169                else:
-170                    data += '%1.16e ' % (num)
-171            data += '\n'
-172        ad['#data'] = data
-173        pd['array'].append(ad)
-174
-175    rs = '<?xml version="1.0" encoding="utf-8"?>\n' + _dict_to_xmlstring_spaces(od)
-176    return rs
-177
+ 88
+ 89def create_pobs_string(obsl, name, spec='', origin='', symbol=None, enstag=None):
+ 90    """Export a list of Obs or structures containing Obs to an xml string
+ 91    according to the Zeuthen pobs format.
+ 92
+ 93    Tags are not written or recovered automatically. The separator | is removed from the replica names.
+ 94
+ 95    Parameters
+ 96    ----------
+ 97    obsl : list
+ 98        List of Obs that will be exported.
+ 99        The Obs inside a structure have to be defined on the same ensemble.
+100    name : str
+101        The name of the observable.
+102    spec : str
+103        Optional string that describes the contents of the file.
+104    origin : str
+105        Specify where the data has its origin.
+106    symbol : list
+107        A list of symbols that describe the observables to be written. May be empty.
+108    enstag : str
+109        Enstag that is written to pobs. If None, the ensemble name is used.
+110
+111    Returns
+112    -------
+113    xml_str : str
+114        XML formatted string of the input data
+115    """
+116
+117    if symbol is None:
+118        symbol = []
+119
+120    od = {}
+121    ename = obsl[0].e_names[0]
+122    names = list(obsl[0].deltas.keys())
+123    nr = len(names)
+124    onames = [name.replace('|', '') for name in names]
+125    for o in obsl:
+126        if len(o.e_names) != 1:
+127            raise Exception('You try to export dobs to obs!')
+128        if o.e_names[0] != ename:
+129            raise Exception('You try to export dobs to obs!')
+130        if len(o.deltas.keys()) != nr:
+131            raise Exception('Incompatible obses in list')
+132    od['observables'] = {}
+133    od['observables']['schema'] = {'name': 'lattobs', 'version': '1.0'}
+134    od['observables']['origin'] = {
+135        'who': getpass.getuser(),
+136        'date': str(datetime.datetime.now())[:-7],
+137        'host': socket.gethostname(),
+138        'tool': {'name': 'pyerrors', 'version': pyerrorsversion.__version__}}
+139    od['observables']['pobs'] = {}
+140    pd = od['observables']['pobs']
+141    pd['spec'] = spec
+142    pd['origin'] = origin
+143    pd['name'] = name
+144    if enstag:
+145        if not isinstance(enstag, str):
+146            raise Exception('enstag has to be a string!')
+147        pd['enstag'] = enstag
+148    else:
+149        pd['enstag'] = ename
+150    pd['nr'] = f'{nr}'
+151    pd['array'] = []
+152    osymbol = 'cfg'
+153    if not isinstance(symbol, list):
+154        raise Exception('Symbol has to be a list!')
+155    if not (len(symbol) == 0 or len(symbol) == len(obsl)):
+156        raise Exception(f'Symbol has to be a list of lenght 0 or {len(obsl)}!')
+157    for s in symbol:
+158        osymbol += f' {s}'
+159    for r in range(nr):
+160        ad = {}
+161        ad['id'] = onames[r]
+162        Nconf = len(obsl[0].deltas[names[r]])
+163        layout = f'{Nconf} i f{len(obsl)}'
+164        ad['layout'] = layout
+165        ad['symbol'] = osymbol
+166        data = ''
+167        for c in range(Nconf):
+168            data += f'{obsl[0].idl[names[r]][c]} '
+169            for o in obsl:
+170                num = o.deltas[names[r]][c] + o.r_values[names[r]]
+171                if num == 0:
+172                    data += '0 '
+173                else:
+174                    data += f'{num:1.16e} '
+175            data += '\n'
+176        ad['#data'] = data
+177        pd['array'].append(ad)
 178
-179def write_pobs(obsl, fname, name, spec='', origin='', symbol=[], enstag=None, gz=True):
-180    """Export a list of Obs or structures containing Obs to a .xml.gz file
-181    according to the Zeuthen pobs format.
+179    rs = '<?xml version="1.0" encoding="utf-8"?>\n' + _dict_to_xmlstring_spaces(od)
+180    return rs
+181
 182
-183    Tags are not written or recovered automatically. The separator | is removed from the replica names.
-184
-185    Parameters
-186    ----------
-187    obsl : list
-188        List of Obs that will be exported.
-189        The Obs inside a structure have to be defined on the same ensemble.
-190    fname : str
-191        Filename of the output file.
-192    name : str
-193        The name of the observable.
-194    spec : str
-195        Optional string that describes the contents of the file.
-196    origin : str
-197        Specify where the data has its origin.
-198    symbol : list
-199        A list of symbols that describe the observables to be written. May be empty.
-200    enstag : str
-201        Enstag that is written to pobs. If None, the ensemble name is used.
-202    gz : bool
-203        If True, the output is a gzipped xml. If False, the output is an xml file.
-204
-205    Returns
-206    -------
-207    None
-208    """
-209    pobsstring = create_pobs_string(obsl, name, spec, origin, symbol, enstag)
-210
-211    if not fname.endswith('.xml') and not fname.endswith('.gz'):
-212        fname += '.xml'
-213
-214    if gz:
-215        if not fname.endswith('.gz'):
-216            fname += '.gz'
+183def write_pobs(obsl, fname, name, spec='', origin='', symbol=None, enstag=None, gz=True):
+184    """Export a list of Obs or structures containing Obs to a .xml.gz file
+185    according to the Zeuthen pobs format.
+186
+187    Tags are not written or recovered automatically. The separator | is removed from the replica names.
+188
+189    Parameters
+190    ----------
+191    obsl : list
+192        List of Obs that will be exported.
+193        The Obs inside a structure have to be defined on the same ensemble.
+194    fname : str
+195        Filename of the output file.
+196    name : str
+197        The name of the observable.
+198    spec : str
+199        Optional string that describes the contents of the file.
+200    origin : str
+201        Specify where the data has its origin.
+202    symbol : list
+203        A list of symbols that describe the observables to be written. May be empty.
+204    enstag : str
+205        Enstag that is written to pobs. If None, the ensemble name is used.
+206    gz : bool
+207        If True, the output is a gzipped xml. If False, the output is an xml file.
+208
+209    Returns
+210    -------
+211    None
+212    """
+213    pobsstring = create_pobs_string(obsl, name, spec, origin, symbol, enstag)
+214
+215    if not fname.endswith('.xml') and not fname.endswith('.gz'):
+216        fname += '.xml'
 217
-218        fp = gzip.open(fname, 'wb')
-219        fp.write(pobsstring.encode('utf-8'))
-220    else:
-221        fp = open(fname, 'w', encoding='utf-8')
-222        fp.write(pobsstring)
-223    fp.close()
-224
-225
-226def _import_data(string):
-227    return json.loads("[" + ",".join(string.replace(' +', ' ').split()) + "]")
+218    if gz:
+219        if not fname.endswith('.gz'):
+220            fname += '.gz'
+221
+222        fp = gzip.open(fname, 'wb')
+223        fp.write(pobsstring.encode('utf-8'))
+224    else:
+225        fp = open(fname, 'w', encoding='utf-8')
+226        fp.write(pobsstring)
+227    fp.close()
 228
 229
-230def _check(condition):
-231    if not condition:
-232        raise Exception("XML file format not supported")
+230def _import_data(string):
+231    return json.loads("[" + ",".join(string.replace(' +', ' ').split()) + "]")
+232
 233
-234
-235class _NoTagInDataError(Exception):
-236    """Raised when tag is not in data"""
-237    def __init__(self, tag):
-238        self.tag = tag
-239        super().__init__('Tag %s not in data!' % (self.tag))
-240
-241
-242def _find_tag(dat, tag):
-243    for i in range(len(dat)):
-244        if dat[i].tag == tag:
-245            return i
-246    raise _NoTagInDataError(tag)
-247
-248
-249def _import_array(arr):
-250    name = arr[_find_tag(arr, 'id')].text.strip()
-251    index = _find_tag(arr, 'layout')
-252    try:
-253        sindex = _find_tag(arr, 'symbol')
-254    except _NoTagInDataError:
-255        sindex = 0
-256    if sindex > index:
-257        tmp = _import_data(arr[sindex].tail)
-258    else:
-259        tmp = _import_data(arr[index].tail)
-260
-261    li = arr[index].text.strip()
-262    m = li.split()
-263    if m[1] == "i" and m[2][0] == "f":
-264        nc = int(m[0])
-265        na = int(m[2].lstrip('f'))
-266        _dat = []
-267        mask = []
-268        for a in range(na):
-269            mask += [a]
-270            _dat += [np.array(tmp[1 + a:: na + 1])]
-271        _check(len(tmp[0:: na + 1]) == nc)
-272        return [name, tmp[0:: na + 1], mask, _dat]
-273    elif m[1][0] == 'f' and len(m) < 3:
-274        sh = (int(m[0]), int(m[1].lstrip('f')))
-275        return np.reshape(tmp, sh)
-276    elif any(['f' in s for s in m]):
-277        for si in range(len(m)):
-278            if m[si] == 'f':
-279                break
-280        sh = [int(m[i]) for i in range(si)]
-281        return np.reshape(tmp, sh)
-282    else:
-283        print(name, m)
-284        _check(False)
-285
-286
-287def _import_rdata(rd):
-288    name, idx, mask, deltas = _import_array(rd)
-289    return deltas, name, idx
+234def _check(condition):
+235    if not condition:
+236        raise Exception("XML file format not supported")
+237
+238
+239class _NoTagInDataError(Exception):
+240    """Raised when tag is not in data"""
+241    def __init__(self, tag):
+242        self.tag = tag
+243        super().__init__(f'Tag {self.tag} not in data!')
+244
+245
+246def _find_tag(dat, tag):
+247    for i in range(len(dat)):
+248        if dat[i].tag == tag:
+249            return i
+250    raise _NoTagInDataError(tag)
+251
+252
+253def _import_array(arr):
+254    name = arr[_find_tag(arr, 'id')].text.strip()
+255    index = _find_tag(arr, 'layout')
+256    try:
+257        sindex = _find_tag(arr, 'symbol')
+258    except _NoTagInDataError:
+259        sindex = 0
+260    if sindex > index:
+261        tmp = _import_data(arr[sindex].tail)
+262    else:
+263        tmp = _import_data(arr[index].tail)
+264
+265    li = arr[index].text.strip()
+266    m = li.split()
+267    if m[1] == "i" and m[2][0] == "f":
+268        nc = int(m[0])
+269        na = int(m[2].lstrip('f'))
+270        _dat = []
+271        mask = []
+272        for a in range(na):
+273            mask += [a]
+274            _dat += [np.array(tmp[1 + a:: na + 1])]
+275        _check(len(tmp[0:: na + 1]) == nc)
+276        return [name, tmp[0:: na + 1], mask, _dat]
+277    elif m[1][0] == 'f' and len(m) < 3:
+278        sh = (int(m[0]), int(m[1].lstrip('f')))
+279        return np.reshape(tmp, sh)
+280    elif any(['f' in s for s in m]):
+281        for si in range(len(m)):
+282            if m[si] == 'f':
+283                break
+284        sh = [int(m[i]) for i in range(si)]
+285        return np.reshape(tmp, sh)
+286    else:
+287        print(name, m)
+288        _check(False)
+289
 290
-291
-292def _import_cdata(cd):
-293    _check(cd[0].tag == "id")
-294    _check(cd[1][0].text.strip() == "cov")
-295    cov = _import_array(cd[1])
-296    grad = _import_array(cd[2])
-297    return cd[0].text.strip(), cov, grad
-298
-299
-300def read_pobs(fname, full_output=False, gz=True, separator_insertion=None):
-301    """Import a list of Obs from an xml.gz file in the Zeuthen pobs format.
+291def _import_rdata(rd):
+292    name, idx, _mask, deltas = _import_array(rd)
+293    return deltas, name, idx
+294
+295
+296def _import_cdata(cd):
+297    _check(cd[0].tag == "id")
+298    _check(cd[1][0].text.strip() == "cov")
+299    cov = _import_array(cd[1])
+300    grad = _import_array(cd[2])
+301    return cd[0].text.strip(), cov, grad
 302
-303    Tags are not written or recovered automatically.
-304
-305    Parameters
-306    ----------
-307    fname : str
-308        Filename of the input file.
-309    full_output : bool
-310        If True, a dict containing auxiliary information and the data is returned.
-311        If False, only the data is returned as list.
-312    separatior_insertion: str or int
-313        str: replace all occurences of "separator_insertion" within the replica names
-314        by "|%s" % (separator_insertion) when constructing the names of the replica.
-315        int: Insert the separator "|" at the position given by separator_insertion.
-316        None (default): Replica names remain unchanged.
-317
-318    Returns
-319    -------
-320    res : list[Obs]
-321        Imported data
-322    or
-323    res : dict
-324        Imported data and meta-data
-325    """
-326
-327    if not fname.endswith('.xml') and not fname.endswith('.gz'):
-328        fname += '.xml'
-329    if gz:
-330        if not fname.endswith('.gz'):
-331            fname += '.gz'
-332        with gzip.open(fname, 'r') as fin:
-333            content = fin.read()
-334    else:
-335        if fname.endswith('.gz'):
-336            warnings.warn("Trying to read from %s without unzipping!" % fname, UserWarning)
-337        with open(fname, 'r') as fin:
-338            content = fin.read()
-339
-340    # parse xml file content
-341    root = et.fromstring(content)
-342
-343    _check(root[2].tag == 'pobs')
-344    pobs = root[2]
-345
-346    version = root[0][1].text.strip()
-347
-348    _check(root[1].tag == 'origin')
-349    file_origin = _etree_to_dict(root[1])['origin']
-350
-351    deltas = []
-352    names = []
-353    idl = []
-354    for i in range(5, len(pobs)):
-355        delta, name, idx = _import_rdata(pobs[i])
-356        deltas.append(delta)
-357        if separator_insertion is None:
-358            pass
-359        elif isinstance(separator_insertion, int):
-360            name = name[:separator_insertion] + '|' + name[separator_insertion:]
-361        elif isinstance(separator_insertion, str):
-362            name = name.replace(separator_insertion, "|%s" % (separator_insertion))
-363        else:
-364            raise Exception("separator_insertion has to be string or int, is ", type(separator_insertion))
-365        names.append(name)
-366        idl.append(idx)
-367    res = [Obs([d[i] for d in deltas], names, idl=idl) for i in range(len(deltas[0]))]
-368
-369    descriptiond = {}
-370    for i in range(4):
-371        descriptiond[pobs[i].tag] = pobs[i].text.strip()
+303
+304def read_pobs(fname, full_output=False, gz=True, separator_insertion=None):
+305    """Import a list of Obs from an xml.gz file in the Zeuthen pobs format.
+306
+307    Tags are not written or recovered automatically.
+308
+309    Parameters
+310    ----------
+311    fname : str
+312        Filename of the input file.
+313    full_output : bool
+314        If True, a dict containing auxiliary information and the data is returned.
+315        If False, only the data is returned as list.
+316    separatior_insertion: str or int
+317        str: replace all occurences of "separator_insertion" within the replica names
+318        by "|%s" % (separator_insertion) when constructing the names of the replica.
+319        int: Insert the separator "|" at the position given by separator_insertion.
+320        None (default): Replica names remain unchanged.
+321
+322    Returns
+323    -------
+324    res : list[Obs]
+325        Imported data
+326    or
+327    res : dict
+328        Imported data and meta-data
+329    """
+330
+331    if not fname.endswith('.xml') and not fname.endswith('.gz'):
+332        fname += '.xml'
+333    if gz:
+334        if not fname.endswith('.gz'):
+335            fname += '.gz'
+336        with gzip.open(fname, 'r') as fin:
+337            content = fin.read()
+338    else:
+339        if fname.endswith('.gz'):
+340            warnings.warn(f"Trying to read from {fname} without unzipping!", UserWarning, stacklevel=2)
+341        with open(fname) as fin:
+342            content = fin.read()
+343
+344    # parse xml file content
+345    root = et.fromstring(content)
+346
+347    _check(root[2].tag == 'pobs')
+348    pobs = root[2]
+349
+350    version = root[0][1].text.strip()
+351
+352    _check(root[1].tag == 'origin')
+353    file_origin = _etree_to_dict(root[1])['origin']
+354
+355    deltas = []
+356    names = []
+357    idl = []
+358    for i in range(5, len(pobs)):
+359        delta, name, idx = _import_rdata(pobs[i])
+360        deltas.append(delta)
+361        if separator_insertion is None:
+362            pass
+363        elif isinstance(separator_insertion, int):
+364            name = name[:separator_insertion] + '|' + name[separator_insertion:]
+365        elif isinstance(separator_insertion, str):
+366            name = name.replace(separator_insertion, f"|{separator_insertion}")
+367        else:
+368            raise Exception("separator_insertion has to be string or int, is ", type(separator_insertion))
+369        names.append(name)
+370        idl.append(idx)
+371    res = [Obs([d[i] for d in deltas], names, idl=idl) for i in range(len(deltas[0]))]
 372
-373    _check(pobs[4].tag == "nr")
-374
-375    _check(pobs[5].tag == 'array')
-376    if pobs[5][1].tag == 'symbol':
-377        symbol = pobs[5][1].text.strip()
-378        descriptiond['symbol'] = symbol
-379
-380    if full_output:
-381        retd = {}
-382        tool = file_origin.get('tool', None)
-383        if tool:
-384            program = tool['name'] + ' ' + tool['version']
-385        else:
-386            program = ''
-387        retd['program'] = program
-388        retd['version'] = version
-389        retd['who'] = file_origin['who']
-390        retd['date'] = file_origin['date']
-391        retd['host'] = file_origin['host']
-392        retd['description'] = descriptiond
-393        retd['obsdata'] = res
-394        return retd
-395    else:
-396        return res
-397
-398
-399# this is based on Mattia Bruno's implementation at https://github.com/mbruno46/pyobs/blob/master/pyobs/IO/xml.py
-400def import_dobs_string(content, full_output=False, separator_insertion=True):
-401    """Import a list of Obs from a string in the Zeuthen dobs format.
+373    descriptiond = {}
+374    for i in range(4):
+375        descriptiond[pobs[i].tag] = pobs[i].text.strip()
+376
+377    _check(pobs[4].tag == "nr")
+378
+379    _check(pobs[5].tag == 'array')
+380    if pobs[5][1].tag == 'symbol':
+381        symbol = pobs[5][1].text.strip()
+382        descriptiond['symbol'] = symbol
+383
+384    if full_output:
+385        retd = {}
+386        tool = file_origin.get('tool', None)
+387        if tool:
+388            program = tool['name'] + ' ' + tool['version']
+389        else:
+390            program = ''
+391        retd['program'] = program
+392        retd['version'] = version
+393        retd['who'] = file_origin['who']
+394        retd['date'] = file_origin['date']
+395        retd['host'] = file_origin['host']
+396        retd['description'] = descriptiond
+397        retd['obsdata'] = res
+398        return retd
+399    else:
+400        return res
+401
 402
-403    Tags are not written or recovered automatically.
-404
-405    Parameters
-406    ----------
-407    content : str
-408        XML string containing the data
-409    full_output : bool
-410        If True, a dict containing auxiliary information and the data is returned.
-411        If False, only the data is returned as list.
-412    separatior_insertion: str, int or bool
-413        str: replace all occurences of "separator_insertion" within the replica names
-414        by "|%s" % (separator_insertion) when constructing the names of the replica.
-415        int: Insert the separator "|" at the position given by separator_insertion.
-416        True (default): separator "|" is inserted after len(ensname), assuming that the
-417        ensemble name is a prefix to the replica name.
-418        None or False: No separator is inserted.
-419
-420    Returns
-421    -------
-422    res : list[Obs]
-423        Imported data
-424    or
-425    res : dict
-426        Imported data and meta-data
-427    """
-428
-429    root = et.fromstring(content)
-430
-431    _check(root.tag == 'OBSERVABLES')
-432    _check(root[0].tag == 'SCHEMA')
-433    version = root[0][1].text.strip()
+403# this is based on Mattia Bruno's implementation at https://github.com/mbruno46/pyobs/blob/master/pyobs/IO/xml.py
+404def import_dobs_string(content, full_output=False, separator_insertion=True):
+405    """Import a list of Obs from a string in the Zeuthen dobs format.
+406
+407    Tags are not written or recovered automatically.
+408
+409    Parameters
+410    ----------
+411    content : str
+412        XML string containing the data
+413    full_output : bool
+414        If True, a dict containing auxiliary information and the data is returned.
+415        If False, only the data is returned as list.
+416    separatior_insertion: str, int or bool
+417        str: replace all occurences of "separator_insertion" within the replica names
+418        by "|%s" % (separator_insertion) when constructing the names of the replica.
+419        int: Insert the separator "|" at the position given by separator_insertion.
+420        True (default): separator "|" is inserted after len(ensname), assuming that the
+421        ensemble name is a prefix to the replica name.
+422        None or False: No separator is inserted.
+423
+424    Returns
+425    -------
+426    res : list[Obs]
+427        Imported data
+428    or
+429    res : dict
+430        Imported data and meta-data
+431    """
+432
+433    root = et.fromstring(content)
 434
-435    _check(root[1].tag == 'origin')
-436    file_origin = _etree_to_dict(root[1])['origin']
-437
-438    _check(root[2].tag == 'dobs')
-439
-440    dobs = root[2]
+435    _check(root.tag == 'OBSERVABLES')
+436    _check(root[0].tag == 'SCHEMA')
+437    version = root[0][1].text.strip()
+438
+439    _check(root[1].tag == 'origin')
+440    file_origin = _etree_to_dict(root[1])['origin']
 441
-442    descriptiond = {}
-443    for i in range(3):
-444        descriptiond[dobs[i].tag] = dobs[i].text.strip()
+442    _check(root[2].tag == 'dobs')
+443
+444    dobs = root[2]
 445
-446    _check(dobs[3].tag == 'array')
-447
-448    symbol = []
-449    if dobs[3][1].tag == 'symbol':
-450        symbol = dobs[3][1].text.strip()
-451        descriptiond['symbol'] = symbol
-452    mean = _import_array(dobs[3])[0]
-453
-454    _check(dobs[4].tag == "ne")
-455    ne = int(dobs[4].text.strip())
-456    _check(dobs[5].tag == "nc")
+446    descriptiond = {}
+447    for i in range(3):
+448        descriptiond[dobs[i].tag] = dobs[i].text.strip()
+449
+450    _check(dobs[3].tag == 'array')
+451
+452    symbol = []
+453    if dobs[3][1].tag == 'symbol':
+454        symbol = dobs[3][1].text.strip()
+455        descriptiond['symbol'] = symbol
+456    mean = _import_array(dobs[3])[0]
 457
-458    idld = {}
-459    deltad = {}
-460    covd = {}
-461    gradd = {}
-462    names = []
-463    e_names = []
-464    enstags = {}
-465    for k in range(6, len(list(dobs))):
-466        if dobs[k].tag == "edata":
-467            _check(dobs[k][0].tag == "enstag")
-468            ename = dobs[k][0].text.strip()
-469            e_names.append(ename)
-470            _check(dobs[k][1].tag == "nr")
-471            R = int(dobs[k][1].text.strip())
-472            for i in range(2, 2 + R):
-473                deltas, rname, idx = _import_rdata(dobs[k][i])
-474                if separator_insertion is None or False:
-475                    pass
-476                elif separator_insertion is True:
-477                    if rname.startswith(ename):
-478                        rname = rname[:len(ename)] + '|' + rname[len(ename):]
-479                elif isinstance(separator_insertion, int):
-480                    rname = rname[:separator_insertion] + '|' + rname[separator_insertion:]
-481                elif isinstance(separator_insertion, str):
-482                    rname = rname.replace(separator_insertion, "|%s" % (separator_insertion))
-483                else:
-484                    raise Exception("separator_insertion has to be string or int, is ", type(separator_insertion))
-485                if '|' in rname:
-486                    new_ename = rname[:rname.index('|')]
+458    _check(dobs[4].tag == "ne")
+459    ne = int(dobs[4].text.strip())
+460    _check(dobs[5].tag == "nc")
+461
+462    idld = {}
+463    deltad = {}
+464    covd = {}
+465    gradd = {}
+466    names = []
+467    e_names = []
+468    enstags = {}
+469    for k in range(6, len(list(dobs))):
+470        if dobs[k].tag == "edata":
+471            _check(dobs[k][0].tag == "enstag")
+472            ename = dobs[k][0].text.strip()
+473            e_names.append(ename)
+474            _check(dobs[k][1].tag == "nr")
+475            R = int(dobs[k][1].text.strip())
+476            for i in range(2, 2 + R):
+477                deltas, rname, idx = _import_rdata(dobs[k][i])
+478                if separator_insertion is None or False:
+479                    pass
+480                elif separator_insertion is True:
+481                    if rname.startswith(ename):
+482                        rname = rname[:len(ename)] + '|' + rname[len(ename):]
+483                elif isinstance(separator_insertion, int):
+484                    rname = rname[:separator_insertion] + '|' + rname[separator_insertion:]
+485                elif isinstance(separator_insertion, str):
+486                    rname = rname.replace(separator_insertion, f"|{separator_insertion}")
 487                else:
-488                    new_ename = ename
-489                enstags[new_ename] = ename
-490                idld[rname] = idx
-491                deltad[rname] = deltas
-492                names.append(rname)
-493        elif dobs[k].tag == "cdata":
-494            cname, cov, grad = _import_cdata(dobs[k])
-495            covd[cname] = cov
-496            if grad.shape[1] == 1:
-497                gradd[cname] = [grad for i in range(len(mean))]
-498            else:
-499                gradd[cname] = grad.T
-500        else:
-501            _check(False)
-502    names = list(set(names))
-503
-504    for name in names:
-505        for i in range(len(deltad[name])):
-506            tmp = np.zeros_like(deltad[name][i])
-507            for j in range(len(deltad[name][i])):
-508                if deltad[name][i][j] != 0.:
-509                    tmp[j] = deltad[name][i][j] + mean[i]
-510            deltad[name][i] = tmp
-511
-512    res = []
-513    for i in range(len(mean)):
-514        deltas = []
-515        idl = []
-516        obs_names = []
-517        for name in names:
-518            h = np.unique(deltad[name][i])
-519            if len(h) == 1 and np.all(h == mean[i]):
-520                continue
-521            repdeltas = []
-522            repidl = []
-523            for j in range(len(deltad[name][i])):
-524                if deltad[name][i][j] != 0.:
-525                    repdeltas.append(deltad[name][i][j])
-526                    repidl.append(idld[name][j])
-527            if len(repdeltas) > 0:
-528                obs_names.append(name)
-529                deltas.append(repdeltas)
-530                idl.append(repidl)
-531
-532        obsmeans = [np.average(deltas[j]) for j in range(len(deltas))]
-533        res.append(Obs([np.array(deltas[j]) - obsmeans[j] for j in range(len(obsmeans))], obs_names, idl=idl, means=obsmeans))
-534        res[-1]._value = mean[i]
-535    _check(len(e_names) == ne)
-536
-537    cnames = list(covd.keys())
-538    for i in range(len(res)):
-539        new_covobs = {name: Covobs(0, covd[name], name, grad=gradd[name][i]) for name in cnames}
-540        for name in cnames:
-541            if np.all(new_covobs[name].grad == 0):
-542                del new_covobs[name]
-543        cnames_loc = list(new_covobs.keys())
-544        for name in cnames_loc:
-545            res[i].names.append(name)
-546            res[i].shape[name] = 1
-547            res[i].idl[name] = []
-548        res[i]._covobs = new_covobs
-549
-550    if symbol:
-551        for i in range(len(res)):
-552            res[i].tag = symbol[i]
-553            if res[i].tag == 'None':
-554                res[i].tag = None
-555    if full_output:
-556        retd = {}
-557        tool = file_origin.get('tool', None)
-558        if tool:
-559            program = tool['name'] + ' ' + tool['version']
-560        else:
-561            program = ''
-562        retd['program'] = program
-563        retd['version'] = version
-564        retd['who'] = file_origin['who']
-565        retd['date'] = file_origin['date']
-566        retd['host'] = file_origin['host']
-567        retd['description'] = descriptiond
-568        retd['enstags'] = enstags
-569        retd['obsdata'] = res
-570        return retd
-571    else:
-572        return res
-573
-574
-575def read_dobs(fname, full_output=False, gz=True, separator_insertion=True):
-576    """Import a list of Obs from an xml.gz file in the Zeuthen dobs format.
+488                    raise Exception("separator_insertion has to be string or int, is ", type(separator_insertion))
+489                if '|' in rname:
+490                    new_ename = rname[:rname.index('|')]
+491                else:
+492                    new_ename = ename
+493                enstags[new_ename] = ename
+494                idld[rname] = idx
+495                deltad[rname] = deltas
+496                names.append(rname)
+497        elif dobs[k].tag == "cdata":
+498            cname, cov, grad = _import_cdata(dobs[k])
+499            covd[cname] = cov
+500            if grad.shape[1] == 1:
+501                gradd[cname] = [grad for i in range(len(mean))]
+502            else:
+503                gradd[cname] = grad.T
+504        else:
+505            _check(False)
+506    names = list(set(names))
+507
+508    for name in names:
+509        for i in range(len(deltad[name])):
+510            tmp = np.zeros_like(deltad[name][i])
+511            for j in range(len(deltad[name][i])):
+512                if deltad[name][i][j] != 0.:
+513                    tmp[j] = deltad[name][i][j] + mean[i]
+514            deltad[name][i] = tmp
+515
+516    res = []
+517    for i in range(len(mean)):
+518        deltas = []
+519        idl = []
+520        obs_names = []
+521        for name in names:
+522            h = np.unique(deltad[name][i])
+523            if len(h) == 1 and np.all(h == mean[i]):
+524                continue
+525            repdeltas = []
+526            repidl = []
+527            for j in range(len(deltad[name][i])):
+528                if deltad[name][i][j] != 0.:
+529                    repdeltas.append(deltad[name][i][j])
+530                    repidl.append(idld[name][j])
+531            if len(repdeltas) > 0:
+532                obs_names.append(name)
+533                deltas.append(repdeltas)
+534                idl.append(repidl)
+535
+536        obsmeans = [np.average(deltas[j]) for j in range(len(deltas))]
+537        res.append(Obs([np.array(deltas[j]) - obsmeans[j] for j in range(len(obsmeans))], obs_names, idl=idl, means=obsmeans))
+538        res[-1]._value = mean[i]
+539    _check(len(e_names) == ne)
+540
+541    cnames = list(covd.keys())
+542    for i in range(len(res)):
+543        new_covobs = {name: Covobs(0, covd[name], name, grad=gradd[name][i]) for name in cnames}
+544        for name in cnames:
+545            if np.all(new_covobs[name].grad == 0):
+546                del new_covobs[name]
+547        cnames_loc = list(new_covobs.keys())
+548        for name in cnames_loc:
+549            res[i].names.append(name)
+550            res[i].shape[name] = 1
+551            res[i].idl[name] = []
+552        res[i]._covobs = new_covobs
+553
+554    if symbol:
+555        for i in range(len(res)):
+556            res[i].tag = symbol[i]
+557            if res[i].tag == 'None':
+558                res[i].tag = None
+559    if full_output:
+560        retd = {}
+561        tool = file_origin.get('tool', None)
+562        if tool:
+563            program = tool['name'] + ' ' + tool['version']
+564        else:
+565            program = ''
+566        retd['program'] = program
+567        retd['version'] = version
+568        retd['who'] = file_origin['who']
+569        retd['date'] = file_origin['date']
+570        retd['host'] = file_origin['host']
+571        retd['description'] = descriptiond
+572        retd['enstags'] = enstags
+573        retd['obsdata'] = res
+574        return retd
+575    else:
+576        return res
 577
-578    Tags are not written or recovered automatically.
-579
-580    Parameters
-581    ----------
-582    fname : str
-583        Filename of the input file.
-584    full_output : bool
-585        If True, a dict containing auxiliary information and the data is returned.
-586        If False, only the data is returned as list.
-587    gz : bool
-588        If True, assumes that data is gzipped. If False, assumes XML file.
-589    separatior_insertion: str, int or bool
-590        str: replace all occurences of "separator_insertion" within the replica names
-591        by "|%s" % (separator_insertion) when constructing the names of the replica.
-592        int: Insert the separator "|" at the position given by separator_insertion.
-593        True (default): separator "|" is inserted after len(ensname), assuming that the
-594        ensemble name is a prefix to the replica name.
-595        None or False: No separator is inserted.
-596
-597    Returns
-598    -------
-599    res : list[Obs]
-600        Imported data
-601    or
-602    res : dict
-603        Imported data and meta-data
-604    """
-605
-606    if not fname.endswith('.xml') and not fname.endswith('.gz'):
-607        fname += '.xml'
-608    if gz:
-609        if not fname.endswith('.gz'):
-610            fname += '.gz'
-611        with gzip.open(fname, 'r') as fin:
-612            content = fin.read()
-613    else:
-614        if fname.endswith('.gz'):
-615            warnings.warn("Trying to read from %s without unzipping!" % fname, UserWarning)
-616        with open(fname, 'r') as fin:
-617            content = fin.read()
-618
-619    return import_dobs_string(content, full_output, separator_insertion=separator_insertion)
-620
-621
-622def _dobsdict_to_xmlstring(d):
-623    if isinstance(d, dict):
-624        iters = ''
-625        for k in d:
-626            if k.startswith('#value'):
-627                for li in d[k]:
-628                    iters += li
-629                return iters + '\n'
-630            elif k.startswith('#'):
+578
+579def read_dobs(fname, full_output=False, gz=True, separator_insertion=True):
+580    """Import a list of Obs from an xml.gz file in the Zeuthen dobs format.
+581
+582    Tags are not written or recovered automatically.
+583
+584    Parameters
+585    ----------
+586    fname : str
+587        Filename of the input file.
+588    full_output : bool
+589        If True, a dict containing auxiliary information and the data is returned.
+590        If False, only the data is returned as list.
+591    gz : bool
+592        If True, assumes that data is gzipped. If False, assumes XML file.
+593    separatior_insertion: str, int or bool
+594        str: replace all occurences of "separator_insertion" within the replica names
+595        by "|%s" % (separator_insertion) when constructing the names of the replica.
+596        int: Insert the separator "|" at the position given by separator_insertion.
+597        True (default): separator "|" is inserted after len(ensname), assuming that the
+598        ensemble name is a prefix to the replica name.
+599        None or False: No separator is inserted.
+600
+601    Returns
+602    -------
+603    res : list[Obs]
+604        Imported data
+605    or
+606    res : dict
+607        Imported data and meta-data
+608    """
+609
+610    if not fname.endswith('.xml') and not fname.endswith('.gz'):
+611        fname += '.xml'
+612    if gz:
+613        if not fname.endswith('.gz'):
+614            fname += '.gz'
+615        with gzip.open(fname, 'r') as fin:
+616            content = fin.read()
+617    else:
+618        if fname.endswith('.gz'):
+619            warnings.warn(f"Trying to read from {fname} without unzipping!", UserWarning, stacklevel=2)
+620        with open(fname) as fin:
+621            content = fin.read()
+622
+623    return import_dobs_string(content, full_output, separator_insertion=separator_insertion)
+624
+625
+626def _dobsdict_to_xmlstring(d):
+627    if isinstance(d, dict):
+628        iters = ''
+629        for k in d:
+630            if k.startswith('#value'):
 631                for li in d[k]:
 632                    iters += li
-633                iters = '<array>\n' + iters + '<%sarray>\n' % ('/')
-634                return iters
-635            if isinstance(d[k], dict):
-636                iters += '<%s>\n' % (k) + _dobsdict_to_xmlstring(d[k]) + '<%s%s>\n' % ('/', k)
-637            elif isinstance(d[k], str):
-638                if len(d[k]) > 100:
-639                    iters += '<%s>\n ' % (k) + d[k] + ' \n<%s%s>\n' % ('/', k)
-640                else:
-641                    iters += '<%s> ' % (k) + d[k] + ' <%s%s>\n' % ('/', k)
-642            elif isinstance(d[k], list):
-643                tmps = ''
-644                if k in ['edata', 'cdata']:
-645                    for i in range(len(d[k])):
-646                        tmps += '<%s>\n' % (k) + _dobsdict_to_xmlstring(d[k][i]) + '</%s>\n' % (k)
-647                else:
-648                    for i in range(len(d[k])):
-649                        tmps += _dobsdict_to_xmlstring(d[k][i])
-650                iters += tmps
-651            elif isinstance(d[k], (int, float)):
-652                iters += '<%s> ' % (k) + str(d[k]) + ' <%s%s>\n' % ('/', k)
-653            elif not d[k]:
-654                return '\n'
-655            else:
-656                raise Exception('Type', type(d[k]), 'not supported in export!')
-657    else:
-658        raise Exception('Type', type(d), 'not supported in export!')
-659    return iters
-660
-661
-662def _dobsdict_to_xmlstring_spaces(d, space='  '):
-663    s = _dobsdict_to_xmlstring(d)
-664    o = ''
-665    c = 0
-666    cm = False
-667    for li in s.split('\n'):
-668        if li.startswith('<%s' % ('/')):
-669            c -= 1
-670            cm = True
-671        for i in range(c):
-672            o += space
-673        o += li + '\n'
-674        if li.startswith('<') and not cm:
-675            if '<%s' % ('/') not in li:
-676                c += 1
-677        cm = False
-678    return o
-679
-680
-681def create_dobs_string(obsl, name, spec='dobs v1.0', origin='', symbol=[], who=None, enstags=None):
-682    """Generate the string for the export of a list of Obs or structures containing Obs
-683    to a .xml.gz file according to the Zeuthen dobs format.
+633                return iters + '\n'
+634            elif k.startswith('#'):
+635                for li in d[k]:
+636                    iters += li
+637                iters = '<array>\n' + iters + '<{}array>\n'.format('/')
+638                return iters
+639            if isinstance(d[k], dict):
+640                iters += f'<{k}>\n' + _dobsdict_to_xmlstring(d[k]) + '<{}{}>\n'.format('/', k)
+641            elif isinstance(d[k], str):
+642                if len(d[k]) > 100:
+643                    iters += f'<{k}>\n ' + d[k] + ' \n<{}{}>\n'.format('/', k)
+644                else:
+645                    iters += f'<{k}> ' + d[k] + ' <{}{}>\n'.format('/', k)
+646            elif isinstance(d[k], list):
+647                tmps = ''
+648                if k in ['edata', 'cdata']:
+649                    for i in range(len(d[k])):
+650                        tmps += f'<{k}>\n' + _dobsdict_to_xmlstring(d[k][i]) + f'</{k}>\n'
+651                else:
+652                    for i in range(len(d[k])):
+653                        tmps += _dobsdict_to_xmlstring(d[k][i])
+654                iters += tmps
+655            elif isinstance(d[k], (int, float)):
+656                iters += f'<{k}> ' + str(d[k]) + ' <{}{}>\n'.format('/', k)
+657            elif not d[k]:
+658                return '\n'
+659            else:
+660                raise Exception('Type', type(d[k]), 'not supported in export!')
+661    else:
+662        raise Exception('Type', type(d), 'not supported in export!')
+663    return iters
+664
+665
+666def _dobsdict_to_xmlstring_spaces(d, space='  '):
+667    s = _dobsdict_to_xmlstring(d)
+668    o = ''
+669    c = 0
+670    cm = False
+671    for li in s.split('\n'):
+672        if li.startswith('<{}'.format('/')):
+673            c -= 1
+674            cm = True
+675        for _i in range(c):
+676            o += space
+677        o += li + '\n'
+678        if li.startswith('<') and not cm:
+679            if '<{}'.format('/') not in li:
+680                c += 1
+681        cm = False
+682    return o
+683
 684
-685    Tags are not written or recovered automatically. The separator |is removed from the replica names.
-686
-687    Parameters
-688    ----------
-689    obsl : list
-690        List of Obs that will be exported.
-691        The Obs inside a structure do not have to be defined on the same set of configurations,
-692        but the storage requirement is increased, if this is not the case.
-693    name : str
-694        The name of the observable.
-695    spec : str
-696        Optional string that describes the contents of the file.
-697    origin : str
-698        Specify where the data has its origin.
-699    symbol : list
-700        A list of symbols that describe the observables to be written. May be empty.
-701    who : str
-702        Provide the name of the person that exports the data.
-703    enstags : dict
-704        Provide alternative enstag for ensembles in the form enstags = {ename: enstag}
-705        Otherwise, the ensemble name is used.
-706
-707    Returns
-708    -------
-709    xml_str : str
-710        XML string generated from the data
-711    """
-712    if enstags is None:
-713        enstags = {}
-714    od = {}
-715    r_names = []
-716    for o in obsl:
-717        r_names += [name for name in o.names if name.split('|')[0] in o.mc_names]
-718    r_names = sorted(set(r_names))
-719    mc_names = sorted(set([n.split('|')[0] for n in r_names]))
-720    for tmpname in mc_names:
-721        if tmpname not in enstags:
-722            enstags[tmpname] = tmpname
-723    ne = len(set(mc_names))
-724    cov_names = []
-725    for o in obsl:
-726        cov_names += list(o.cov_names)
-727    cov_names = sorted(set(cov_names))
-728    nc = len(set(cov_names))
-729    od['OBSERVABLES'] = {}
-730    od['OBSERVABLES']['SCHEMA'] = {'NAME': 'lattobs', 'VERSION': '1.0'}
-731    if who is None:
-732        who = getpass.getuser()
-733    od['OBSERVABLES']['origin'] = {
-734        'who': who,
-735        'date': str(datetime.datetime.now())[:-7],
-736        'host': socket.gethostname(),
-737        'tool': {'name': 'pyerrors', 'version': pyerrorsversion.__version__}}
-738    od['OBSERVABLES']['dobs'] = {}
-739    pd = od['OBSERVABLES']['dobs']
-740    pd['spec'] = spec
-741    pd['origin'] = origin
-742    pd['name'] = name
-743    pd['array'] = {}
-744    pd['array']['id'] = 'val'
-745    pd['array']['layout'] = '1 f%d' % (len(obsl))
-746    osymbol = ''
-747    if symbol:
-748        if not isinstance(symbol, list):
-749            raise Exception('Symbol has to be a list!')
-750        if not (len(symbol) == 0 or len(symbol) == len(obsl)):
-751            raise Exception('Symbol has to be a list of lenght 0 or %d!' % (len(obsl)))
-752        osymbol = symbol[0]
-753        for s in symbol[1:]:
-754            osymbol += ' %s' % s
-755        pd['array']['symbol'] = osymbol
-756
-757    pd['array']['#values'] = ['  '.join(['%1.16e' % o.value for o in obsl])]
-758    pd['ne'] = '%d' % (ne)
-759    pd['nc'] = '%d' % (nc)
-760    pd['edata'] = []
-761    for name in mc_names:
-762        ed = {}
-763        ed['enstag'] = enstags[name]
-764        onames = sorted([n for n in r_names if (n.startswith(name + '|') or n == name)])
-765        nr = len(onames)
-766        ed['nr'] = nr
-767        ed[''] = []
-768
-769        for r in range(nr):
-770            ad = {}
-771            repname = onames[r]
-772            ad['id'] = repname.replace('|', '')
-773            idx = _merge_idx([o.idl.get(repname, []) for o in obsl])
-774            Nconf = len(idx)
-775            layout = '%d i f%d' % (Nconf, len(obsl))
-776            ad['layout'] = layout
-777            data = ''
-778            counters = [0 for o in obsl]
-779            offsets = [o.r_values[repname] - o.value if repname in o.r_values else 0 for o in obsl]
-780            for ci in idx:
-781                data += '%d ' % ci
-782                for oi in range(len(obsl)):
-783                    o = obsl[oi]
-784                    if repname in o.idl:
-785                        if counters[oi] < 0:
-786                            num = 0
-787                            if num == 0:
-788                                data += '0 '
-789                            else:
-790                                data += '%1.16e ' % (num)
-791                            continue
-792                        if o.idl[repname][counters[oi]] == ci:
-793                            num = o.deltas[repname][counters[oi]] + offsets[oi]
-794                            if num == 0:
-795                                data += '0 '
-796                            else:
-797                                data += '%1.16e ' % (num)
-798                            counters[oi] += 1
-799                            if counters[oi] >= len(o.idl[repname]):
-800                                counters[oi] = -1
-801                        else:
-802                            num = 0
-803                            if num == 0:
-804                                data += '0 '
-805                            else:
-806                                data += '%1.16e ' % (num)
-807                    else:
-808                        data += '0 '
-809                data += '\n'
-810            ad['#data'] = data
-811            ed[''].append(ad)
-812        pd['edata'].append(ed)
-813
-814        allcov = {}
-815        for o in obsl:
-816            for cname in o.cov_names:
-817                if cname in allcov:
-818                    if not np.array_equal(allcov[cname], o.covobs[cname].cov):
-819                        raise Exception('Inconsistent covariance matrices for %s!' % (cname))
-820                else:
-821                    allcov[cname] = o.covobs[cname].cov
-822        pd['cdata'] = []
-823        for cname in cov_names:
-824            cd = {}
-825            cd['id'] = cname
-826
-827            covd = {'id': 'cov'}
-828            if allcov[cname].shape == ():
-829                ncov = 1
-830                covd['layout'] = '1 1 f'
-831                covd['#data'] = '%1.14e' % (allcov[cname])
-832            else:
-833                shape = allcov[cname].shape
-834                assert (shape[0] == shape[1])
-835                ncov = shape[0]
-836                covd['layout'] = '%d %d f' % (ncov, ncov)
-837                ds = ''
-838                for i in range(ncov):
-839                    for j in range(ncov):
-840                        val = allcov[cname][i][j]
-841                        if val == 0:
-842                            ds += '0 '
-843                        else:
-844                            ds += '%1.14e ' % (val)
-845                    ds += '\n'
-846                covd['#data'] = ds
-847
-848            gradd = {'id': 'grad'}
-849            gradd['layout'] = '%d f%d' % (ncov, len(obsl))
-850            ds = ''
-851            for i in range(ncov):
-852                for o in obsl:
-853                    if cname in o.covobs:
-854                        val = o.covobs[cname].grad[i].item()
-855                        if val != 0:
-856                            ds += '%1.14e ' % (val)
-857                        else:
-858                            ds += '0 '
-859                    else:
-860                        ds += '0 '
-861            gradd['#data'] = ds
-862            cd['array'] = [covd, gradd]
-863            pd['cdata'].append(cd)
-864
-865    rs = '<?xml version="1.0" encoding="utf-8"?>\n' + _dobsdict_to_xmlstring_spaces(od)
-866
-867    return rs
-868
-869
-870def write_dobs(obsl, fname, name, spec='dobs v1.0', origin='', symbol=[], who=None, enstags=None, gz=True):
-871    """Export a list of Obs or structures containing Obs to a .xml.gz file
-872    according to the Zeuthen dobs format.
-873
-874    Tags are not written or recovered automatically. The separator | is removed from the replica names.
+685def create_dobs_string(obsl, name, spec='dobs v1.0', origin='', symbol=None, who=None, enstags=None):
+686    """Generate the string for the export of a list of Obs or structures containing Obs
+687    to a .xml.gz file according to the Zeuthen dobs format.
+688
+689    Tags are not written or recovered automatically. The separator |is removed from the replica names.
+690
+691    Parameters
+692    ----------
+693    obsl : list
+694        List of Obs that will be exported.
+695        The Obs inside a structure do not have to be defined on the same set of configurations,
+696        but the storage requirement is increased, if this is not the case.
+697    name : str
+698        The name of the observable.
+699    spec : str
+700        Optional string that describes the contents of the file.
+701    origin : str
+702        Specify where the data has its origin.
+703    symbol : list
+704        A list of symbols that describe the observables to be written. May be empty.
+705    who : str
+706        Provide the name of the person that exports the data.
+707    enstags : dict
+708        Provide alternative enstag for ensembles in the form enstags = {ename: enstag}
+709        Otherwise, the ensemble name is used.
+710
+711    Returns
+712    -------
+713    xml_str : str
+714        XML string generated from the data
+715    """
+716    if enstags is None:
+717        enstags = {}
+718    if symbol is None:
+719        symbol = []
+720    od = {}
+721    r_names = []
+722    for o in obsl:
+723        r_names += [name for name in o.names if name.split('|')[0] in o.mc_names]
+724    r_names = sorted(set(r_names))
+725    mc_names = sorted(set([n.split('|')[0] for n in r_names]))
+726    for tmpname in mc_names:
+727        if tmpname not in enstags:
+728            enstags[tmpname] = tmpname
+729    ne = len(set(mc_names))
+730    cov_names = []
+731    for o in obsl:
+732        cov_names += list(o.cov_names)
+733    cov_names = sorted(set(cov_names))
+734    nc = len(set(cov_names))
+735    od['OBSERVABLES'] = {}
+736    od['OBSERVABLES']['SCHEMA'] = {'NAME': 'lattobs', 'VERSION': '1.0'}
+737    if who is None:
+738        who = getpass.getuser()
+739    od['OBSERVABLES']['origin'] = {
+740        'who': who,
+741        'date': str(datetime.datetime.now())[:-7],
+742        'host': socket.gethostname(),
+743        'tool': {'name': 'pyerrors', 'version': pyerrorsversion.__version__}}
+744    od['OBSERVABLES']['dobs'] = {}
+745    pd = od['OBSERVABLES']['dobs']
+746    pd['spec'] = spec
+747    pd['origin'] = origin
+748    pd['name'] = name
+749    pd['array'] = {}
+750    pd['array']['id'] = 'val'
+751    pd['array']['layout'] = f'1 f{len(obsl)}'
+752    osymbol = ''
+753    if symbol:
+754        if not isinstance(symbol, list):
+755            raise Exception('Symbol has to be a list!')
+756        if not (len(symbol) == 0 or len(symbol) == len(obsl)):
+757            raise Exception(f'Symbol has to be a list of lenght 0 or {len(obsl)}!')
+758        osymbol = symbol[0]
+759        for s in symbol[1:]:
+760            osymbol += f' {s}'
+761        pd['array']['symbol'] = osymbol
+762
+763    pd['array']['#values'] = ['  '.join([f'{o.value:1.16e}' for o in obsl])]
+764    pd['ne'] = f'{ne}'
+765    pd['nc'] = f'{nc}'
+766    pd['edata'] = []
+767    for name in mc_names:
+768        ed = {}
+769        ed['enstag'] = enstags[name]
+770        onames = sorted([n for n in r_names if (n.startswith(name + '|') or n == name)])
+771        nr = len(onames)
+772        ed['nr'] = nr
+773        ed[''] = []
+774
+775        for r in range(nr):
+776            ad = {}
+777            repname = onames[r]
+778            ad['id'] = repname.replace('|', '')
+779            idx = _merge_idx([o.idl.get(repname, []) for o in obsl])
+780            Nconf = len(idx)
+781            layout = f'{Nconf} i f{len(obsl)}'
+782            ad['layout'] = layout
+783            data = ''
+784            counters = [0 for o in obsl]
+785            offsets = [o.r_values[repname] - o.value if repname in o.r_values else 0 for o in obsl]
+786            for ci in idx:
+787                data += f'{ci} '
+788                for oi in range(len(obsl)):
+789                    o = obsl[oi]
+790                    if repname in o.idl:
+791                        if counters[oi] < 0:
+792                            num = 0
+793                            if num == 0:
+794                                data += '0 '
+795                            else:
+796                                data += f'{num:1.16e} '
+797                            continue
+798                        if o.idl[repname][counters[oi]] == ci:
+799                            num = o.deltas[repname][counters[oi]] + offsets[oi]
+800                            if num == 0:
+801                                data += '0 '
+802                            else:
+803                                data += f'{num:1.16e} '
+804                            counters[oi] += 1
+805                            if counters[oi] >= len(o.idl[repname]):
+806                                counters[oi] = -1
+807                        else:
+808                            num = 0
+809                            if num == 0:
+810                                data += '0 '
+811                            else:
+812                                data += f'{num:1.16e} '
+813                    else:
+814                        data += '0 '
+815                data += '\n'
+816            ad['#data'] = data
+817            ed[''].append(ad)
+818        pd['edata'].append(ed)
+819
+820        allcov = {}
+821        for o in obsl:
+822            for cname in o.cov_names:
+823                if cname in allcov:
+824                    if not np.array_equal(allcov[cname], o.covobs[cname].cov):
+825                        raise Exception(f'Inconsistent covariance matrices for {cname}!')
+826                else:
+827                    allcov[cname] = o.covobs[cname].cov
+828        pd['cdata'] = []
+829        for cname in cov_names:
+830            cd = {}
+831            cd['id'] = cname
+832
+833            covd = {'id': 'cov'}
+834            if allcov[cname].shape == ():
+835                ncov = 1
+836                covd['layout'] = '1 1 f'
+837                covd['#data'] = f'{allcov[cname]:1.14e}'
+838            else:
+839                shape = allcov[cname].shape
+840                assert (shape[0] == shape[1])
+841                ncov = shape[0]
+842                covd['layout'] = f'{ncov} {ncov} f'
+843                ds = ''
+844                for i in range(ncov):
+845                    for j in range(ncov):
+846                        val = allcov[cname][i][j]
+847                        if val == 0:
+848                            ds += '0 '
+849                        else:
+850                            ds += f'{val:1.14e} '
+851                    ds += '\n'
+852                covd['#data'] = ds
+853
+854            gradd = {'id': 'grad'}
+855            gradd['layout'] = f'{ncov} f{len(obsl)}'
+856            ds = ''
+857            for i in range(ncov):
+858                for o in obsl:
+859                    if cname in o.covobs:
+860                        val = o.covobs[cname].grad[i].item()
+861                        if val != 0:
+862                            ds += f'{val:1.14e} '
+863                        else:
+864                            ds += '0 '
+865                    else:
+866                        ds += '0 '
+867            gradd['#data'] = ds
+868            cd['array'] = [covd, gradd]
+869            pd['cdata'].append(cd)
+870
+871    rs = '<?xml version="1.0" encoding="utf-8"?>\n' + _dobsdict_to_xmlstring_spaces(od)
+872
+873    return rs
+874
 875
-876    Parameters
-877    ----------
-878    obsl : list
-879        List of Obs that will be exported.
-880        The Obs inside a structure do not have to be defined on the same set of configurations,
-881        but the storage requirement is increased, if this is not the case.
-882    fname : str
-883        Filename of the output file.
-884    name : str
-885        The name of the observable.
-886    spec : str
-887        Optional string that describes the contents of the file.
-888    origin : str
-889        Specify where the data has its origin.
-890    symbol : list
-891        A list of symbols that describe the observables to be written. May be empty.
-892    who : str
-893        Provide the name of the person that exports the data.
-894    enstags : dict
-895        Provide alternative enstag for ensembles in the form enstags = {ename: enstag}
-896        Otherwise, the ensemble name is used.
-897    gz : bool
-898        If True, the output is a gzipped XML. If False, the output is a XML file.
-899
-900    Returns
-901    -------
-902    None
-903    """
-904    if enstags is None:
-905        enstags = {}
-906
-907    dobsstring = create_dobs_string(obsl, name, spec, origin, symbol, who, enstags=enstags)
-908
-909    if not fname.endswith('.xml') and not fname.endswith('.gz'):
-910        fname += '.xml'
-911
-912    if gz:
-913        if not fname.endswith('.gz'):
-914            fname += '.gz'
-915
-916        fp = gzip.open(fname, 'wb')
-917        fp.write(dobsstring.encode('utf-8'))
-918    else:
-919        fp = open(fname, 'w', encoding='utf-8')
-920        fp.write(dobsstring)
-921    fp.close()
+876def write_dobs(obsl, fname, name, spec='dobs v1.0', origin='', symbol=None, who=None, enstags=None, gz=True):
+877    """Export a list of Obs or structures containing Obs to a .xml.gz file
+878    according to the Zeuthen dobs format.
+879
+880    Tags are not written or recovered automatically. The separator | is removed from the replica names.
+881
+882    Parameters
+883    ----------
+884    obsl : list
+885        List of Obs that will be exported.
+886        The Obs inside a structure do not have to be defined on the same set of configurations,
+887        but the storage requirement is increased, if this is not the case.
+888    fname : str
+889        Filename of the output file.
+890    name : str
+891        The name of the observable.
+892    spec : str
+893        Optional string that describes the contents of the file.
+894    origin : str
+895        Specify where the data has its origin.
+896    symbol : list
+897        A list of symbols that describe the observables to be written. May be empty.
+898    who : str
+899        Provide the name of the person that exports the data.
+900    enstags : dict
+901        Provide alternative enstag for ensembles in the form enstags = {ename: enstag}
+902        Otherwise, the ensemble name is used.
+903    gz : bool
+904        If True, the output is a gzipped XML. If False, the output is a XML file.
+905
+906    Returns
+907    -------
+908    None
+909    """
+910    if enstags is None:
+911        enstags = {}
+912
+913    dobsstring = create_dobs_string(obsl, name, spec, origin, symbol, who, enstags=enstags)
+914
+915    if not fname.endswith('.xml') and not fname.endswith('.gz'):
+916        fname += '.xml'
+917
+918    if gz:
+919        if not fname.endswith('.gz'):
+920            fname += '.gz'
+921
+922        fp = gzip.open(fname, 'wb')
+923        fp.write(dobsstring.encode('utf-8'))
+924    else:
+925        fp = open(fname, 'w', encoding='utf-8')
+926        fp.write(dobsstring)
+927    fp.close()
 
@@ -1024,101 +1030,104 @@
def - create_pobs_string(obsl, name, spec='', origin='', symbol=[], enstag=None): + create_pobs_string(obsl, name, spec='', origin='', symbol=None, enstag=None):
-
 89def create_pobs_string(obsl, name, spec='', origin='', symbol=[], enstag=None):
- 90    """Export a list of Obs or structures containing Obs to an xml string
- 91    according to the Zeuthen pobs format.
- 92
- 93    Tags are not written or recovered automatically. The separator | is removed from the replica names.
- 94
- 95    Parameters
- 96    ----------
- 97    obsl : list
- 98        List of Obs that will be exported.
- 99        The Obs inside a structure have to be defined on the same ensemble.
-100    name : str
-101        The name of the observable.
-102    spec : str
-103        Optional string that describes the contents of the file.
-104    origin : str
-105        Specify where the data has its origin.
-106    symbol : list
-107        A list of symbols that describe the observables to be written. May be empty.
-108    enstag : str
-109        Enstag that is written to pobs. If None, the ensemble name is used.
-110
-111    Returns
-112    -------
-113    xml_str : str
-114        XML formatted string of the input data
-115    """
-116
-117    od = {}
-118    ename = obsl[0].e_names[0]
-119    names = list(obsl[0].deltas.keys())
-120    nr = len(names)
-121    onames = [name.replace('|', '') for name in names]
-122    for o in obsl:
-123        if len(o.e_names) != 1:
-124            raise Exception('You try to export dobs to obs!')
-125        if o.e_names[0] != ename:
-126            raise Exception('You try to export dobs to obs!')
-127        if len(o.deltas.keys()) != nr:
-128            raise Exception('Incompatible obses in list')
-129    od['observables'] = {}
-130    od['observables']['schema'] = {'name': 'lattobs', 'version': '1.0'}
-131    od['observables']['origin'] = {
-132        'who': getpass.getuser(),
-133        'date': str(datetime.datetime.now())[:-7],
-134        'host': socket.gethostname(),
-135        'tool': {'name': 'pyerrors', 'version': pyerrorsversion.__version__}}
-136    od['observables']['pobs'] = {}
-137    pd = od['observables']['pobs']
-138    pd['spec'] = spec
-139    pd['origin'] = origin
-140    pd['name'] = name
-141    if enstag:
-142        if not isinstance(enstag, str):
-143            raise Exception('enstag has to be a string!')
-144        pd['enstag'] = enstag
-145    else:
-146        pd['enstag'] = ename
-147    pd['nr'] = '%d' % (nr)
-148    pd['array'] = []
-149    osymbol = 'cfg'
-150    if not isinstance(symbol, list):
-151        raise Exception('Symbol has to be a list!')
-152    if not (len(symbol) == 0 or len(symbol) == len(obsl)):
-153        raise Exception('Symbol has to be a list of lenght 0 or %d!' % (len(obsl)))
-154    for s in symbol:
-155        osymbol += ' %s' % s
-156    for r in range(nr):
-157        ad = {}
-158        ad['id'] = onames[r]
-159        Nconf = len(obsl[0].deltas[names[r]])
-160        layout = '%d i f%d' % (Nconf, len(obsl))
-161        ad['layout'] = layout
-162        ad['symbol'] = osymbol
-163        data = ''
-164        for c in range(Nconf):
-165            data += '%d ' % obsl[0].idl[names[r]][c]
-166            for o in obsl:
-167                num = o.deltas[names[r]][c] + o.r_values[names[r]]
-168                if num == 0:
-169                    data += '0 '
-170                else:
-171                    data += '%1.16e ' % (num)
-172            data += '\n'
-173        ad['#data'] = data
-174        pd['array'].append(ad)
-175
-176    rs = '<?xml version="1.0" encoding="utf-8"?>\n' + _dict_to_xmlstring_spaces(od)
-177    return rs
+            
 90def create_pobs_string(obsl, name, spec='', origin='', symbol=None, enstag=None):
+ 91    """Export a list of Obs or structures containing Obs to an xml string
+ 92    according to the Zeuthen pobs format.
+ 93
+ 94    Tags are not written or recovered automatically. The separator | is removed from the replica names.
+ 95
+ 96    Parameters
+ 97    ----------
+ 98    obsl : list
+ 99        List of Obs that will be exported.
+100        The Obs inside a structure have to be defined on the same ensemble.
+101    name : str
+102        The name of the observable.
+103    spec : str
+104        Optional string that describes the contents of the file.
+105    origin : str
+106        Specify where the data has its origin.
+107    symbol : list
+108        A list of symbols that describe the observables to be written. May be empty.
+109    enstag : str
+110        Enstag that is written to pobs. If None, the ensemble name is used.
+111
+112    Returns
+113    -------
+114    xml_str : str
+115        XML formatted string of the input data
+116    """
+117
+118    if symbol is None:
+119        symbol = []
+120
+121    od = {}
+122    ename = obsl[0].e_names[0]
+123    names = list(obsl[0].deltas.keys())
+124    nr = len(names)
+125    onames = [name.replace('|', '') for name in names]
+126    for o in obsl:
+127        if len(o.e_names) != 1:
+128            raise Exception('You try to export dobs to obs!')
+129        if o.e_names[0] != ename:
+130            raise Exception('You try to export dobs to obs!')
+131        if len(o.deltas.keys()) != nr:
+132            raise Exception('Incompatible obses in list')
+133    od['observables'] = {}
+134    od['observables']['schema'] = {'name': 'lattobs', 'version': '1.0'}
+135    od['observables']['origin'] = {
+136        'who': getpass.getuser(),
+137        'date': str(datetime.datetime.now())[:-7],
+138        'host': socket.gethostname(),
+139        'tool': {'name': 'pyerrors', 'version': pyerrorsversion.__version__}}
+140    od['observables']['pobs'] = {}
+141    pd = od['observables']['pobs']
+142    pd['spec'] = spec
+143    pd['origin'] = origin
+144    pd['name'] = name
+145    if enstag:
+146        if not isinstance(enstag, str):
+147            raise Exception('enstag has to be a string!')
+148        pd['enstag'] = enstag
+149    else:
+150        pd['enstag'] = ename
+151    pd['nr'] = f'{nr}'
+152    pd['array'] = []
+153    osymbol = 'cfg'
+154    if not isinstance(symbol, list):
+155        raise Exception('Symbol has to be a list!')
+156    if not (len(symbol) == 0 or len(symbol) == len(obsl)):
+157        raise Exception(f'Symbol has to be a list of lenght 0 or {len(obsl)}!')
+158    for s in symbol:
+159        osymbol += f' {s}'
+160    for r in range(nr):
+161        ad = {}
+162        ad['id'] = onames[r]
+163        Nconf = len(obsl[0].deltas[names[r]])
+164        layout = f'{Nconf} i f{len(obsl)}'
+165        ad['layout'] = layout
+166        ad['symbol'] = osymbol
+167        data = ''
+168        for c in range(Nconf):
+169            data += f'{obsl[0].idl[names[r]][c]} '
+170            for o in obsl:
+171                num = o.deltas[names[r]][c] + o.r_values[names[r]]
+172                if num == 0:
+173                    data += '0 '
+174                else:
+175                    data += f'{num:1.16e} '
+176            data += '\n'
+177        ad['#data'] = data
+178        pd['array'].append(ad)
+179
+180    rs = '<?xml version="1.0" encoding="utf-8"?>\n' + _dict_to_xmlstring_spaces(od)
+181    return rs
 
@@ -1160,57 +1169,57 @@ XML formatted string of the input data
def - write_pobs( obsl, fname, name, spec='', origin='', symbol=[], enstag=None, gz=True): + write_pobs( obsl, fname, name, spec='', origin='', symbol=None, enstag=None, gz=True):
-
180def write_pobs(obsl, fname, name, spec='', origin='', symbol=[], enstag=None, gz=True):
-181    """Export a list of Obs or structures containing Obs to a .xml.gz file
-182    according to the Zeuthen pobs format.
-183
-184    Tags are not written or recovered automatically. The separator | is removed from the replica names.
-185
-186    Parameters
-187    ----------
-188    obsl : list
-189        List of Obs that will be exported.
-190        The Obs inside a structure have to be defined on the same ensemble.
-191    fname : str
-192        Filename of the output file.
-193    name : str
-194        The name of the observable.
-195    spec : str
-196        Optional string that describes the contents of the file.
-197    origin : str
-198        Specify where the data has its origin.
-199    symbol : list
-200        A list of symbols that describe the observables to be written. May be empty.
-201    enstag : str
-202        Enstag that is written to pobs. If None, the ensemble name is used.
-203    gz : bool
-204        If True, the output is a gzipped xml. If False, the output is an xml file.
-205
-206    Returns
-207    -------
-208    None
-209    """
-210    pobsstring = create_pobs_string(obsl, name, spec, origin, symbol, enstag)
-211
-212    if not fname.endswith('.xml') and not fname.endswith('.gz'):
-213        fname += '.xml'
-214
-215    if gz:
-216        if not fname.endswith('.gz'):
-217            fname += '.gz'
+            
184def write_pobs(obsl, fname, name, spec='', origin='', symbol=None, enstag=None, gz=True):
+185    """Export a list of Obs or structures containing Obs to a .xml.gz file
+186    according to the Zeuthen pobs format.
+187
+188    Tags are not written or recovered automatically. The separator | is removed from the replica names.
+189
+190    Parameters
+191    ----------
+192    obsl : list
+193        List of Obs that will be exported.
+194        The Obs inside a structure have to be defined on the same ensemble.
+195    fname : str
+196        Filename of the output file.
+197    name : str
+198        The name of the observable.
+199    spec : str
+200        Optional string that describes the contents of the file.
+201    origin : str
+202        Specify where the data has its origin.
+203    symbol : list
+204        A list of symbols that describe the observables to be written. May be empty.
+205    enstag : str
+206        Enstag that is written to pobs. If None, the ensemble name is used.
+207    gz : bool
+208        If True, the output is a gzipped xml. If False, the output is an xml file.
+209
+210    Returns
+211    -------
+212    None
+213    """
+214    pobsstring = create_pobs_string(obsl, name, spec, origin, symbol, enstag)
+215
+216    if not fname.endswith('.xml') and not fname.endswith('.gz'):
+217        fname += '.xml'
 218
-219        fp = gzip.open(fname, 'wb')
-220        fp.write(pobsstring.encode('utf-8'))
-221    else:
-222        fp = open(fname, 'w', encoding='utf-8')
-223        fp.write(pobsstring)
-224    fp.close()
+219    if gz:
+220        if not fname.endswith('.gz'):
+221            fname += '.gz'
+222
+223        fp = gzip.open(fname, 'wb')
+224        fp.write(pobsstring.encode('utf-8'))
+225    else:
+226        fp = open(fname, 'w', encoding='utf-8')
+227        fp.write(pobsstring)
+228    fp.close()
 
@@ -1261,103 +1270,103 @@ If True, the output is a gzipped xml. If False, the output is an xml file.
-
301def read_pobs(fname, full_output=False, gz=True, separator_insertion=None):
-302    """Import a list of Obs from an xml.gz file in the Zeuthen pobs format.
-303
-304    Tags are not written or recovered automatically.
-305
-306    Parameters
-307    ----------
-308    fname : str
-309        Filename of the input file.
-310    full_output : bool
-311        If True, a dict containing auxiliary information and the data is returned.
-312        If False, only the data is returned as list.
-313    separatior_insertion: str or int
-314        str: replace all occurences of "separator_insertion" within the replica names
-315        by "|%s" % (separator_insertion) when constructing the names of the replica.
-316        int: Insert the separator "|" at the position given by separator_insertion.
-317        None (default): Replica names remain unchanged.
-318
-319    Returns
-320    -------
-321    res : list[Obs]
-322        Imported data
-323    or
-324    res : dict
-325        Imported data and meta-data
-326    """
-327
-328    if not fname.endswith('.xml') and not fname.endswith('.gz'):
-329        fname += '.xml'
-330    if gz:
-331        if not fname.endswith('.gz'):
-332            fname += '.gz'
-333        with gzip.open(fname, 'r') as fin:
-334            content = fin.read()
-335    else:
-336        if fname.endswith('.gz'):
-337            warnings.warn("Trying to read from %s without unzipping!" % fname, UserWarning)
-338        with open(fname, 'r') as fin:
-339            content = fin.read()
-340
-341    # parse xml file content
-342    root = et.fromstring(content)
-343
-344    _check(root[2].tag == 'pobs')
-345    pobs = root[2]
-346
-347    version = root[0][1].text.strip()
-348
-349    _check(root[1].tag == 'origin')
-350    file_origin = _etree_to_dict(root[1])['origin']
-351
-352    deltas = []
-353    names = []
-354    idl = []
-355    for i in range(5, len(pobs)):
-356        delta, name, idx = _import_rdata(pobs[i])
-357        deltas.append(delta)
-358        if separator_insertion is None:
-359            pass
-360        elif isinstance(separator_insertion, int):
-361            name = name[:separator_insertion] + '|' + name[separator_insertion:]
-362        elif isinstance(separator_insertion, str):
-363            name = name.replace(separator_insertion, "|%s" % (separator_insertion))
-364        else:
-365            raise Exception("separator_insertion has to be string or int, is ", type(separator_insertion))
-366        names.append(name)
-367        idl.append(idx)
-368    res = [Obs([d[i] for d in deltas], names, idl=idl) for i in range(len(deltas[0]))]
-369
-370    descriptiond = {}
-371    for i in range(4):
-372        descriptiond[pobs[i].tag] = pobs[i].text.strip()
+            
305def read_pobs(fname, full_output=False, gz=True, separator_insertion=None):
+306    """Import a list of Obs from an xml.gz file in the Zeuthen pobs format.
+307
+308    Tags are not written or recovered automatically.
+309
+310    Parameters
+311    ----------
+312    fname : str
+313        Filename of the input file.
+314    full_output : bool
+315        If True, a dict containing auxiliary information and the data is returned.
+316        If False, only the data is returned as list.
+317    separatior_insertion: str or int
+318        str: replace all occurences of "separator_insertion" within the replica names
+319        by "|%s" % (separator_insertion) when constructing the names of the replica.
+320        int: Insert the separator "|" at the position given by separator_insertion.
+321        None (default): Replica names remain unchanged.
+322
+323    Returns
+324    -------
+325    res : list[Obs]
+326        Imported data
+327    or
+328    res : dict
+329        Imported data and meta-data
+330    """
+331
+332    if not fname.endswith('.xml') and not fname.endswith('.gz'):
+333        fname += '.xml'
+334    if gz:
+335        if not fname.endswith('.gz'):
+336            fname += '.gz'
+337        with gzip.open(fname, 'r') as fin:
+338            content = fin.read()
+339    else:
+340        if fname.endswith('.gz'):
+341            warnings.warn(f"Trying to read from {fname} without unzipping!", UserWarning, stacklevel=2)
+342        with open(fname) as fin:
+343            content = fin.read()
+344
+345    # parse xml file content
+346    root = et.fromstring(content)
+347
+348    _check(root[2].tag == 'pobs')
+349    pobs = root[2]
+350
+351    version = root[0][1].text.strip()
+352
+353    _check(root[1].tag == 'origin')
+354    file_origin = _etree_to_dict(root[1])['origin']
+355
+356    deltas = []
+357    names = []
+358    idl = []
+359    for i in range(5, len(pobs)):
+360        delta, name, idx = _import_rdata(pobs[i])
+361        deltas.append(delta)
+362        if separator_insertion is None:
+363            pass
+364        elif isinstance(separator_insertion, int):
+365            name = name[:separator_insertion] + '|' + name[separator_insertion:]
+366        elif isinstance(separator_insertion, str):
+367            name = name.replace(separator_insertion, f"|{separator_insertion}")
+368        else:
+369            raise Exception("separator_insertion has to be string or int, is ", type(separator_insertion))
+370        names.append(name)
+371        idl.append(idx)
+372    res = [Obs([d[i] for d in deltas], names, idl=idl) for i in range(len(deltas[0]))]
 373
-374    _check(pobs[4].tag == "nr")
-375
-376    _check(pobs[5].tag == 'array')
-377    if pobs[5][1].tag == 'symbol':
-378        symbol = pobs[5][1].text.strip()
-379        descriptiond['symbol'] = symbol
-380
-381    if full_output:
-382        retd = {}
-383        tool = file_origin.get('tool', None)
-384        if tool:
-385            program = tool['name'] + ' ' + tool['version']
-386        else:
-387            program = ''
-388        retd['program'] = program
-389        retd['version'] = version
-390        retd['who'] = file_origin['who']
-391        retd['date'] = file_origin['date']
-392        retd['host'] = file_origin['host']
-393        retd['description'] = descriptiond
-394        retd['obsdata'] = res
-395        return retd
-396    else:
-397        return res
+374    descriptiond = {}
+375    for i in range(4):
+376        descriptiond[pobs[i].tag] = pobs[i].text.strip()
+377
+378    _check(pobs[4].tag == "nr")
+379
+380    _check(pobs[5].tag == 'array')
+381    if pobs[5][1].tag == 'symbol':
+382        symbol = pobs[5][1].text.strip()
+383        descriptiond['symbol'] = symbol
+384
+385    if full_output:
+386        retd = {}
+387        tool = file_origin.get('tool', None)
+388        if tool:
+389            program = tool['name'] + ' ' + tool['version']
+390        else:
+391            program = ''
+392        retd['program'] = program
+393        retd['version'] = version
+394        retd['who'] = file_origin['who']
+395        retd['date'] = file_origin['date']
+396        retd['host'] = file_origin['host']
+397        retd['description'] = descriptiond
+398        retd['obsdata'] = res
+399        return retd
+400    else:
+401        return res
 
@@ -1404,179 +1413,179 @@ Imported data and meta-data
-
401def import_dobs_string(content, full_output=False, separator_insertion=True):
-402    """Import a list of Obs from a string in the Zeuthen dobs format.
-403
-404    Tags are not written or recovered automatically.
-405
-406    Parameters
-407    ----------
-408    content : str
-409        XML string containing the data
-410    full_output : bool
-411        If True, a dict containing auxiliary information and the data is returned.
-412        If False, only the data is returned as list.
-413    separatior_insertion: str, int or bool
-414        str: replace all occurences of "separator_insertion" within the replica names
-415        by "|%s" % (separator_insertion) when constructing the names of the replica.
-416        int: Insert the separator "|" at the position given by separator_insertion.
-417        True (default): separator "|" is inserted after len(ensname), assuming that the
-418        ensemble name is a prefix to the replica name.
-419        None or False: No separator is inserted.
-420
-421    Returns
-422    -------
-423    res : list[Obs]
-424        Imported data
-425    or
-426    res : dict
-427        Imported data and meta-data
-428    """
-429
-430    root = et.fromstring(content)
-431
-432    _check(root.tag == 'OBSERVABLES')
-433    _check(root[0].tag == 'SCHEMA')
-434    version = root[0][1].text.strip()
+            
405def import_dobs_string(content, full_output=False, separator_insertion=True):
+406    """Import a list of Obs from a string in the Zeuthen dobs format.
+407
+408    Tags are not written or recovered automatically.
+409
+410    Parameters
+411    ----------
+412    content : str
+413        XML string containing the data
+414    full_output : bool
+415        If True, a dict containing auxiliary information and the data is returned.
+416        If False, only the data is returned as list.
+417    separatior_insertion: str, int or bool
+418        str: replace all occurences of "separator_insertion" within the replica names
+419        by "|%s" % (separator_insertion) when constructing the names of the replica.
+420        int: Insert the separator "|" at the position given by separator_insertion.
+421        True (default): separator "|" is inserted after len(ensname), assuming that the
+422        ensemble name is a prefix to the replica name.
+423        None or False: No separator is inserted.
+424
+425    Returns
+426    -------
+427    res : list[Obs]
+428        Imported data
+429    or
+430    res : dict
+431        Imported data and meta-data
+432    """
+433
+434    root = et.fromstring(content)
 435
-436    _check(root[1].tag == 'origin')
-437    file_origin = _etree_to_dict(root[1])['origin']
-438
-439    _check(root[2].tag == 'dobs')
-440
-441    dobs = root[2]
+436    _check(root.tag == 'OBSERVABLES')
+437    _check(root[0].tag == 'SCHEMA')
+438    version = root[0][1].text.strip()
+439
+440    _check(root[1].tag == 'origin')
+441    file_origin = _etree_to_dict(root[1])['origin']
 442
-443    descriptiond = {}
-444    for i in range(3):
-445        descriptiond[dobs[i].tag] = dobs[i].text.strip()
+443    _check(root[2].tag == 'dobs')
+444
+445    dobs = root[2]
 446
-447    _check(dobs[3].tag == 'array')
-448
-449    symbol = []
-450    if dobs[3][1].tag == 'symbol':
-451        symbol = dobs[3][1].text.strip()
-452        descriptiond['symbol'] = symbol
-453    mean = _import_array(dobs[3])[0]
-454
-455    _check(dobs[4].tag == "ne")
-456    ne = int(dobs[4].text.strip())
-457    _check(dobs[5].tag == "nc")
+447    descriptiond = {}
+448    for i in range(3):
+449        descriptiond[dobs[i].tag] = dobs[i].text.strip()
+450
+451    _check(dobs[3].tag == 'array')
+452
+453    symbol = []
+454    if dobs[3][1].tag == 'symbol':
+455        symbol = dobs[3][1].text.strip()
+456        descriptiond['symbol'] = symbol
+457    mean = _import_array(dobs[3])[0]
 458
-459    idld = {}
-460    deltad = {}
-461    covd = {}
-462    gradd = {}
-463    names = []
-464    e_names = []
-465    enstags = {}
-466    for k in range(6, len(list(dobs))):
-467        if dobs[k].tag == "edata":
-468            _check(dobs[k][0].tag == "enstag")
-469            ename = dobs[k][0].text.strip()
-470            e_names.append(ename)
-471            _check(dobs[k][1].tag == "nr")
-472            R = int(dobs[k][1].text.strip())
-473            for i in range(2, 2 + R):
-474                deltas, rname, idx = _import_rdata(dobs[k][i])
-475                if separator_insertion is None or False:
-476                    pass
-477                elif separator_insertion is True:
-478                    if rname.startswith(ename):
-479                        rname = rname[:len(ename)] + '|' + rname[len(ename):]
-480                elif isinstance(separator_insertion, int):
-481                    rname = rname[:separator_insertion] + '|' + rname[separator_insertion:]
-482                elif isinstance(separator_insertion, str):
-483                    rname = rname.replace(separator_insertion, "|%s" % (separator_insertion))
-484                else:
-485                    raise Exception("separator_insertion has to be string or int, is ", type(separator_insertion))
-486                if '|' in rname:
-487                    new_ename = rname[:rname.index('|')]
+459    _check(dobs[4].tag == "ne")
+460    ne = int(dobs[4].text.strip())
+461    _check(dobs[5].tag == "nc")
+462
+463    idld = {}
+464    deltad = {}
+465    covd = {}
+466    gradd = {}
+467    names = []
+468    e_names = []
+469    enstags = {}
+470    for k in range(6, len(list(dobs))):
+471        if dobs[k].tag == "edata":
+472            _check(dobs[k][0].tag == "enstag")
+473            ename = dobs[k][0].text.strip()
+474            e_names.append(ename)
+475            _check(dobs[k][1].tag == "nr")
+476            R = int(dobs[k][1].text.strip())
+477            for i in range(2, 2 + R):
+478                deltas, rname, idx = _import_rdata(dobs[k][i])
+479                if separator_insertion is None or False:
+480                    pass
+481                elif separator_insertion is True:
+482                    if rname.startswith(ename):
+483                        rname = rname[:len(ename)] + '|' + rname[len(ename):]
+484                elif isinstance(separator_insertion, int):
+485                    rname = rname[:separator_insertion] + '|' + rname[separator_insertion:]
+486                elif isinstance(separator_insertion, str):
+487                    rname = rname.replace(separator_insertion, f"|{separator_insertion}")
 488                else:
-489                    new_ename = ename
-490                enstags[new_ename] = ename
-491                idld[rname] = idx
-492                deltad[rname] = deltas
-493                names.append(rname)
-494        elif dobs[k].tag == "cdata":
-495            cname, cov, grad = _import_cdata(dobs[k])
-496            covd[cname] = cov
-497            if grad.shape[1] == 1:
-498                gradd[cname] = [grad for i in range(len(mean))]
-499            else:
-500                gradd[cname] = grad.T
-501        else:
-502            _check(False)
-503    names = list(set(names))
-504
-505    for name in names:
-506        for i in range(len(deltad[name])):
-507            tmp = np.zeros_like(deltad[name][i])
-508            for j in range(len(deltad[name][i])):
-509                if deltad[name][i][j] != 0.:
-510                    tmp[j] = deltad[name][i][j] + mean[i]
-511            deltad[name][i] = tmp
-512
-513    res = []
-514    for i in range(len(mean)):
-515        deltas = []
-516        idl = []
-517        obs_names = []
-518        for name in names:
-519            h = np.unique(deltad[name][i])
-520            if len(h) == 1 and np.all(h == mean[i]):
-521                continue
-522            repdeltas = []
-523            repidl = []
-524            for j in range(len(deltad[name][i])):
-525                if deltad[name][i][j] != 0.:
-526                    repdeltas.append(deltad[name][i][j])
-527                    repidl.append(idld[name][j])
-528            if len(repdeltas) > 0:
-529                obs_names.append(name)
-530                deltas.append(repdeltas)
-531                idl.append(repidl)
-532
-533        obsmeans = [np.average(deltas[j]) for j in range(len(deltas))]
-534        res.append(Obs([np.array(deltas[j]) - obsmeans[j] for j in range(len(obsmeans))], obs_names, idl=idl, means=obsmeans))
-535        res[-1]._value = mean[i]
-536    _check(len(e_names) == ne)
-537
-538    cnames = list(covd.keys())
-539    for i in range(len(res)):
-540        new_covobs = {name: Covobs(0, covd[name], name, grad=gradd[name][i]) for name in cnames}
-541        for name in cnames:
-542            if np.all(new_covobs[name].grad == 0):
-543                del new_covobs[name]
-544        cnames_loc = list(new_covobs.keys())
-545        for name in cnames_loc:
-546            res[i].names.append(name)
-547            res[i].shape[name] = 1
-548            res[i].idl[name] = []
-549        res[i]._covobs = new_covobs
-550
-551    if symbol:
-552        for i in range(len(res)):
-553            res[i].tag = symbol[i]
-554            if res[i].tag == 'None':
-555                res[i].tag = None
-556    if full_output:
-557        retd = {}
-558        tool = file_origin.get('tool', None)
-559        if tool:
-560            program = tool['name'] + ' ' + tool['version']
-561        else:
-562            program = ''
-563        retd['program'] = program
-564        retd['version'] = version
-565        retd['who'] = file_origin['who']
-566        retd['date'] = file_origin['date']
-567        retd['host'] = file_origin['host']
-568        retd['description'] = descriptiond
-569        retd['enstags'] = enstags
-570        retd['obsdata'] = res
-571        return retd
-572    else:
-573        return res
+489                    raise Exception("separator_insertion has to be string or int, is ", type(separator_insertion))
+490                if '|' in rname:
+491                    new_ename = rname[:rname.index('|')]
+492                else:
+493                    new_ename = ename
+494                enstags[new_ename] = ename
+495                idld[rname] = idx
+496                deltad[rname] = deltas
+497                names.append(rname)
+498        elif dobs[k].tag == "cdata":
+499            cname, cov, grad = _import_cdata(dobs[k])
+500            covd[cname] = cov
+501            if grad.shape[1] == 1:
+502                gradd[cname] = [grad for i in range(len(mean))]
+503            else:
+504                gradd[cname] = grad.T
+505        else:
+506            _check(False)
+507    names = list(set(names))
+508
+509    for name in names:
+510        for i in range(len(deltad[name])):
+511            tmp = np.zeros_like(deltad[name][i])
+512            for j in range(len(deltad[name][i])):
+513                if deltad[name][i][j] != 0.:
+514                    tmp[j] = deltad[name][i][j] + mean[i]
+515            deltad[name][i] = tmp
+516
+517    res = []
+518    for i in range(len(mean)):
+519        deltas = []
+520        idl = []
+521        obs_names = []
+522        for name in names:
+523            h = np.unique(deltad[name][i])
+524            if len(h) == 1 and np.all(h == mean[i]):
+525                continue
+526            repdeltas = []
+527            repidl = []
+528            for j in range(len(deltad[name][i])):
+529                if deltad[name][i][j] != 0.:
+530                    repdeltas.append(deltad[name][i][j])
+531                    repidl.append(idld[name][j])
+532            if len(repdeltas) > 0:
+533                obs_names.append(name)
+534                deltas.append(repdeltas)
+535                idl.append(repidl)
+536
+537        obsmeans = [np.average(deltas[j]) for j in range(len(deltas))]
+538        res.append(Obs([np.array(deltas[j]) - obsmeans[j] for j in range(len(obsmeans))], obs_names, idl=idl, means=obsmeans))
+539        res[-1]._value = mean[i]
+540    _check(len(e_names) == ne)
+541
+542    cnames = list(covd.keys())
+543    for i in range(len(res)):
+544        new_covobs = {name: Covobs(0, covd[name], name, grad=gradd[name][i]) for name in cnames}
+545        for name in cnames:
+546            if np.all(new_covobs[name].grad == 0):
+547                del new_covobs[name]
+548        cnames_loc = list(new_covobs.keys())
+549        for name in cnames_loc:
+550            res[i].names.append(name)
+551            res[i].shape[name] = 1
+552            res[i].idl[name] = []
+553        res[i]._covobs = new_covobs
+554
+555    if symbol:
+556        for i in range(len(res)):
+557            res[i].tag = symbol[i]
+558            if res[i].tag == 'None':
+559                res[i].tag = None
+560    if full_output:
+561        retd = {}
+562        tool = file_origin.get('tool', None)
+563        if tool:
+564            program = tool['name'] + ' ' + tool['version']
+565        else:
+566            program = ''
+567        retd['program'] = program
+568        retd['version'] = version
+569        retd['who'] = file_origin['who']
+570        retd['date'] = file_origin['date']
+571        retd['host'] = file_origin['host']
+572        retd['description'] = descriptiond
+573        retd['enstags'] = enstags
+574        retd['obsdata'] = res
+575        return retd
+576    else:
+577        return res
 
@@ -1625,51 +1634,51 @@ Imported data and meta-data
-
576def read_dobs(fname, full_output=False, gz=True, separator_insertion=True):
-577    """Import a list of Obs from an xml.gz file in the Zeuthen dobs format.
-578
-579    Tags are not written or recovered automatically.
-580
-581    Parameters
-582    ----------
-583    fname : str
-584        Filename of the input file.
-585    full_output : bool
-586        If True, a dict containing auxiliary information and the data is returned.
-587        If False, only the data is returned as list.
-588    gz : bool
-589        If True, assumes that data is gzipped. If False, assumes XML file.
-590    separatior_insertion: str, int or bool
-591        str: replace all occurences of "separator_insertion" within the replica names
-592        by "|%s" % (separator_insertion) when constructing the names of the replica.
-593        int: Insert the separator "|" at the position given by separator_insertion.
-594        True (default): separator "|" is inserted after len(ensname), assuming that the
-595        ensemble name is a prefix to the replica name.
-596        None or False: No separator is inserted.
-597
-598    Returns
-599    -------
-600    res : list[Obs]
-601        Imported data
-602    or
-603    res : dict
-604        Imported data and meta-data
-605    """
-606
-607    if not fname.endswith('.xml') and not fname.endswith('.gz'):
-608        fname += '.xml'
-609    if gz:
-610        if not fname.endswith('.gz'):
-611            fname += '.gz'
-612        with gzip.open(fname, 'r') as fin:
-613            content = fin.read()
-614    else:
-615        if fname.endswith('.gz'):
-616            warnings.warn("Trying to read from %s without unzipping!" % fname, UserWarning)
-617        with open(fname, 'r') as fin:
-618            content = fin.read()
-619
-620    return import_dobs_string(content, full_output, separator_insertion=separator_insertion)
+            
580def read_dobs(fname, full_output=False, gz=True, separator_insertion=True):
+581    """Import a list of Obs from an xml.gz file in the Zeuthen dobs format.
+582
+583    Tags are not written or recovered automatically.
+584
+585    Parameters
+586    ----------
+587    fname : str
+588        Filename of the input file.
+589    full_output : bool
+590        If True, a dict containing auxiliary information and the data is returned.
+591        If False, only the data is returned as list.
+592    gz : bool
+593        If True, assumes that data is gzipped. If False, assumes XML file.
+594    separatior_insertion: str, int or bool
+595        str: replace all occurences of "separator_insertion" within the replica names
+596        by "|%s" % (separator_insertion) when constructing the names of the replica.
+597        int: Insert the separator "|" at the position given by separator_insertion.
+598        True (default): separator "|" is inserted after len(ensname), assuming that the
+599        ensemble name is a prefix to the replica name.
+600        None or False: No separator is inserted.
+601
+602    Returns
+603    -------
+604    res : list[Obs]
+605        Imported data
+606    or
+607    res : dict
+608        Imported data and meta-data
+609    """
+610
+611    if not fname.endswith('.xml') and not fname.endswith('.gz'):
+612        fname += '.xml'
+613    if gz:
+614        if not fname.endswith('.gz'):
+615            fname += '.gz'
+616        with gzip.open(fname, 'r') as fin:
+617            content = fin.read()
+618    else:
+619        if fname.endswith('.gz'):
+620            warnings.warn(f"Trying to read from {fname} without unzipping!", UserWarning, stacklevel=2)
+621        with open(fname) as fin:
+622            content = fin.read()
+623
+624    return import_dobs_string(content, full_output, separator_insertion=separator_insertion)
 
@@ -1714,199 +1723,201 @@ Imported data and meta-data
def - create_dobs_string( obsl, name, spec='dobs v1.0', origin='', symbol=[], who=None, enstags=None): + create_dobs_string( obsl, name, spec='dobs v1.0', origin='', symbol=None, who=None, enstags=None):
-
682def create_dobs_string(obsl, name, spec='dobs v1.0', origin='', symbol=[], who=None, enstags=None):
-683    """Generate the string for the export of a list of Obs or structures containing Obs
-684    to a .xml.gz file according to the Zeuthen dobs format.
-685
-686    Tags are not written or recovered automatically. The separator |is removed from the replica names.
-687
-688    Parameters
-689    ----------
-690    obsl : list
-691        List of Obs that will be exported.
-692        The Obs inside a structure do not have to be defined on the same set of configurations,
-693        but the storage requirement is increased, if this is not the case.
-694    name : str
-695        The name of the observable.
-696    spec : str
-697        Optional string that describes the contents of the file.
-698    origin : str
-699        Specify where the data has its origin.
-700    symbol : list
-701        A list of symbols that describe the observables to be written. May be empty.
-702    who : str
-703        Provide the name of the person that exports the data.
-704    enstags : dict
-705        Provide alternative enstag for ensembles in the form enstags = {ename: enstag}
-706        Otherwise, the ensemble name is used.
-707
-708    Returns
-709    -------
-710    xml_str : str
-711        XML string generated from the data
-712    """
-713    if enstags is None:
-714        enstags = {}
-715    od = {}
-716    r_names = []
-717    for o in obsl:
-718        r_names += [name for name in o.names if name.split('|')[0] in o.mc_names]
-719    r_names = sorted(set(r_names))
-720    mc_names = sorted(set([n.split('|')[0] for n in r_names]))
-721    for tmpname in mc_names:
-722        if tmpname not in enstags:
-723            enstags[tmpname] = tmpname
-724    ne = len(set(mc_names))
-725    cov_names = []
-726    for o in obsl:
-727        cov_names += list(o.cov_names)
-728    cov_names = sorted(set(cov_names))
-729    nc = len(set(cov_names))
-730    od['OBSERVABLES'] = {}
-731    od['OBSERVABLES']['SCHEMA'] = {'NAME': 'lattobs', 'VERSION': '1.0'}
-732    if who is None:
-733        who = getpass.getuser()
-734    od['OBSERVABLES']['origin'] = {
-735        'who': who,
-736        'date': str(datetime.datetime.now())[:-7],
-737        'host': socket.gethostname(),
-738        'tool': {'name': 'pyerrors', 'version': pyerrorsversion.__version__}}
-739    od['OBSERVABLES']['dobs'] = {}
-740    pd = od['OBSERVABLES']['dobs']
-741    pd['spec'] = spec
-742    pd['origin'] = origin
-743    pd['name'] = name
-744    pd['array'] = {}
-745    pd['array']['id'] = 'val'
-746    pd['array']['layout'] = '1 f%d' % (len(obsl))
-747    osymbol = ''
-748    if symbol:
-749        if not isinstance(symbol, list):
-750            raise Exception('Symbol has to be a list!')
-751        if not (len(symbol) == 0 or len(symbol) == len(obsl)):
-752            raise Exception('Symbol has to be a list of lenght 0 or %d!' % (len(obsl)))
-753        osymbol = symbol[0]
-754        for s in symbol[1:]:
-755            osymbol += ' %s' % s
-756        pd['array']['symbol'] = osymbol
-757
-758    pd['array']['#values'] = ['  '.join(['%1.16e' % o.value for o in obsl])]
-759    pd['ne'] = '%d' % (ne)
-760    pd['nc'] = '%d' % (nc)
-761    pd['edata'] = []
-762    for name in mc_names:
-763        ed = {}
-764        ed['enstag'] = enstags[name]
-765        onames = sorted([n for n in r_names if (n.startswith(name + '|') or n == name)])
-766        nr = len(onames)
-767        ed['nr'] = nr
-768        ed[''] = []
-769
-770        for r in range(nr):
-771            ad = {}
-772            repname = onames[r]
-773            ad['id'] = repname.replace('|', '')
-774            idx = _merge_idx([o.idl.get(repname, []) for o in obsl])
-775            Nconf = len(idx)
-776            layout = '%d i f%d' % (Nconf, len(obsl))
-777            ad['layout'] = layout
-778            data = ''
-779            counters = [0 for o in obsl]
-780            offsets = [o.r_values[repname] - o.value if repname in o.r_values else 0 for o in obsl]
-781            for ci in idx:
-782                data += '%d ' % ci
-783                for oi in range(len(obsl)):
-784                    o = obsl[oi]
-785                    if repname in o.idl:
-786                        if counters[oi] < 0:
-787                            num = 0
-788                            if num == 0:
-789                                data += '0 '
-790                            else:
-791                                data += '%1.16e ' % (num)
-792                            continue
-793                        if o.idl[repname][counters[oi]] == ci:
-794                            num = o.deltas[repname][counters[oi]] + offsets[oi]
-795                            if num == 0:
-796                                data += '0 '
-797                            else:
-798                                data += '%1.16e ' % (num)
-799                            counters[oi] += 1
-800                            if counters[oi] >= len(o.idl[repname]):
-801                                counters[oi] = -1
-802                        else:
-803                            num = 0
-804                            if num == 0:
-805                                data += '0 '
-806                            else:
-807                                data += '%1.16e ' % (num)
-808                    else:
-809                        data += '0 '
-810                data += '\n'
-811            ad['#data'] = data
-812            ed[''].append(ad)
-813        pd['edata'].append(ed)
-814
-815        allcov = {}
-816        for o in obsl:
-817            for cname in o.cov_names:
-818                if cname in allcov:
-819                    if not np.array_equal(allcov[cname], o.covobs[cname].cov):
-820                        raise Exception('Inconsistent covariance matrices for %s!' % (cname))
-821                else:
-822                    allcov[cname] = o.covobs[cname].cov
-823        pd['cdata'] = []
-824        for cname in cov_names:
-825            cd = {}
-826            cd['id'] = cname
-827
-828            covd = {'id': 'cov'}
-829            if allcov[cname].shape == ():
-830                ncov = 1
-831                covd['layout'] = '1 1 f'
-832                covd['#data'] = '%1.14e' % (allcov[cname])
-833            else:
-834                shape = allcov[cname].shape
-835                assert (shape[0] == shape[1])
-836                ncov = shape[0]
-837                covd['layout'] = '%d %d f' % (ncov, ncov)
-838                ds = ''
-839                for i in range(ncov):
-840                    for j in range(ncov):
-841                        val = allcov[cname][i][j]
-842                        if val == 0:
-843                            ds += '0 '
-844                        else:
-845                            ds += '%1.14e ' % (val)
-846                    ds += '\n'
-847                covd['#data'] = ds
-848
-849            gradd = {'id': 'grad'}
-850            gradd['layout'] = '%d f%d' % (ncov, len(obsl))
-851            ds = ''
-852            for i in range(ncov):
-853                for o in obsl:
-854                    if cname in o.covobs:
-855                        val = o.covobs[cname].grad[i].item()
-856                        if val != 0:
-857                            ds += '%1.14e ' % (val)
-858                        else:
-859                            ds += '0 '
-860                    else:
-861                        ds += '0 '
-862            gradd['#data'] = ds
-863            cd['array'] = [covd, gradd]
-864            pd['cdata'].append(cd)
-865
-866    rs = '<?xml version="1.0" encoding="utf-8"?>\n' + _dobsdict_to_xmlstring_spaces(od)
-867
-868    return rs
+            
686def create_dobs_string(obsl, name, spec='dobs v1.0', origin='', symbol=None, who=None, enstags=None):
+687    """Generate the string for the export of a list of Obs or structures containing Obs
+688    to a .xml.gz file according to the Zeuthen dobs format.
+689
+690    Tags are not written or recovered automatically. The separator |is removed from the replica names.
+691
+692    Parameters
+693    ----------
+694    obsl : list
+695        List of Obs that will be exported.
+696        The Obs inside a structure do not have to be defined on the same set of configurations,
+697        but the storage requirement is increased, if this is not the case.
+698    name : str
+699        The name of the observable.
+700    spec : str
+701        Optional string that describes the contents of the file.
+702    origin : str
+703        Specify where the data has its origin.
+704    symbol : list
+705        A list of symbols that describe the observables to be written. May be empty.
+706    who : str
+707        Provide the name of the person that exports the data.
+708    enstags : dict
+709        Provide alternative enstag for ensembles in the form enstags = {ename: enstag}
+710        Otherwise, the ensemble name is used.
+711
+712    Returns
+713    -------
+714    xml_str : str
+715        XML string generated from the data
+716    """
+717    if enstags is None:
+718        enstags = {}
+719    if symbol is None:
+720        symbol = []
+721    od = {}
+722    r_names = []
+723    for o in obsl:
+724        r_names += [name for name in o.names if name.split('|')[0] in o.mc_names]
+725    r_names = sorted(set(r_names))
+726    mc_names = sorted(set([n.split('|')[0] for n in r_names]))
+727    for tmpname in mc_names:
+728        if tmpname not in enstags:
+729            enstags[tmpname] = tmpname
+730    ne = len(set(mc_names))
+731    cov_names = []
+732    for o in obsl:
+733        cov_names += list(o.cov_names)
+734    cov_names = sorted(set(cov_names))
+735    nc = len(set(cov_names))
+736    od['OBSERVABLES'] = {}
+737    od['OBSERVABLES']['SCHEMA'] = {'NAME': 'lattobs', 'VERSION': '1.0'}
+738    if who is None:
+739        who = getpass.getuser()
+740    od['OBSERVABLES']['origin'] = {
+741        'who': who,
+742        'date': str(datetime.datetime.now())[:-7],
+743        'host': socket.gethostname(),
+744        'tool': {'name': 'pyerrors', 'version': pyerrorsversion.__version__}}
+745    od['OBSERVABLES']['dobs'] = {}
+746    pd = od['OBSERVABLES']['dobs']
+747    pd['spec'] = spec
+748    pd['origin'] = origin
+749    pd['name'] = name
+750    pd['array'] = {}
+751    pd['array']['id'] = 'val'
+752    pd['array']['layout'] = f'1 f{len(obsl)}'
+753    osymbol = ''
+754    if symbol:
+755        if not isinstance(symbol, list):
+756            raise Exception('Symbol has to be a list!')
+757        if not (len(symbol) == 0 or len(symbol) == len(obsl)):
+758            raise Exception(f'Symbol has to be a list of lenght 0 or {len(obsl)}!')
+759        osymbol = symbol[0]
+760        for s in symbol[1:]:
+761            osymbol += f' {s}'
+762        pd['array']['symbol'] = osymbol
+763
+764    pd['array']['#values'] = ['  '.join([f'{o.value:1.16e}' for o in obsl])]
+765    pd['ne'] = f'{ne}'
+766    pd['nc'] = f'{nc}'
+767    pd['edata'] = []
+768    for name in mc_names:
+769        ed = {}
+770        ed['enstag'] = enstags[name]
+771        onames = sorted([n for n in r_names if (n.startswith(name + '|') or n == name)])
+772        nr = len(onames)
+773        ed['nr'] = nr
+774        ed[''] = []
+775
+776        for r in range(nr):
+777            ad = {}
+778            repname = onames[r]
+779            ad['id'] = repname.replace('|', '')
+780            idx = _merge_idx([o.idl.get(repname, []) for o in obsl])
+781            Nconf = len(idx)
+782            layout = f'{Nconf} i f{len(obsl)}'
+783            ad['layout'] = layout
+784            data = ''
+785            counters = [0 for o in obsl]
+786            offsets = [o.r_values[repname] - o.value if repname in o.r_values else 0 for o in obsl]
+787            for ci in idx:
+788                data += f'{ci} '
+789                for oi in range(len(obsl)):
+790                    o = obsl[oi]
+791                    if repname in o.idl:
+792                        if counters[oi] < 0:
+793                            num = 0
+794                            if num == 0:
+795                                data += '0 '
+796                            else:
+797                                data += f'{num:1.16e} '
+798                            continue
+799                        if o.idl[repname][counters[oi]] == ci:
+800                            num = o.deltas[repname][counters[oi]] + offsets[oi]
+801                            if num == 0:
+802                                data += '0 '
+803                            else:
+804                                data += f'{num:1.16e} '
+805                            counters[oi] += 1
+806                            if counters[oi] >= len(o.idl[repname]):
+807                                counters[oi] = -1
+808                        else:
+809                            num = 0
+810                            if num == 0:
+811                                data += '0 '
+812                            else:
+813                                data += f'{num:1.16e} '
+814                    else:
+815                        data += '0 '
+816                data += '\n'
+817            ad['#data'] = data
+818            ed[''].append(ad)
+819        pd['edata'].append(ed)
+820
+821        allcov = {}
+822        for o in obsl:
+823            for cname in o.cov_names:
+824                if cname in allcov:
+825                    if not np.array_equal(allcov[cname], o.covobs[cname].cov):
+826                        raise Exception(f'Inconsistent covariance matrices for {cname}!')
+827                else:
+828                    allcov[cname] = o.covobs[cname].cov
+829        pd['cdata'] = []
+830        for cname in cov_names:
+831            cd = {}
+832            cd['id'] = cname
+833
+834            covd = {'id': 'cov'}
+835            if allcov[cname].shape == ():
+836                ncov = 1
+837                covd['layout'] = '1 1 f'
+838                covd['#data'] = f'{allcov[cname]:1.14e}'
+839            else:
+840                shape = allcov[cname].shape
+841                assert (shape[0] == shape[1])
+842                ncov = shape[0]
+843                covd['layout'] = f'{ncov} {ncov} f'
+844                ds = ''
+845                for i in range(ncov):
+846                    for j in range(ncov):
+847                        val = allcov[cname][i][j]
+848                        if val == 0:
+849                            ds += '0 '
+850                        else:
+851                            ds += f'{val:1.14e} '
+852                    ds += '\n'
+853                covd['#data'] = ds
+854
+855            gradd = {'id': 'grad'}
+856            gradd['layout'] = f'{ncov} f{len(obsl)}'
+857            ds = ''
+858            for i in range(ncov):
+859                for o in obsl:
+860                    if cname in o.covobs:
+861                        val = o.covobs[cname].grad[i].item()
+862                        if val != 0:
+863                            ds += f'{val:1.14e} '
+864                        else:
+865                            ds += '0 '
+866                    else:
+867                        ds += '0 '
+868            gradd['#data'] = ds
+869            cd['array'] = [covd, gradd]
+870            pd['cdata'].append(cd)
+871
+872    rs = '<?xml version="1.0" encoding="utf-8"?>\n' + _dobsdict_to_xmlstring_spaces(od)
+873
+874    return rs
 
@@ -1952,64 +1963,64 @@ XML string generated from the data
def - write_dobs( obsl, fname, name, spec='dobs v1.0', origin='', symbol=[], who=None, enstags=None, gz=True): + write_dobs( obsl, fname, name, spec='dobs v1.0', origin='', symbol=None, who=None, enstags=None, gz=True):
-
871def write_dobs(obsl, fname, name, spec='dobs v1.0', origin='', symbol=[], who=None, enstags=None, gz=True):
-872    """Export a list of Obs or structures containing Obs to a .xml.gz file
-873    according to the Zeuthen dobs format.
-874
-875    Tags are not written or recovered automatically. The separator | is removed from the replica names.
-876
-877    Parameters
-878    ----------
-879    obsl : list
-880        List of Obs that will be exported.
-881        The Obs inside a structure do not have to be defined on the same set of configurations,
-882        but the storage requirement is increased, if this is not the case.
-883    fname : str
-884        Filename of the output file.
-885    name : str
-886        The name of the observable.
-887    spec : str
-888        Optional string that describes the contents of the file.
-889    origin : str
-890        Specify where the data has its origin.
-891    symbol : list
-892        A list of symbols that describe the observables to be written. May be empty.
-893    who : str
-894        Provide the name of the person that exports the data.
-895    enstags : dict
-896        Provide alternative enstag for ensembles in the form enstags = {ename: enstag}
-897        Otherwise, the ensemble name is used.
-898    gz : bool
-899        If True, the output is a gzipped XML. If False, the output is a XML file.
-900
-901    Returns
-902    -------
-903    None
-904    """
-905    if enstags is None:
-906        enstags = {}
-907
-908    dobsstring = create_dobs_string(obsl, name, spec, origin, symbol, who, enstags=enstags)
-909
-910    if not fname.endswith('.xml') and not fname.endswith('.gz'):
-911        fname += '.xml'
-912
-913    if gz:
-914        if not fname.endswith('.gz'):
-915            fname += '.gz'
-916
-917        fp = gzip.open(fname, 'wb')
-918        fp.write(dobsstring.encode('utf-8'))
-919    else:
-920        fp = open(fname, 'w', encoding='utf-8')
-921        fp.write(dobsstring)
-922    fp.close()
+            
877def write_dobs(obsl, fname, name, spec='dobs v1.0', origin='', symbol=None, who=None, enstags=None, gz=True):
+878    """Export a list of Obs or structures containing Obs to a .xml.gz file
+879    according to the Zeuthen dobs format.
+880
+881    Tags are not written or recovered automatically. The separator | is removed from the replica names.
+882
+883    Parameters
+884    ----------
+885    obsl : list
+886        List of Obs that will be exported.
+887        The Obs inside a structure do not have to be defined on the same set of configurations,
+888        but the storage requirement is increased, if this is not the case.
+889    fname : str
+890        Filename of the output file.
+891    name : str
+892        The name of the observable.
+893    spec : str
+894        Optional string that describes the contents of the file.
+895    origin : str
+896        Specify where the data has its origin.
+897    symbol : list
+898        A list of symbols that describe the observables to be written. May be empty.
+899    who : str
+900        Provide the name of the person that exports the data.
+901    enstags : dict
+902        Provide alternative enstag for ensembles in the form enstags = {ename: enstag}
+903        Otherwise, the ensemble name is used.
+904    gz : bool
+905        If True, the output is a gzipped XML. If False, the output is a XML file.
+906
+907    Returns
+908    -------
+909    None
+910    """
+911    if enstags is None:
+912        enstags = {}
+913
+914    dobsstring = create_dobs_string(obsl, name, spec, origin, symbol, who, enstags=enstags)
+915
+916    if not fname.endswith('.xml') and not fname.endswith('.gz'):
+917        fname += '.xml'
+918
+919    if gz:
+920        if not fname.endswith('.gz'):
+921            fname += '.gz'
+922
+923        fp = gzip.open(fname, 'wb')
+924        fp.write(dobsstring.encode('utf-8'))
+925    else:
+926        fp = open(fname, 'w', encoding='utf-8')
+927        fp.write(dobsstring)
+928    fp.close()
 
diff --git a/docs/pyerrors/input/hadrons.html b/docs/pyerrors/input/hadrons.html index eac00fff..095defa3 100644 --- a/docs/pyerrors/input/hadrons.html +++ b/docs/pyerrors/input/hadrons.html @@ -105,619 +105,627 @@
  1import os
   2from collections import Counter
-  3import h5py
-  4from pathlib import Path
-  5import numpy as np
-  6from ..obs import Obs, CObs
-  7from ..correlators import Corr
-  8from ..dirac import epsilon_tensor_rank4
-  9from .misc import fit_t0
- 10
- 11
- 12def _get_files(path, filestem, idl):
- 13    ls = os.listdir(path)
- 14
- 15    # Clean up file list
- 16    files = list(filter(lambda x: x.startswith(filestem + "."), ls))
- 17
- 18    if not files:
- 19        raise Exception('No files starting with', filestem, 'in folder', path)
- 20
- 21    def get_cnfg_number(n):
- 22        return int(n.replace(".h5", "")[len(filestem) + 1:])  # From python 3.9 onward the safer 'removesuffix' method can be used.
- 23
- 24    # Sort according to configuration number
- 25    files.sort(key=get_cnfg_number)
- 26
- 27    cnfg_numbers = []
- 28    filtered_files = []
- 29    for line in files:
- 30        no = get_cnfg_number(line)
- 31        if idl:
- 32            if no in list(idl):
- 33                filtered_files.append(line)
- 34                cnfg_numbers.append(no)
- 35        else:
- 36            filtered_files.append(line)
- 37            cnfg_numbers.append(no)
- 38
- 39    if idl:
- 40        if Counter(list(idl)) != Counter(cnfg_numbers):
- 41            raise Exception("Not all configurations specified in idl found, configurations " + str(list(Counter(list(idl)) - Counter(cnfg_numbers))) + " are missing.")
- 42
- 43    # Check that configurations are evenly spaced
- 44    dc = np.unique(np.diff(cnfg_numbers))
- 45    if np.any(dc < 0):
- 46        raise Exception("Unsorted files")
- 47    if len(dc) == 1:
- 48        idx = range(cnfg_numbers[0], cnfg_numbers[-1] + dc[0], dc[0])
- 49    elif idl:
- 50        idx = idl
- 51    else:
- 52        raise Exception("Configurations are not evenly spaced. Provide an idl if you want to proceed with this set of configurations.")
- 53
- 54    return filtered_files, idx
+  3from pathlib import Path
+  4
+  5import h5py
+  6import numpy as np
+  7
+  8from ..correlators import Corr
+  9from ..dirac import epsilon_tensor_rank4
+ 10from ..obs import CObs, Obs
+ 11from .misc import fit_t0
+ 12
+ 13
+ 14def _get_files(path, filestem, idl):
+ 15    ls = os.listdir(path)
+ 16
+ 17    # Clean up file list
+ 18    files = list(filter(lambda x: x.startswith(filestem + "."), ls))
+ 19
+ 20    if not files:
+ 21        raise Exception('No files starting with', filestem, 'in folder', path)
+ 22
+ 23    def get_cnfg_number(n):
+ 24        return int(n.replace(".h5", "")[len(filestem) + 1:])  # From python 3.9 onward the safer 'removesuffix' method can be used.
+ 25
+ 26    # Sort according to configuration number
+ 27    files.sort(key=get_cnfg_number)
+ 28
+ 29    cnfg_numbers = []
+ 30    filtered_files = []
+ 31    for line in files:
+ 32        no = get_cnfg_number(line)
+ 33        if idl:
+ 34            if no in list(idl):
+ 35                filtered_files.append(line)
+ 36                cnfg_numbers.append(no)
+ 37        else:
+ 38            filtered_files.append(line)
+ 39            cnfg_numbers.append(no)
+ 40
+ 41    if idl:
+ 42        if Counter(list(idl)) != Counter(cnfg_numbers):
+ 43            raise Exception("Not all configurations specified in idl found, configurations " + str(list(Counter(list(idl)) - Counter(cnfg_numbers))) + " are missing.")
+ 44
+ 45    # Check that configurations are evenly spaced
+ 46    dc = np.unique(np.diff(cnfg_numbers))
+ 47    if np.any(dc < 0):
+ 48        raise Exception("Unsorted files")
+ 49    if len(dc) == 1:
+ 50        idx = range(cnfg_numbers[0], cnfg_numbers[-1] + dc[0], dc[0])
+ 51    elif idl:
+ 52        idx = idl
+ 53    else:
+ 54        raise Exception("Configurations are not evenly spaced. Provide an idl if you want to proceed with this set of configurations.")
  55
- 56
- 57def read_hd5(filestem, ens_id, group, attrs=None, idl=None, part="real"):
- 58    r'''Read hadrons hdf5 file and extract entry based on attributes.
- 59
- 60    Parameters
- 61    -----------------
- 62    filestem : str
- 63        Full namestem of the files to read, including the full path.
- 64    ens_id : str
- 65        name of the ensemble, required for internal bookkeeping
- 66    group : str
- 67        label of the group to be extracted.
- 68    attrs : dict or int
- 69        Dictionary containing the attributes. For example
- 70        ```python
- 71        attrs = {"gamma_snk": "Gamma5",
- 72                 "gamma_src": "Gamma5"}
- 73         ```
- 74        Alternatively an integer can be specified to identify the sub group.
- 75        This is discouraged as the order in the file is not guaranteed.
- 76    idl : range
- 77        If specified only configurations in the given range are read in.
- 78    part: str
- 79        string specifying whether to extract the real part ('real'),
- 80        the imaginary part ('imag') or a complex correlator ('complex').
- 81        Default 'real'.
- 82
- 83    Returns
- 84    -------
- 85    corr : Corr
- 86        Correlator of the source sink combination in question.
- 87    '''
- 88
- 89    path_obj = Path(filestem)
- 90    path = path_obj.parent.as_posix()
- 91    filestem = path_obj.name
- 92
- 93    files, idx = _get_files(path, filestem, idl)
+ 56    return filtered_files, idx
+ 57
+ 58
+ 59def read_hd5(filestem, ens_id, group, attrs=None, idl=None, part="real"):
+ 60    r'''Read hadrons hdf5 file and extract entry based on attributes.
+ 61
+ 62    Parameters
+ 63    -----------------
+ 64    filestem : str
+ 65        Full namestem of the files to read, including the full path.
+ 66    ens_id : str
+ 67        name of the ensemble, required for internal bookkeeping
+ 68    group : str
+ 69        label of the group to be extracted.
+ 70    attrs : dict or int
+ 71        Dictionary containing the attributes. For example
+ 72        ```python
+ 73        attrs = {"gamma_snk": "Gamma5",
+ 74                 "gamma_src": "Gamma5"}
+ 75         ```
+ 76        Alternatively an integer can be specified to identify the sub group.
+ 77        This is discouraged as the order in the file is not guaranteed.
+ 78    idl : range
+ 79        If specified only configurations in the given range are read in.
+ 80    part: str
+ 81        string specifying whether to extract the real part ('real'),
+ 82        the imaginary part ('imag') or a complex correlator ('complex').
+ 83        Default 'real'.
+ 84
+ 85    Returns
+ 86    -------
+ 87    corr : Corr
+ 88        Correlator of the source sink combination in question.
+ 89    '''
+ 90
+ 91    path_obj = Path(filestem)
+ 92    path = path_obj.parent.as_posix()
+ 93    filestem = path_obj.name
  94
- 95    if isinstance(attrs, dict):
- 96        h5file = h5py.File(path + '/' + files[0], "r")
- 97        entry = None
- 98        for key in h5file[group].keys():
- 99            if attrs.items() <= {k: v[0].decode() for k, v in h5file[group][key].attrs.items()}.items():
-100                if entry is None:
-101                    entry = key
-102                else:
-103                    raise ValueError("More than one fitting entry found. More constraint on attributes needed.")
-104        h5file.close()
-105        if entry is None:
-106            raise ValueError(f"Entry with attributes {attrs} not found.")
-107    elif isinstance(attrs, int):
-108        entry = group + f"_{attrs}"
-109    else:
-110        raise TypeError("Invalid type for 'attrs'. Needs to be dict or int.")
-111
-112    corr_data = []
-113    infos = []
-114    for hd5_file in files:
-115        h5file = h5py.File(path + '/' + hd5_file, "r")
-116        if group + '/' + entry not in h5file:
-117            raise Exception("Entry '" + entry + "' not contained in the files.")
-118        raw_data = h5file[group + '/' + entry + '/corr']
-119        real_data = raw_data[:].view("complex")
-120        corr_data.append(real_data)
-121        if not infos:
-122            for k, i in h5file[group + '/' + entry].attrs.items():
-123                infos.append(k + ': ' + i[0].decode())
-124        h5file.close()
-125    corr_data = np.array(corr_data)
-126
-127    if part == "complex":
-128        l_obs = []
-129        for c in corr_data.T:
-130            l_obs.append(CObs(Obs([c.real], [ens_id], idl=[idx]),
-131                              Obs([c.imag], [ens_id], idl=[idx])))
-132    else:
-133        corr_data = getattr(corr_data, part)
-134        l_obs = []
-135        for c in corr_data.T:
-136            l_obs.append(Obs([c], [ens_id], idl=[idx]))
-137
-138    corr = Corr(l_obs)
-139    corr.tag = r", ".join(infos)
-140    return corr
-141
-142
-143def read_meson_hd5(path, filestem, ens_id, meson='meson_0', idl=None, gammas=None):
-144    r'''Read hadrons meson hdf5 file and extract the meson labeled 'meson'
-145
-146    Parameters
-147    -----------------
-148    path : str
-149        path to the files to read
-150    filestem : str
-151        namestem of the files to read
-152    ens_id : str
-153        name of the ensemble, required for internal bookkeeping
-154    meson : str
-155        label of the meson to be extracted, standard value meson_0 which
-156        corresponds to the pseudoscalar pseudoscalar two-point function.
-157    gammas : tuple of strings
-158        Instrad of a meson label one can also provide a tuple of two strings
-159        indicating the gamma matrices at sink and source (gamma_snk, gamma_src).
-160        ("Gamma5", "Gamma5") corresponds to the pseudoscalar pseudoscalar
-161        two-point function. The gammas argument dominateds over meson.
-162    idl : range
-163        If specified only configurations in the given range are read in.
-164
-165    Returns
-166    -------
-167    corr : Corr
-168        Correlator of the source sink combination in question.
-169    '''
-170    if gammas is None:
-171        attrs = int(meson.rsplit('_', 1)[-1])
-172    else:
-173        if len(gammas) != 2:
-174            raise ValueError("'gammas' needs to have exactly two entries")
-175        attrs = {"gamma_snk": gammas[0],
-176                 "gamma_src": gammas[1]}
-177    return read_hd5(filestem=path + "/" + filestem, ens_id=ens_id,
-178                    group=meson.rsplit('_', 1)[0], attrs=attrs, idl=idl,
-179                    part="real")
-180
-181
-182def _extract_real_arrays(path, files, tree, keys):
-183    corr_data = {}
-184    for key in keys:
-185        corr_data[key] = []
-186    for hd5_file in files:
-187        h5file = h5py.File(path + '/' + hd5_file, "r")
-188        for key in keys:
-189            if tree + '/' + key not in h5file:
-190                raise Exception("Entry '" + key + "' not contained in the files.")
-191            raw_data = h5file[tree + '/' + key + '/data']
-192            real_data = raw_data[:].astype(np.double)
-193            corr_data[key].append(real_data)
-194        h5file.close()
-195    for key in keys:
-196        corr_data[key] = np.array(corr_data[key])
-197    return corr_data
-198
-199
-200def extract_t0_hd5(path, filestem, ens_id, obs='Clover energy density', fit_range=5, idl=None, **kwargs):
-201    r'''Read hadrons FlowObservables hdf5 file and extract t0
-202
-203    Parameters
-204    -----------------
-205    path : str
-206        path to the files to read
-207    filestem : str
-208        namestem of the files to read
-209    ens_id : str
-210        name of the ensemble, required for internal bookkeeping
-211    obs : str
-212        label of the observable from which t0 should be extracted.
-213        Options: 'Clover energy density' and 'Plaquette energy density'
-214    fit_range : int
-215        Number of data points left and right of the zero
-216        crossing to be included in the linear fit. (Default: 5)
-217    idl : range
-218        If specified only configurations in the given range are read in.
-219    plot_fit : bool
-220        If true, the fit for the extraction of t0 is shown together with the data.
-221    '''
-222
-223    files, idx = _get_files(path, filestem, idl)
-224    tree = "FlowObservables"
-225
-226    h5file = h5py.File(path + '/' + files[0], "r")
-227    obs_key = None
-228    for key in h5file[tree].keys():
-229        if obs == h5file[tree][key].attrs["description"][0].decode():
-230            obs_key = key
-231            break
-232    h5file.close()
-233    if obs_key is None:
-234        raise Exception(f"Observable {obs} not found.")
-235
-236    corr_data = _extract_real_arrays(path, files, tree, ["FlowObservables_0", obs_key])
+ 95    files, idx = _get_files(path, filestem, idl)
+ 96
+ 97    if isinstance(attrs, dict):
+ 98        h5file = h5py.File(path + '/' + files[0], "r")
+ 99        entry = None
+100        for key in h5file[group].keys():
+101            if attrs.items() <= {k: v[0].decode() for k, v in h5file[group][key].attrs.items()}.items():
+102                if entry is None:
+103                    entry = key
+104                else:
+105                    raise ValueError("More than one fitting entry found. More constraint on attributes needed.")
+106        h5file.close()
+107        if entry is None:
+108            raise ValueError(f"Entry with attributes {attrs} not found.")
+109    elif isinstance(attrs, int):
+110        entry = group + f"_{attrs}"
+111    else:
+112        raise TypeError("Invalid type for 'attrs'. Needs to be dict or int.")
+113
+114    corr_data = []
+115    infos = []
+116    for hd5_file in files:
+117        h5file = h5py.File(path + '/' + hd5_file, "r")
+118        if group + '/' + entry not in h5file:
+119            raise Exception("Entry '" + entry + "' not contained in the files.")
+120        raw_data = h5file[group + '/' + entry + '/corr']
+121        real_data = raw_data[:].view("complex")
+122        corr_data.append(real_data)
+123        if not infos:
+124            for k, i in h5file[group + '/' + entry].attrs.items():
+125                infos.append(k + ': ' + i[0].decode())
+126        h5file.close()
+127    corr_data = np.array(corr_data)
+128
+129    if part == "complex":
+130        l_obs = []
+131        for c in corr_data.T:
+132            l_obs.append(CObs(Obs([c.real], [ens_id], idl=[idx]),
+133                              Obs([c.imag], [ens_id], idl=[idx])))
+134    else:
+135        corr_data = getattr(corr_data, part)
+136        l_obs = []
+137        for c in corr_data.T:
+138            l_obs.append(Obs([c], [ens_id], idl=[idx]))
+139
+140    corr = Corr(l_obs)
+141    corr.tag = r", ".join(infos)
+142    return corr
+143
+144
+145def read_meson_hd5(path, filestem, ens_id, meson='meson_0', idl=None, gammas=None):
+146    r'''Read hadrons meson hdf5 file and extract the meson labeled 'meson'
+147
+148    Parameters
+149    -----------------
+150    path : str
+151        path to the files to read
+152    filestem : str
+153        namestem of the files to read
+154    ens_id : str
+155        name of the ensemble, required for internal bookkeeping
+156    meson : str
+157        label of the meson to be extracted, standard value meson_0 which
+158        corresponds to the pseudoscalar pseudoscalar two-point function.
+159    gammas : tuple of strings
+160        Instrad of a meson label one can also provide a tuple of two strings
+161        indicating the gamma matrices at sink and source (gamma_snk, gamma_src).
+162        ("Gamma5", "Gamma5") corresponds to the pseudoscalar pseudoscalar
+163        two-point function. The gammas argument dominateds over meson.
+164    idl : range
+165        If specified only configurations in the given range are read in.
+166
+167    Returns
+168    -------
+169    corr : Corr
+170        Correlator of the source sink combination in question.
+171    '''
+172    if gammas is None:
+173        attrs = int(meson.rsplit('_', 1)[-1])
+174    else:
+175        if len(gammas) != 2:
+176            raise ValueError("'gammas' needs to have exactly two entries")
+177        attrs = {"gamma_snk": gammas[0],
+178                 "gamma_src": gammas[1]}
+179    return read_hd5(filestem=path + "/" + filestem, ens_id=ens_id,
+180                    group=meson.rsplit('_', 1)[0], attrs=attrs, idl=idl,
+181                    part="real")
+182
+183
+184def _extract_real_arrays(path, files, tree, keys):
+185    corr_data = {}
+186    for key in keys:
+187        corr_data[key] = []
+188    for hd5_file in files:
+189        h5file = h5py.File(path + '/' + hd5_file, "r")
+190        for key in keys:
+191            if tree + '/' + key not in h5file:
+192                raise Exception("Entry '" + key + "' not contained in the files.")
+193            raw_data = h5file[tree + '/' + key + '/data']
+194            real_data = raw_data[:].astype(np.double)
+195            corr_data[key].append(real_data)
+196        h5file.close()
+197    for key in keys:
+198        corr_data[key] = np.array(corr_data[key])
+199    return corr_data
+200
+201
+202def extract_t0_hd5(path, filestem, ens_id, obs='Clover energy density', fit_range=5, idl=None, **kwargs):
+203    r'''Read hadrons FlowObservables hdf5 file and extract t0
+204
+205    Parameters
+206    -----------------
+207    path : str
+208        path to the files to read
+209    filestem : str
+210        namestem of the files to read
+211    ens_id : str
+212        name of the ensemble, required for internal bookkeeping
+213    obs : str
+214        label of the observable from which t0 should be extracted.
+215        Options: 'Clover energy density' and 'Plaquette energy density'
+216    fit_range : int
+217        Number of data points left and right of the zero
+218        crossing to be included in the linear fit. (Default: 5)
+219    idl : range
+220        If specified only configurations in the given range are read in.
+221    plot_fit : bool
+222        If true, the fit for the extraction of t0 is shown together with the data.
+223    '''
+224
+225    files, idx = _get_files(path, filestem, idl)
+226    tree = "FlowObservables"
+227
+228    h5file = h5py.File(path + '/' + files[0], "r")
+229    obs_key = None
+230    for key in h5file[tree].keys():
+231        if obs == h5file[tree][key].attrs["description"][0].decode():
+232            obs_key = key
+233            break
+234    h5file.close()
+235    if obs_key is None:
+236        raise Exception(f"Observable {obs} not found.")
 237
-238    if not np.allclose(corr_data["FlowObservables_0"][0], corr_data["FlowObservables_0"][:]):
-239        raise Exception("Not all flow times were equal.")
-240
-241    t2E_dict = {}
-242    for t2, dat in zip(corr_data["FlowObservables_0"][0], corr_data[obs_key].T):
-243        t2E_dict[t2] = Obs([dat], [ens_id], idl=[idx]) - 0.3
-244
-245    return fit_t0(t2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'))
+238    corr_data = _extract_real_arrays(path, files, tree, ["FlowObservables_0", obs_key])
+239
+240    if not np.allclose(corr_data["FlowObservables_0"][0], corr_data["FlowObservables_0"][:]):
+241        raise Exception("Not all flow times were equal.")
+242
+243    t2E_dict = {}
+244    for t2, dat in zip(corr_data["FlowObservables_0"][0], corr_data[obs_key].T, strict=True):
+245        t2E_dict[t2] = Obs([dat], [ens_id], idl=[idx]) - 0.3
 246
-247
-248def read_DistillationContraction_hd5(path, ens_id, diagrams=["direct"], idl=None):
-249    """Read hadrons DistillationContraction hdf5 files in given directory structure
-250
-251    Parameters
-252    -----------------
-253    path : str
-254        path to the directories to read
-255    ens_id : str
-256        name of the ensemble, required for internal bookkeeping
-257    diagrams : list
-258        List of strings of the diagrams to extract, e.g. ["direct", "box", "cross"].
-259    idl : range
-260        If specified only configurations in the given range are read in.
-261
-262    Returns
-263    -------
-264    result : dict
-265        extracted DistillationContration data
-266    """
-267
-268    res_dict = {}
+247    return fit_t0(t2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'))
+248
+249
+250def read_DistillationContraction_hd5(path, ens_id, diagrams=None, idl=None):
+251    """Read hadrons DistillationContraction hdf5 files in given directory structure
+252
+253    Parameters
+254    -----------------
+255    path : str
+256        path to the directories to read
+257    ens_id : str
+258        name of the ensemble, required for internal bookkeeping
+259    diagrams : list
+260        List of strings of the diagrams to extract, e.g. ["direct", "box", "cross"].
+261    idl : range
+262        If specified only configurations in the given range are read in.
+263
+264    Returns
+265    -------
+266    result : dict
+267        extracted DistillationContration data
+268    """
 269
-270    directories, idx = _get_files(path, "data", idl)
-271
-272    explore_path = Path(path + "/" + directories[0])
-273
-274    for explore_file in explore_path.iterdir():
-275        if explore_file.is_file():
-276            stem = explore_file.with_suffix("").with_suffix("").as_posix().split("/")[-1]
-277        else:
-278            continue
-279
-280        file_list = []
-281        for dir in directories:
-282            tmp_path = Path(path + "/" + dir)
-283            file_list.append((tmp_path / stem).as_posix() + tmp_path.suffix + ".h5")
+270    if diagrams is None:
+271        diagrams = ["direct"]
+272
+273    res_dict = {}
+274
+275    directories, idx = _get_files(path, "data", idl)
+276
+277    explore_path = Path(path + "/" + directories[0])
+278
+279    for explore_file in explore_path.iterdir():
+280        if explore_file.is_file():
+281            stem = explore_file.with_suffix("").with_suffix("").as_posix().split("/")[-1]
+282        else:
+283            continue
 284
-285        corr_data = {}
-286
-287        for diagram in diagrams:
-288            corr_data[diagram] = []
+285        file_list = []
+286        for dir in directories:
+287            tmp_path = Path(path + "/" + dir)
+288            file_list.append((tmp_path / stem).as_posix() + tmp_path.suffix + ".h5")
 289
-290        try:
-291            for n_file, (hd5_file, n_traj) in enumerate(zip(file_list, list(idx))):
-292                h5file = h5py.File(hd5_file)
-293
-294                if n_file == 0:
-295                    if h5file["DistillationContraction/Metadata"].attrs.get("TimeSources")[0].decode() != "0...":
-296                        raise Exception("Routine is only implemented for files containing inversions on all timeslices.")
-297
-298                    Nt = h5file["DistillationContraction/Metadata"].attrs.get("Nt")[0]
-299
-300                    identifier = []
-301                    for in_file in range(len(h5file["DistillationContraction/Metadata/DmfInputFiles"].attrs.keys()) - 1):
-302                        encoded_info = h5file["DistillationContraction/Metadata/DmfInputFiles"].attrs.get("DmfInputFiles_" + str(in_file))
-303                        full_info = encoded_info[0].decode().split("/")[-1].replace(".h5", "").split("_")
-304                        my_tuple = (full_info[0], full_info[1][1:], full_info[2], full_info[3])
-305                        identifier.append(my_tuple)
-306                    identifier = tuple(identifier)
-307                    # "DistillationContraction/Metadata/DmfSuffix" contains info about different quarks, irrelevant in the SU(3) case.
-308
-309                for diagram in diagrams:
-310
-311                    if diagram == "triangle" and "Identity" not in str(identifier):
-312                        part = "im"
-313                    else:
-314                        part = "re"
+290        corr_data = {}
+291
+292        for diagram in diagrams:
+293            corr_data[diagram] = []
+294
+295        try:
+296            for n_file, (hd5_file, _n_traj) in enumerate(zip(file_list, list(idx), strict=True)):
+297                h5file = h5py.File(hd5_file)
+298
+299                if n_file == 0:
+300                    if h5file["DistillationContraction/Metadata"].attrs.get("TimeSources")[0].decode() != "0...":
+301                        raise Exception("Routine is only implemented for files containing inversions on all timeslices.")
+302
+303                    Nt = h5file["DistillationContraction/Metadata"].attrs.get("Nt")[0]
+304
+305                    identifier = []
+306                    for in_file in range(len(h5file["DistillationContraction/Metadata/DmfInputFiles"].attrs.keys()) - 1):
+307                        encoded_info = h5file["DistillationContraction/Metadata/DmfInputFiles"].attrs.get("DmfInputFiles_" + str(in_file))
+308                        full_info = encoded_info[0].decode().split("/")[-1].replace(".h5", "").split("_")
+309                        my_tuple = (full_info[0], full_info[1][1:], full_info[2], full_info[3])
+310                        identifier.append(my_tuple)
+311                    identifier = tuple(identifier)
+312                    # "DistillationContraction/Metadata/DmfSuffix" contains info about different quarks, irrelevant in the SU(3) case.
+313
+314                for diagram in diagrams:
 315
-316                    real_data = np.zeros(Nt)
-317                    for x0 in range(Nt):
-318                        raw_data = h5file["DistillationContraction/Correlators/" + diagram + "/" + str(x0)][:][part].astype(np.double)
-319                        real_data += np.roll(raw_data, -x0)
-320                    real_data /= Nt
-321
-322                    corr_data[diagram].append(real_data)
-323                h5file.close()
-324
-325            res_dict[str(identifier)] = {}
+316                    if diagram == "triangle" and "Identity" not in str(identifier):
+317                        part = "im"
+318                    else:
+319                        part = "re"
+320
+321                    real_data = np.zeros(Nt)
+322                    for x0 in range(Nt):
+323                        raw_data = h5file["DistillationContraction/Correlators/" + diagram + "/" + str(x0)][:][part].astype(np.double)
+324                        real_data += np.roll(raw_data, -x0)
+325                    real_data /= Nt
 326
-327            for diagram in diagrams:
-328
-329                tmp_data = np.array(corr_data[diagram])
-330
-331                l_obs = []
-332                for c in tmp_data.T:
-333                    l_obs.append(Obs([c], [ens_id], idl=[idx]))
-334
-335                corr = Corr(l_obs)
-336                corr.tag = str(identifier)
-337
-338                res_dict[str(identifier)][diagram] = corr
-339        except FileNotFoundError:
-340            print("Skip", stem)
-341
-342    return res_dict
-343
-344
-345class Npr_matrix(np.ndarray):
+327                    corr_data[diagram].append(real_data)
+328                h5file.close()
+329
+330            res_dict[str(identifier)] = {}
+331
+332            for diagram in diagrams:
+333
+334                tmp_data = np.array(corr_data[diagram])
+335
+336                l_obs = []
+337                for c in tmp_data.T:
+338                    l_obs.append(Obs([c], [ens_id], idl=[idx]))
+339
+340                corr = Corr(l_obs)
+341                corr.tag = str(identifier)
+342
+343                res_dict[str(identifier)][diagram] = corr
+344        except FileNotFoundError:
+345            print("Skip", stem)
 346
-347    def __new__(cls, input_array, mom_in=None, mom_out=None):
-348        obj = np.asarray(input_array).view(cls)
-349        obj.mom_in = mom_in
-350        obj.mom_out = mom_out
-351        return obj
-352
-353    @property
-354    def g5H(self):
-355        """Gamma_5 hermitean conjugate
-356
-357        Uses the fact that the propagator is gamma5 hermitean, so just the
-358        in and out momenta of the propagator are exchanged.
-359        """
-360        return Npr_matrix(self,
-361                          mom_in=self.mom_out,
-362                          mom_out=self.mom_in)
-363
-364    def _propagate_mom(self, other, name):
-365        s_mom = getattr(self, name, None)
-366        o_mom = getattr(other, name, None)
-367        if s_mom is not None and o_mom is not None:
-368            if not np.allclose(s_mom, o_mom):
-369                raise Exception(name + ' does not match.')
-370        return o_mom if o_mom is not None else s_mom
-371
-372    def __matmul__(self, other):
-373        return self.__new__(Npr_matrix,
-374                            super().__matmul__(other),
-375                            self._propagate_mom(other, 'mom_in'),
-376                            self._propagate_mom(other, 'mom_out'))
-377
-378    def __array_finalize__(self, obj):
-379        if obj is None:
-380            return
-381        self.mom_in = getattr(obj, 'mom_in', None)
-382        self.mom_out = getattr(obj, 'mom_out', None)
-383
-384
-385def read_ExternalLeg_hd5(path, filestem, ens_id, idl=None):
-386    """Read hadrons ExternalLeg hdf5 file and output an array of CObs
-387
-388    Parameters
-389    ----------
-390    path : str
-391        path to the files to read
-392    filestem : str
-393        namestem of the files to read
-394    ens_id : str
-395        name of the ensemble, required for internal bookkeeping
-396    idl : range
-397        If specified only configurations in the given range are read in.
-398
-399    Returns
-400    -------
-401    result : Npr_matrix
-402        read Cobs-matrix
-403    """
-404
-405    files, idx = _get_files(path, filestem, idl)
-406
-407    mom = None
-408
-409    corr_data = []
-410    for hd5_file in files:
-411        file = h5py.File(path + '/' + hd5_file, "r")
-412        raw_data = file['ExternalLeg/corr'][0][0].view('complex')
-413        corr_data.append(raw_data)
-414        if mom is None:
-415            mom = np.array(str(file['ExternalLeg/info'].attrs['pIn'])[3:-2].strip().split(), dtype=float)
-416        file.close()
-417    corr_data = np.array(corr_data)
-418
-419    rolled_array = np.rollaxis(corr_data, 0, 5)
-420
-421    matrix = np.empty((rolled_array.shape[:-1]), dtype=object)
-422    for si, sj, ci, cj in np.ndindex(rolled_array.shape[:-1]):
-423        real = Obs([rolled_array[si, sj, ci, cj].real], [ens_id], idl=[idx])
-424        imag = Obs([rolled_array[si, sj, ci, cj].imag], [ens_id], idl=[idx])
-425        matrix[si, sj, ci, cj] = CObs(real, imag)
-426
-427    return Npr_matrix(matrix, mom_in=mom)
-428
-429
-430def read_Bilinear_hd5(path, filestem, ens_id, idl=None):
-431    """Read hadrons Bilinear hdf5 file and output an array of CObs
-432
-433    Parameters
-434    ----------
-435    path : str
-436        path to the files to read
-437    filestem : str
-438        namestem of the files to read
-439    ens_id : str
-440        name of the ensemble, required for internal bookkeeping
-441    idl : range
-442        If specified only configurations in the given range are read in.
-443
-444    Returns
-445    -------
-446    result_dict: dict[Npr_matrix]
-447        extracted Bilinears
-448    """
-449
-450    files, idx = _get_files(path, filestem, idl)
-451
-452    mom_in = None
-453    mom_out = None
+347    return res_dict
+348
+349
+350class Npr_matrix(np.ndarray):
+351
+352    def __new__(cls, input_array, mom_in=None, mom_out=None):
+353        obj = np.asarray(input_array).view(cls)
+354        obj.mom_in = mom_in
+355        obj.mom_out = mom_out
+356        return obj
+357
+358    @property
+359    def g5H(self):
+360        """Gamma_5 hermitean conjugate
+361
+362        Uses the fact that the propagator is gamma5 hermitean, so just the
+363        in and out momenta of the propagator are exchanged.
+364        """
+365        return Npr_matrix(self,
+366                          mom_in=self.mom_out,
+367                          mom_out=self.mom_in)
+368
+369    def _propagate_mom(self, other, name):
+370        s_mom = getattr(self, name, None)
+371        o_mom = getattr(other, name, None)
+372        if s_mom is not None and o_mom is not None:
+373            if not np.allclose(s_mom, o_mom):
+374                raise Exception(name + ' does not match.')
+375        return o_mom if o_mom is not None else s_mom
+376
+377    def __matmul__(self, other):
+378        return self.__new__(Npr_matrix,
+379                            super().__matmul__(other),
+380                            self._propagate_mom(other, 'mom_in'),
+381                            self._propagate_mom(other, 'mom_out'))
+382
+383    def __array_finalize__(self, obj):
+384        if obj is None:
+385            return
+386        self.mom_in = getattr(obj, 'mom_in', None)
+387        self.mom_out = getattr(obj, 'mom_out', None)
+388
+389
+390def read_ExternalLeg_hd5(path, filestem, ens_id, idl=None):
+391    """Read hadrons ExternalLeg hdf5 file and output an array of CObs
+392
+393    Parameters
+394    ----------
+395    path : str
+396        path to the files to read
+397    filestem : str
+398        namestem of the files to read
+399    ens_id : str
+400        name of the ensemble, required for internal bookkeeping
+401    idl : range
+402        If specified only configurations in the given range are read in.
+403
+404    Returns
+405    -------
+406    result : Npr_matrix
+407        read Cobs-matrix
+408    """
+409
+410    files, idx = _get_files(path, filestem, idl)
+411
+412    mom = None
+413
+414    corr_data = []
+415    for hd5_file in files:
+416        file = h5py.File(path + '/' + hd5_file, "r")
+417        raw_data = file['ExternalLeg/corr'][0][0].view('complex')
+418        corr_data.append(raw_data)
+419        if mom is None:
+420            mom = np.array(str(file['ExternalLeg/info'].attrs['pIn'])[3:-2].strip().split(), dtype=float)
+421        file.close()
+422    corr_data = np.array(corr_data)
+423
+424    rolled_array = np.rollaxis(corr_data, 0, 5)
+425
+426    matrix = np.empty((rolled_array.shape[:-1]), dtype=object)
+427    for si, sj, ci, cj in np.ndindex(rolled_array.shape[:-1]):
+428        real = Obs([rolled_array[si, sj, ci, cj].real], [ens_id], idl=[idx])
+429        imag = Obs([rolled_array[si, sj, ci, cj].imag], [ens_id], idl=[idx])
+430        matrix[si, sj, ci, cj] = CObs(real, imag)
+431
+432    return Npr_matrix(matrix, mom_in=mom)
+433
+434
+435def read_Bilinear_hd5(path, filestem, ens_id, idl=None):
+436    """Read hadrons Bilinear hdf5 file and output an array of CObs
+437
+438    Parameters
+439    ----------
+440    path : str
+441        path to the files to read
+442    filestem : str
+443        namestem of the files to read
+444    ens_id : str
+445        name of the ensemble, required for internal bookkeeping
+446    idl : range
+447        If specified only configurations in the given range are read in.
+448
+449    Returns
+450    -------
+451    result_dict: dict[Npr_matrix]
+452        extracted Bilinears
+453    """
 454
-455    corr_data = {}
-456    for hd5_file in files:
-457        file = h5py.File(path + '/' + hd5_file, "r")
-458        for i in range(16):
-459            name = file['Bilinear/Bilinear_' + str(i) + '/info'].attrs['gamma'][0].decode('UTF-8')
-460            if name not in corr_data:
-461                corr_data[name] = []
-462            raw_data = file['Bilinear/Bilinear_' + str(i) + '/corr'][0][0].view('complex')
-463            corr_data[name].append(raw_data)
-464            if mom_in is None:
-465                mom_in = np.array(str(file['Bilinear/Bilinear_' + str(i) + '/info'].attrs['pIn'])[3:-2].strip().split(), dtype=float)
-466            if mom_out is None:
-467                mom_out = np.array(str(file['Bilinear/Bilinear_' + str(i) + '/info'].attrs['pOut'])[3:-2].strip().split(), dtype=float)
-468
-469        file.close()
-470
-471    result_dict = {}
-472
-473    for key, data in corr_data.items():
-474        local_data = np.array(data)
+455    files, idx = _get_files(path, filestem, idl)
+456
+457    mom_in = None
+458    mom_out = None
+459
+460    corr_data = {}
+461    for hd5_file in files:
+462        file = h5py.File(path + '/' + hd5_file, "r")
+463        for i in range(16):
+464            name = file['Bilinear/Bilinear_' + str(i) + '/info'].attrs['gamma'][0].decode('UTF-8')
+465            if name not in corr_data:
+466                corr_data[name] = []
+467            raw_data = file['Bilinear/Bilinear_' + str(i) + '/corr'][0][0].view('complex')
+468            corr_data[name].append(raw_data)
+469            if mom_in is None:
+470                mom_in = np.array(str(file['Bilinear/Bilinear_' + str(i) + '/info'].attrs['pIn'])[3:-2].strip().split(), dtype=float)
+471            if mom_out is None:
+472                mom_out = np.array(str(file['Bilinear/Bilinear_' + str(i) + '/info'].attrs['pOut'])[3:-2].strip().split(), dtype=float)
+473
+474        file.close()
 475
-476        rolled_array = np.rollaxis(local_data, 0, 5)
+476    result_dict = {}
 477
-478        matrix = np.empty((rolled_array.shape[:-1]), dtype=object)
-479        for si, sj, ci, cj in np.ndindex(rolled_array.shape[:-1]):
-480            real = Obs([rolled_array[si, sj, ci, cj].real], [ens_id], idl=[idx])
-481            imag = Obs([rolled_array[si, sj, ci, cj].imag], [ens_id], idl=[idx])
-482            matrix[si, sj, ci, cj] = CObs(real, imag)
-483
-484        result_dict[key] = Npr_matrix(matrix, mom_in=mom_in, mom_out=mom_out)
-485
-486    return result_dict
-487
+478    for key, data in corr_data.items():
+479        local_data = np.array(data)
+480
+481        rolled_array = np.rollaxis(local_data, 0, 5)
+482
+483        matrix = np.empty((rolled_array.shape[:-1]), dtype=object)
+484        for si, sj, ci, cj in np.ndindex(rolled_array.shape[:-1]):
+485            real = Obs([rolled_array[si, sj, ci, cj].real], [ens_id], idl=[idx])
+486            imag = Obs([rolled_array[si, sj, ci, cj].imag], [ens_id], idl=[idx])
+487            matrix[si, sj, ci, cj] = CObs(real, imag)
 488
-489def read_Fourquark_hd5(path, filestem, ens_id, idl=None, vertices=["VA", "AV"]):
-490    """Read hadrons FourquarkFullyConnected hdf5 file and output an array of CObs
-491
-492    Parameters
-493    ----------
-494    path : str
-495        path to the files to read
-496    filestem : str
-497        namestem of the files to read
-498    ens_id : str
-499        name of the ensemble, required for internal bookkeeping
-500    idl : range
-501        If specified only configurations in the given range are read in.
-502    vertices : list
-503        Vertex functions to be extracted.
-504
-505    Returns
-506    -------
-507    result_dict : dict
-508        extracted fourquark matrizes
-509    """
-510
-511    files, idx = _get_files(path, filestem, idl)
-512
-513    mom_in = None
-514    mom_out = None
+489        result_dict[key] = Npr_matrix(matrix, mom_in=mom_in, mom_out=mom_out)
+490
+491    return result_dict
+492
+493
+494def read_Fourquark_hd5(path, filestem, ens_id, idl=None, vertices=None):
+495    """Read hadrons FourquarkFullyConnected hdf5 file and output an array of CObs
+496
+497    Parameters
+498    ----------
+499    path : str
+500        path to the files to read
+501    filestem : str
+502        namestem of the files to read
+503    ens_id : str
+504        name of the ensemble, required for internal bookkeeping
+505    idl : range
+506        If specified only configurations in the given range are read in.
+507    vertices : list
+508        Vertex functions to be extracted.
+509
+510    Returns
+511    -------
+512    result_dict : dict
+513        extracted fourquark matrizes
+514    """
 515
-516    vertex_names = []
-517    for vertex in vertices:
-518        vertex_names += _get_lorentz_names(vertex)
-519
-520    corr_data = {}
-521
-522    tree = 'FourQuarkFullyConnected/FourQuarkFullyConnected_'
+516    if vertices is None:
+517        vertices = ["VA", "AV"]
+518
+519    files, idx = _get_files(path, filestem, idl)
+520
+521    mom_in = None
+522    mom_out = None
 523
-524    for hd5_file in files:
-525        file = h5py.File(path + '/' + hd5_file, "r")
-526
-527        for i in range(32):
-528            name = (file[tree + str(i) + '/info'].attrs['gammaA'][0].decode('UTF-8'), file[tree + str(i) + '/info'].attrs['gammaB'][0].decode('UTF-8'))
-529            if name in vertex_names:
-530                if name not in corr_data:
-531                    corr_data[name] = []
-532                raw_data = file[tree + str(i) + '/corr'][0][0].view('complex')
-533                corr_data[name].append(raw_data)
-534                if mom_in is None:
-535                    mom_in = np.array(str(file[tree + str(i) + '/info'].attrs['pIn'])[3:-2].strip().split(), dtype=float)
-536                if mom_out is None:
-537                    mom_out = np.array(str(file[tree + str(i) + '/info'].attrs['pOut'])[3:-2].strip().split(), dtype=float)
-538
-539        file.close()
-540
-541    intermediate_dict = {}
-542
-543    for vertex in vertices:
-544        lorentz_names = _get_lorentz_names(vertex)
-545        for v_name in lorentz_names:
-546            if v_name in [('SigmaXY', 'SigmaZT'),
-547                          ('SigmaXT', 'SigmaYZ'),
-548                          ('SigmaYZ', 'SigmaXT'),
-549                          ('SigmaZT', 'SigmaXY')]:
-550                sign = -1
-551            else:
-552                sign = 1
-553            if vertex not in intermediate_dict:
-554                intermediate_dict[vertex] = sign * np.array(corr_data[v_name])
-555            else:
-556                intermediate_dict[vertex] += sign * np.array(corr_data[v_name])
-557
-558    result_dict = {}
-559
-560    for key, data in intermediate_dict.items():
-561
-562        rolled_array = np.moveaxis(data, 0, 8)
-563
-564        matrix = np.empty((rolled_array.shape[:-1]), dtype=object)
-565        for index in np.ndindex(rolled_array.shape[:-1]):
-566            real = Obs([rolled_array[index].real], [ens_id], idl=[idx])
-567            imag = Obs([rolled_array[index].imag], [ens_id], idl=[idx])
-568            matrix[index] = CObs(real, imag)
+524    vertex_names = []
+525    for vertex in vertices:
+526        vertex_names += _get_lorentz_names(vertex)
+527
+528    corr_data = {}
+529
+530    tree = 'FourQuarkFullyConnected/FourQuarkFullyConnected_'
+531
+532    for hd5_file in files:
+533        file = h5py.File(path + '/' + hd5_file, "r")
+534
+535        for i in range(32):
+536            name = (file[tree + str(i) + '/info'].attrs['gammaA'][0].decode('UTF-8'), file[tree + str(i) + '/info'].attrs['gammaB'][0].decode('UTF-8'))
+537            if name in vertex_names:
+538                if name not in corr_data:
+539                    corr_data[name] = []
+540                raw_data = file[tree + str(i) + '/corr'][0][0].view('complex')
+541                corr_data[name].append(raw_data)
+542                if mom_in is None:
+543                    mom_in = np.array(str(file[tree + str(i) + '/info'].attrs['pIn'])[3:-2].strip().split(), dtype=float)
+544                if mom_out is None:
+545                    mom_out = np.array(str(file[tree + str(i) + '/info'].attrs['pOut'])[3:-2].strip().split(), dtype=float)
+546
+547        file.close()
+548
+549    intermediate_dict = {}
+550
+551    for vertex in vertices:
+552        lorentz_names = _get_lorentz_names(vertex)
+553        for v_name in lorentz_names:
+554            if v_name in [('SigmaXY', 'SigmaZT'),
+555                          ('SigmaXT', 'SigmaYZ'),
+556                          ('SigmaYZ', 'SigmaXT'),
+557                          ('SigmaZT', 'SigmaXY')]:
+558                sign = -1
+559            else:
+560                sign = 1
+561            if vertex not in intermediate_dict:
+562                intermediate_dict[vertex] = sign * np.array(corr_data[v_name])
+563            else:
+564                intermediate_dict[vertex] += sign * np.array(corr_data[v_name])
+565
+566    result_dict = {}
+567
+568    for key, data in intermediate_dict.items():
 569
-570        result_dict[key] = Npr_matrix(matrix, mom_in=mom_in, mom_out=mom_out)
+570        rolled_array = np.moveaxis(data, 0, 8)
 571
-572    return result_dict
-573
-574
-575def _get_lorentz_names(name):
-576    lorentz_index = ['X', 'Y', 'Z', 'T']
+572        matrix = np.empty((rolled_array.shape[:-1]), dtype=object)
+573        for index in np.ndindex(rolled_array.shape[:-1]):
+574            real = Obs([rolled_array[index].real], [ens_id], idl=[idx])
+575            imag = Obs([rolled_array[index].imag], [ens_id], idl=[idx])
+576            matrix[index] = CObs(real, imag)
 577
-578    res = []
+578        result_dict[key] = Npr_matrix(matrix, mom_in=mom_in, mom_out=mom_out)
 579
-580    if name == "TT":
-581        for i in range(4):
-582            for j in range(i + 1, 4):
-583                res.append(("Sigma" + lorentz_index[i] + lorentz_index[j], "Sigma" + lorentz_index[i] + lorentz_index[j]))
-584        return res
+580    return result_dict
+581
+582
+583def _get_lorentz_names(name):
+584    lorentz_index = ['X', 'Y', 'Z', 'T']
 585
-586    if name == "TTtilde":
-587        for i in range(4):
-588            for j in range(i + 1, 4):
-589                for k in range(4):
-590                    for o in range(k + 1, 4):
-591                        fac = epsilon_tensor_rank4(i, j, k, o)
-592                        if not np.isclose(fac, 0.0):
-593                            res.append(("Sigma" + lorentz_index[i] + lorentz_index[j], "Sigma" + lorentz_index[k] + lorentz_index[o]))
-594        return res
-595
-596    assert len(name) == 2
-597
-598    if 'S' in name or 'P' in name:
-599        if not set(name) <= set(['S', 'P']):
-600            raise Exception("'" + name + "' is not a Lorentz scalar")
-601
-602        g_names = {'S': 'Identity',
-603                   'P': 'Gamma5'}
-604
-605        res.append((g_names[name[0]], g_names[name[1]]))
-606
-607    else:
-608        if not set(name) <= set(['V', 'A']):
-609            raise Exception("'" + name + "' is not a Lorentz scalar")
-610
-611        for ind in lorentz_index:
-612            res.append(('Gamma' + ind + (name[0] == 'A') * 'Gamma5',
-613                        'Gamma' + ind + (name[1] == 'A') * 'Gamma5'))
+586    res = []
+587
+588    if name == "TT":
+589        for i in range(4):
+590            for j in range(i + 1, 4):
+591                res.append(("Sigma" + lorentz_index[i] + lorentz_index[j], "Sigma" + lorentz_index[i] + lorentz_index[j]))
+592        return res
+593
+594    if name == "TTtilde":
+595        for i in range(4):
+596            for j in range(i + 1, 4):
+597                for k in range(4):
+598                    for o in range(k + 1, 4):
+599                        fac = epsilon_tensor_rank4(i, j, k, o)
+600                        if not np.isclose(fac, 0.0):
+601                            res.append(("Sigma" + lorentz_index[i] + lorentz_index[j], "Sigma" + lorentz_index[k] + lorentz_index[o]))
+602        return res
+603
+604    assert len(name) == 2
+605
+606    if 'S' in name or 'P' in name:
+607        if not set(name) <= set(['S', 'P']):
+608            raise Exception("'" + name + "' is not a Lorentz scalar")
+609
+610        g_names = {'S': 'Identity',
+611                   'P': 'Gamma5'}
+612
+613        res.append((g_names[name[0]], g_names[name[1]]))
 614
-615    return res
+615    else:
+616        if not set(name) <= set(['V', 'A']):
+617            raise Exception("'" + name + "' is not a Lorentz scalar")
+618
+619        for ind in lorentz_index:
+620            res.append(('Gamma' + ind + (name[0] == 'A') * 'Gamma5',
+621                        'Gamma' + ind + (name[1] == 'A') * 'Gamma5'))
+622
+623    return res
 
@@ -733,90 +741,90 @@
-
 58def read_hd5(filestem, ens_id, group, attrs=None, idl=None, part="real"):
- 59    r'''Read hadrons hdf5 file and extract entry based on attributes.
- 60
- 61    Parameters
- 62    -----------------
- 63    filestem : str
- 64        Full namestem of the files to read, including the full path.
- 65    ens_id : str
- 66        name of the ensemble, required for internal bookkeeping
- 67    group : str
- 68        label of the group to be extracted.
- 69    attrs : dict or int
- 70        Dictionary containing the attributes. For example
- 71        ```python
- 72        attrs = {"gamma_snk": "Gamma5",
- 73                 "gamma_src": "Gamma5"}
- 74         ```
- 75        Alternatively an integer can be specified to identify the sub group.
- 76        This is discouraged as the order in the file is not guaranteed.
- 77    idl : range
- 78        If specified only configurations in the given range are read in.
- 79    part: str
- 80        string specifying whether to extract the real part ('real'),
- 81        the imaginary part ('imag') or a complex correlator ('complex').
- 82        Default 'real'.
- 83
- 84    Returns
- 85    -------
- 86    corr : Corr
- 87        Correlator of the source sink combination in question.
- 88    '''
- 89
- 90    path_obj = Path(filestem)
- 91    path = path_obj.parent.as_posix()
- 92    filestem = path_obj.name
- 93
- 94    files, idx = _get_files(path, filestem, idl)
+            
 60def read_hd5(filestem, ens_id, group, attrs=None, idl=None, part="real"):
+ 61    r'''Read hadrons hdf5 file and extract entry based on attributes.
+ 62
+ 63    Parameters
+ 64    -----------------
+ 65    filestem : str
+ 66        Full namestem of the files to read, including the full path.
+ 67    ens_id : str
+ 68        name of the ensemble, required for internal bookkeeping
+ 69    group : str
+ 70        label of the group to be extracted.
+ 71    attrs : dict or int
+ 72        Dictionary containing the attributes. For example
+ 73        ```python
+ 74        attrs = {"gamma_snk": "Gamma5",
+ 75                 "gamma_src": "Gamma5"}
+ 76         ```
+ 77        Alternatively an integer can be specified to identify the sub group.
+ 78        This is discouraged as the order in the file is not guaranteed.
+ 79    idl : range
+ 80        If specified only configurations in the given range are read in.
+ 81    part: str
+ 82        string specifying whether to extract the real part ('real'),
+ 83        the imaginary part ('imag') or a complex correlator ('complex').
+ 84        Default 'real'.
+ 85
+ 86    Returns
+ 87    -------
+ 88    corr : Corr
+ 89        Correlator of the source sink combination in question.
+ 90    '''
+ 91
+ 92    path_obj = Path(filestem)
+ 93    path = path_obj.parent.as_posix()
+ 94    filestem = path_obj.name
  95
- 96    if isinstance(attrs, dict):
- 97        h5file = h5py.File(path + '/' + files[0], "r")
- 98        entry = None
- 99        for key in h5file[group].keys():
-100            if attrs.items() <= {k: v[0].decode() for k, v in h5file[group][key].attrs.items()}.items():
-101                if entry is None:
-102                    entry = key
-103                else:
-104                    raise ValueError("More than one fitting entry found. More constraint on attributes needed.")
-105        h5file.close()
-106        if entry is None:
-107            raise ValueError(f"Entry with attributes {attrs} not found.")
-108    elif isinstance(attrs, int):
-109        entry = group + f"_{attrs}"
-110    else:
-111        raise TypeError("Invalid type for 'attrs'. Needs to be dict or int.")
-112
-113    corr_data = []
-114    infos = []
-115    for hd5_file in files:
-116        h5file = h5py.File(path + '/' + hd5_file, "r")
-117        if group + '/' + entry not in h5file:
-118            raise Exception("Entry '" + entry + "' not contained in the files.")
-119        raw_data = h5file[group + '/' + entry + '/corr']
-120        real_data = raw_data[:].view("complex")
-121        corr_data.append(real_data)
-122        if not infos:
-123            for k, i in h5file[group + '/' + entry].attrs.items():
-124                infos.append(k + ': ' + i[0].decode())
-125        h5file.close()
-126    corr_data = np.array(corr_data)
-127
-128    if part == "complex":
-129        l_obs = []
-130        for c in corr_data.T:
-131            l_obs.append(CObs(Obs([c.real], [ens_id], idl=[idx]),
-132                              Obs([c.imag], [ens_id], idl=[idx])))
-133    else:
-134        corr_data = getattr(corr_data, part)
-135        l_obs = []
-136        for c in corr_data.T:
-137            l_obs.append(Obs([c], [ens_id], idl=[idx]))
-138
-139    corr = Corr(l_obs)
-140    corr.tag = r", ".join(infos)
-141    return corr
+ 96    files, idx = _get_files(path, filestem, idl)
+ 97
+ 98    if isinstance(attrs, dict):
+ 99        h5file = h5py.File(path + '/' + files[0], "r")
+100        entry = None
+101        for key in h5file[group].keys():
+102            if attrs.items() <= {k: v[0].decode() for k, v in h5file[group][key].attrs.items()}.items():
+103                if entry is None:
+104                    entry = key
+105                else:
+106                    raise ValueError("More than one fitting entry found. More constraint on attributes needed.")
+107        h5file.close()
+108        if entry is None:
+109            raise ValueError(f"Entry with attributes {attrs} not found.")
+110    elif isinstance(attrs, int):
+111        entry = group + f"_{attrs}"
+112    else:
+113        raise TypeError("Invalid type for 'attrs'. Needs to be dict or int.")
+114
+115    corr_data = []
+116    infos = []
+117    for hd5_file in files:
+118        h5file = h5py.File(path + '/' + hd5_file, "r")
+119        if group + '/' + entry not in h5file:
+120            raise Exception("Entry '" + entry + "' not contained in the files.")
+121        raw_data = h5file[group + '/' + entry + '/corr']
+122        real_data = raw_data[:].view("complex")
+123        corr_data.append(real_data)
+124        if not infos:
+125            for k, i in h5file[group + '/' + entry].attrs.items():
+126                infos.append(k + ': ' + i[0].decode())
+127        h5file.close()
+128    corr_data = np.array(corr_data)
+129
+130    if part == "complex":
+131        l_obs = []
+132        for c in corr_data.T:
+133            l_obs.append(CObs(Obs([c.real], [ens_id], idl=[idx]),
+134                              Obs([c.imag], [ens_id], idl=[idx])))
+135    else:
+136        corr_data = getattr(corr_data, part)
+137        l_obs = []
+138        for c in corr_data.T:
+139            l_obs.append(Obs([c], [ens_id], idl=[idx]))
+140
+141    corr = Corr(l_obs)
+142    corr.tag = r", ".join(infos)
+143    return corr
 
@@ -871,43 +879,43 @@ Correlator of the source sink combination in question.
-
144def read_meson_hd5(path, filestem, ens_id, meson='meson_0', idl=None, gammas=None):
-145    r'''Read hadrons meson hdf5 file and extract the meson labeled 'meson'
-146
-147    Parameters
-148    -----------------
-149    path : str
-150        path to the files to read
-151    filestem : str
-152        namestem of the files to read
-153    ens_id : str
-154        name of the ensemble, required for internal bookkeeping
-155    meson : str
-156        label of the meson to be extracted, standard value meson_0 which
-157        corresponds to the pseudoscalar pseudoscalar two-point function.
-158    gammas : tuple of strings
-159        Instrad of a meson label one can also provide a tuple of two strings
-160        indicating the gamma matrices at sink and source (gamma_snk, gamma_src).
-161        ("Gamma5", "Gamma5") corresponds to the pseudoscalar pseudoscalar
-162        two-point function. The gammas argument dominateds over meson.
-163    idl : range
-164        If specified only configurations in the given range are read in.
-165
-166    Returns
-167    -------
-168    corr : Corr
-169        Correlator of the source sink combination in question.
-170    '''
-171    if gammas is None:
-172        attrs = int(meson.rsplit('_', 1)[-1])
-173    else:
-174        if len(gammas) != 2:
-175            raise ValueError("'gammas' needs to have exactly two entries")
-176        attrs = {"gamma_snk": gammas[0],
-177                 "gamma_src": gammas[1]}
-178    return read_hd5(filestem=path + "/" + filestem, ens_id=ens_id,
-179                    group=meson.rsplit('_', 1)[0], attrs=attrs, idl=idl,
-180                    part="real")
+            
146def read_meson_hd5(path, filestem, ens_id, meson='meson_0', idl=None, gammas=None):
+147    r'''Read hadrons meson hdf5 file and extract the meson labeled 'meson'
+148
+149    Parameters
+150    -----------------
+151    path : str
+152        path to the files to read
+153    filestem : str
+154        namestem of the files to read
+155    ens_id : str
+156        name of the ensemble, required for internal bookkeeping
+157    meson : str
+158        label of the meson to be extracted, standard value meson_0 which
+159        corresponds to the pseudoscalar pseudoscalar two-point function.
+160    gammas : tuple of strings
+161        Instrad of a meson label one can also provide a tuple of two strings
+162        indicating the gamma matrices at sink and source (gamma_snk, gamma_src).
+163        ("Gamma5", "Gamma5") corresponds to the pseudoscalar pseudoscalar
+164        two-point function. The gammas argument dominateds over meson.
+165    idl : range
+166        If specified only configurations in the given range are read in.
+167
+168    Returns
+169    -------
+170    corr : Corr
+171        Correlator of the source sink combination in question.
+172    '''
+173    if gammas is None:
+174        attrs = int(meson.rsplit('_', 1)[-1])
+175    else:
+176        if len(gammas) != 2:
+177            raise ValueError("'gammas' needs to have exactly two entries")
+178        attrs = {"gamma_snk": gammas[0],
+179                 "gamma_src": gammas[1]}
+180    return read_hd5(filestem=path + "/" + filestem, ens_id=ens_id,
+181                    group=meson.rsplit('_', 1)[0], attrs=attrs, idl=idl,
+182                    part="real")
 
@@ -955,52 +963,52 @@ Correlator of the source sink combination in question.
-
201def extract_t0_hd5(path, filestem, ens_id, obs='Clover energy density', fit_range=5, idl=None, **kwargs):
-202    r'''Read hadrons FlowObservables hdf5 file and extract t0
-203
-204    Parameters
-205    -----------------
-206    path : str
-207        path to the files to read
-208    filestem : str
-209        namestem of the files to read
-210    ens_id : str
-211        name of the ensemble, required for internal bookkeeping
-212    obs : str
-213        label of the observable from which t0 should be extracted.
-214        Options: 'Clover energy density' and 'Plaquette energy density'
-215    fit_range : int
-216        Number of data points left and right of the zero
-217        crossing to be included in the linear fit. (Default: 5)
-218    idl : range
-219        If specified only configurations in the given range are read in.
-220    plot_fit : bool
-221        If true, the fit for the extraction of t0 is shown together with the data.
-222    '''
-223
-224    files, idx = _get_files(path, filestem, idl)
-225    tree = "FlowObservables"
-226
-227    h5file = h5py.File(path + '/' + files[0], "r")
-228    obs_key = None
-229    for key in h5file[tree].keys():
-230        if obs == h5file[tree][key].attrs["description"][0].decode():
-231            obs_key = key
-232            break
-233    h5file.close()
-234    if obs_key is None:
-235        raise Exception(f"Observable {obs} not found.")
-236
-237    corr_data = _extract_real_arrays(path, files, tree, ["FlowObservables_0", obs_key])
+            
203def extract_t0_hd5(path, filestem, ens_id, obs='Clover energy density', fit_range=5, idl=None, **kwargs):
+204    r'''Read hadrons FlowObservables hdf5 file and extract t0
+205
+206    Parameters
+207    -----------------
+208    path : str
+209        path to the files to read
+210    filestem : str
+211        namestem of the files to read
+212    ens_id : str
+213        name of the ensemble, required for internal bookkeeping
+214    obs : str
+215        label of the observable from which t0 should be extracted.
+216        Options: 'Clover energy density' and 'Plaquette energy density'
+217    fit_range : int
+218        Number of data points left and right of the zero
+219        crossing to be included in the linear fit. (Default: 5)
+220    idl : range
+221        If specified only configurations in the given range are read in.
+222    plot_fit : bool
+223        If true, the fit for the extraction of t0 is shown together with the data.
+224    '''
+225
+226    files, idx = _get_files(path, filestem, idl)
+227    tree = "FlowObservables"
+228
+229    h5file = h5py.File(path + '/' + files[0], "r")
+230    obs_key = None
+231    for key in h5file[tree].keys():
+232        if obs == h5file[tree][key].attrs["description"][0].decode():
+233            obs_key = key
+234            break
+235    h5file.close()
+236    if obs_key is None:
+237        raise Exception(f"Observable {obs} not found.")
 238
-239    if not np.allclose(corr_data["FlowObservables_0"][0], corr_data["FlowObservables_0"][:]):
-240        raise Exception("Not all flow times were equal.")
-241
-242    t2E_dict = {}
-243    for t2, dat in zip(corr_data["FlowObservables_0"][0], corr_data[obs_key].T):
-244        t2E_dict[t2] = Obs([dat], [ens_id], idl=[idx]) - 0.3
-245
-246    return fit_t0(t2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'))
+239    corr_data = _extract_real_arrays(path, files, tree, ["FlowObservables_0", obs_key])
+240
+241    if not np.allclose(corr_data["FlowObservables_0"][0], corr_data["FlowObservables_0"][:]):
+242        raise Exception("Not all flow times were equal.")
+243
+244    t2E_dict = {}
+245    for t2, dat in zip(corr_data["FlowObservables_0"][0], corr_data[obs_key].T, strict=True):
+246        t2E_dict[t2] = Obs([dat], [ens_id], idl=[idx]) - 0.3
+247
+248    return fit_t0(t2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'))
 
@@ -1035,107 +1043,110 @@ If true, the fit for the extraction of t0 is shown together with the data.
def - read_DistillationContraction_hd5(path, ens_id, diagrams=['direct'], idl=None): + read_DistillationContraction_hd5(path, ens_id, diagrams=None, idl=None):
-
249def read_DistillationContraction_hd5(path, ens_id, diagrams=["direct"], idl=None):
-250    """Read hadrons DistillationContraction hdf5 files in given directory structure
-251
-252    Parameters
-253    -----------------
-254    path : str
-255        path to the directories to read
-256    ens_id : str
-257        name of the ensemble, required for internal bookkeeping
-258    diagrams : list
-259        List of strings of the diagrams to extract, e.g. ["direct", "box", "cross"].
-260    idl : range
-261        If specified only configurations in the given range are read in.
-262
-263    Returns
-264    -------
-265    result : dict
-266        extracted DistillationContration data
-267    """
-268
-269    res_dict = {}
+            
251def read_DistillationContraction_hd5(path, ens_id, diagrams=None, idl=None):
+252    """Read hadrons DistillationContraction hdf5 files in given directory structure
+253
+254    Parameters
+255    -----------------
+256    path : str
+257        path to the directories to read
+258    ens_id : str
+259        name of the ensemble, required for internal bookkeeping
+260    diagrams : list
+261        List of strings of the diagrams to extract, e.g. ["direct", "box", "cross"].
+262    idl : range
+263        If specified only configurations in the given range are read in.
+264
+265    Returns
+266    -------
+267    result : dict
+268        extracted DistillationContration data
+269    """
 270
-271    directories, idx = _get_files(path, "data", idl)
-272
-273    explore_path = Path(path + "/" + directories[0])
-274
-275    for explore_file in explore_path.iterdir():
-276        if explore_file.is_file():
-277            stem = explore_file.with_suffix("").with_suffix("").as_posix().split("/")[-1]
-278        else:
-279            continue
-280
-281        file_list = []
-282        for dir in directories:
-283            tmp_path = Path(path + "/" + dir)
-284            file_list.append((tmp_path / stem).as_posix() + tmp_path.suffix + ".h5")
+271    if diagrams is None:
+272        diagrams = ["direct"]
+273
+274    res_dict = {}
+275
+276    directories, idx = _get_files(path, "data", idl)
+277
+278    explore_path = Path(path + "/" + directories[0])
+279
+280    for explore_file in explore_path.iterdir():
+281        if explore_file.is_file():
+282            stem = explore_file.with_suffix("").with_suffix("").as_posix().split("/")[-1]
+283        else:
+284            continue
 285
-286        corr_data = {}
-287
-288        for diagram in diagrams:
-289            corr_data[diagram] = []
+286        file_list = []
+287        for dir in directories:
+288            tmp_path = Path(path + "/" + dir)
+289            file_list.append((tmp_path / stem).as_posix() + tmp_path.suffix + ".h5")
 290
-291        try:
-292            for n_file, (hd5_file, n_traj) in enumerate(zip(file_list, list(idx))):
-293                h5file = h5py.File(hd5_file)
-294
-295                if n_file == 0:
-296                    if h5file["DistillationContraction/Metadata"].attrs.get("TimeSources")[0].decode() != "0...":
-297                        raise Exception("Routine is only implemented for files containing inversions on all timeslices.")
-298
-299                    Nt = h5file["DistillationContraction/Metadata"].attrs.get("Nt")[0]
-300
-301                    identifier = []
-302                    for in_file in range(len(h5file["DistillationContraction/Metadata/DmfInputFiles"].attrs.keys()) - 1):
-303                        encoded_info = h5file["DistillationContraction/Metadata/DmfInputFiles"].attrs.get("DmfInputFiles_" + str(in_file))
-304                        full_info = encoded_info[0].decode().split("/")[-1].replace(".h5", "").split("_")
-305                        my_tuple = (full_info[0], full_info[1][1:], full_info[2], full_info[3])
-306                        identifier.append(my_tuple)
-307                    identifier = tuple(identifier)
-308                    # "DistillationContraction/Metadata/DmfSuffix" contains info about different quarks, irrelevant in the SU(3) case.
-309
-310                for diagram in diagrams:
-311
-312                    if diagram == "triangle" and "Identity" not in str(identifier):
-313                        part = "im"
-314                    else:
-315                        part = "re"
+291        corr_data = {}
+292
+293        for diagram in diagrams:
+294            corr_data[diagram] = []
+295
+296        try:
+297            for n_file, (hd5_file, _n_traj) in enumerate(zip(file_list, list(idx), strict=True)):
+298                h5file = h5py.File(hd5_file)
+299
+300                if n_file == 0:
+301                    if h5file["DistillationContraction/Metadata"].attrs.get("TimeSources")[0].decode() != "0...":
+302                        raise Exception("Routine is only implemented for files containing inversions on all timeslices.")
+303
+304                    Nt = h5file["DistillationContraction/Metadata"].attrs.get("Nt")[0]
+305
+306                    identifier = []
+307                    for in_file in range(len(h5file["DistillationContraction/Metadata/DmfInputFiles"].attrs.keys()) - 1):
+308                        encoded_info = h5file["DistillationContraction/Metadata/DmfInputFiles"].attrs.get("DmfInputFiles_" + str(in_file))
+309                        full_info = encoded_info[0].decode().split("/")[-1].replace(".h5", "").split("_")
+310                        my_tuple = (full_info[0], full_info[1][1:], full_info[2], full_info[3])
+311                        identifier.append(my_tuple)
+312                    identifier = tuple(identifier)
+313                    # "DistillationContraction/Metadata/DmfSuffix" contains info about different quarks, irrelevant in the SU(3) case.
+314
+315                for diagram in diagrams:
 316
-317                    real_data = np.zeros(Nt)
-318                    for x0 in range(Nt):
-319                        raw_data = h5file["DistillationContraction/Correlators/" + diagram + "/" + str(x0)][:][part].astype(np.double)
-320                        real_data += np.roll(raw_data, -x0)
-321                    real_data /= Nt
-322
-323                    corr_data[diagram].append(real_data)
-324                h5file.close()
-325
-326            res_dict[str(identifier)] = {}
+317                    if diagram == "triangle" and "Identity" not in str(identifier):
+318                        part = "im"
+319                    else:
+320                        part = "re"
+321
+322                    real_data = np.zeros(Nt)
+323                    for x0 in range(Nt):
+324                        raw_data = h5file["DistillationContraction/Correlators/" + diagram + "/" + str(x0)][:][part].astype(np.double)
+325                        real_data += np.roll(raw_data, -x0)
+326                    real_data /= Nt
 327
-328            for diagram in diagrams:
-329
-330                tmp_data = np.array(corr_data[diagram])
-331
-332                l_obs = []
-333                for c in tmp_data.T:
-334                    l_obs.append(Obs([c], [ens_id], idl=[idx]))
-335
-336                corr = Corr(l_obs)
-337                corr.tag = str(identifier)
-338
-339                res_dict[str(identifier)][diagram] = corr
-340        except FileNotFoundError:
-341            print("Skip", stem)
-342
-343    return res_dict
+328                    corr_data[diagram].append(real_data)
+329                h5file.close()
+330
+331            res_dict[str(identifier)] = {}
+332
+333            for diagram in diagrams:
+334
+335                tmp_data = np.array(corr_data[diagram])
+336
+337                l_obs = []
+338                for c in tmp_data.T:
+339                    l_obs.append(Obs([c], [ens_id], idl=[idx]))
+340
+341                corr = Corr(l_obs)
+342                corr.tag = str(identifier)
+343
+344                res_dict[str(identifier)][diagram] = corr
+345        except FileNotFoundError:
+346            print("Skip", stem)
+347
+348    return res_dict
 
@@ -1175,44 +1186,44 @@ extracted DistillationContration data
-
346class Npr_matrix(np.ndarray):
-347
-348    def __new__(cls, input_array, mom_in=None, mom_out=None):
-349        obj = np.asarray(input_array).view(cls)
-350        obj.mom_in = mom_in
-351        obj.mom_out = mom_out
-352        return obj
-353
-354    @property
-355    def g5H(self):
-356        """Gamma_5 hermitean conjugate
-357
-358        Uses the fact that the propagator is gamma5 hermitean, so just the
-359        in and out momenta of the propagator are exchanged.
-360        """
-361        return Npr_matrix(self,
-362                          mom_in=self.mom_out,
-363                          mom_out=self.mom_in)
-364
-365    def _propagate_mom(self, other, name):
-366        s_mom = getattr(self, name, None)
-367        o_mom = getattr(other, name, None)
-368        if s_mom is not None and o_mom is not None:
-369            if not np.allclose(s_mom, o_mom):
-370                raise Exception(name + ' does not match.')
-371        return o_mom if o_mom is not None else s_mom
-372
-373    def __matmul__(self, other):
-374        return self.__new__(Npr_matrix,
-375                            super().__matmul__(other),
-376                            self._propagate_mom(other, 'mom_in'),
-377                            self._propagate_mom(other, 'mom_out'))
-378
-379    def __array_finalize__(self, obj):
-380        if obj is None:
-381            return
-382        self.mom_in = getattr(obj, 'mom_in', None)
-383        self.mom_out = getattr(obj, 'mom_out', None)
+            
351class Npr_matrix(np.ndarray):
+352
+353    def __new__(cls, input_array, mom_in=None, mom_out=None):
+354        obj = np.asarray(input_array).view(cls)
+355        obj.mom_in = mom_in
+356        obj.mom_out = mom_out
+357        return obj
+358
+359    @property
+360    def g5H(self):
+361        """Gamma_5 hermitean conjugate
+362
+363        Uses the fact that the propagator is gamma5 hermitean, so just the
+364        in and out momenta of the propagator are exchanged.
+365        """
+366        return Npr_matrix(self,
+367                          mom_in=self.mom_out,
+368                          mom_out=self.mom_in)
+369
+370    def _propagate_mom(self, other, name):
+371        s_mom = getattr(self, name, None)
+372        o_mom = getattr(other, name, None)
+373        if s_mom is not None and o_mom is not None:
+374            if not np.allclose(s_mom, o_mom):
+375                raise Exception(name + ' does not match.')
+376        return o_mom if o_mom is not None else s_mom
+377
+378    def __matmul__(self, other):
+379        return self.__new__(Npr_matrix,
+380                            super().__matmul__(other),
+381                            self._propagate_mom(other, 'mom_in'),
+382                            self._propagate_mom(other, 'mom_out'))
+383
+384    def __array_finalize__(self, obj):
+385        if obj is None:
+386            return
+387        self.mom_in = getattr(obj, 'mom_in', None)
+388        self.mom_out = getattr(obj, 'mom_out', None)
 
@@ -1358,16 +1369,16 @@ ndarray.

-
354    @property
-355    def g5H(self):
-356        """Gamma_5 hermitean conjugate
-357
-358        Uses the fact that the propagator is gamma5 hermitean, so just the
-359        in and out momenta of the propagator are exchanged.
-360        """
-361        return Npr_matrix(self,
-362                          mom_in=self.mom_out,
-363                          mom_out=self.mom_in)
+            
359    @property
+360    def g5H(self):
+361        """Gamma_5 hermitean conjugate
+362
+363        Uses the fact that the propagator is gamma5 hermitean, so just the
+364        in and out momenta of the propagator are exchanged.
+365        """
+366        return Npr_matrix(self,
+367                          mom_in=self.mom_out,
+368                          mom_out=self.mom_in)
 
@@ -1391,49 +1402,49 @@ in and out momenta of the propagator are exchanged.

-
386def read_ExternalLeg_hd5(path, filestem, ens_id, idl=None):
-387    """Read hadrons ExternalLeg hdf5 file and output an array of CObs
-388
-389    Parameters
-390    ----------
-391    path : str
-392        path to the files to read
-393    filestem : str
-394        namestem of the files to read
-395    ens_id : str
-396        name of the ensemble, required for internal bookkeeping
-397    idl : range
-398        If specified only configurations in the given range are read in.
-399
-400    Returns
-401    -------
-402    result : Npr_matrix
-403        read Cobs-matrix
-404    """
-405
-406    files, idx = _get_files(path, filestem, idl)
-407
-408    mom = None
-409
-410    corr_data = []
-411    for hd5_file in files:
-412        file = h5py.File(path + '/' + hd5_file, "r")
-413        raw_data = file['ExternalLeg/corr'][0][0].view('complex')
-414        corr_data.append(raw_data)
-415        if mom is None:
-416            mom = np.array(str(file['ExternalLeg/info'].attrs['pIn'])[3:-2].strip().split(), dtype=float)
-417        file.close()
-418    corr_data = np.array(corr_data)
-419
-420    rolled_array = np.rollaxis(corr_data, 0, 5)
-421
-422    matrix = np.empty((rolled_array.shape[:-1]), dtype=object)
-423    for si, sj, ci, cj in np.ndindex(rolled_array.shape[:-1]):
-424        real = Obs([rolled_array[si, sj, ci, cj].real], [ens_id], idl=[idx])
-425        imag = Obs([rolled_array[si, sj, ci, cj].imag], [ens_id], idl=[idx])
-426        matrix[si, sj, ci, cj] = CObs(real, imag)
-427
-428    return Npr_matrix(matrix, mom_in=mom)
+            
391def read_ExternalLeg_hd5(path, filestem, ens_id, idl=None):
+392    """Read hadrons ExternalLeg hdf5 file and output an array of CObs
+393
+394    Parameters
+395    ----------
+396    path : str
+397        path to the files to read
+398    filestem : str
+399        namestem of the files to read
+400    ens_id : str
+401        name of the ensemble, required for internal bookkeeping
+402    idl : range
+403        If specified only configurations in the given range are read in.
+404
+405    Returns
+406    -------
+407    result : Npr_matrix
+408        read Cobs-matrix
+409    """
+410
+411    files, idx = _get_files(path, filestem, idl)
+412
+413    mom = None
+414
+415    corr_data = []
+416    for hd5_file in files:
+417        file = h5py.File(path + '/' + hd5_file, "r")
+418        raw_data = file['ExternalLeg/corr'][0][0].view('complex')
+419        corr_data.append(raw_data)
+420        if mom is None:
+421            mom = np.array(str(file['ExternalLeg/info'].attrs['pIn'])[3:-2].strip().split(), dtype=float)
+422        file.close()
+423    corr_data = np.array(corr_data)
+424
+425    rolled_array = np.rollaxis(corr_data, 0, 5)
+426
+427    matrix = np.empty((rolled_array.shape[:-1]), dtype=object)
+428    for si, sj, ci, cj in np.ndindex(rolled_array.shape[:-1]):
+429        real = Obs([rolled_array[si, sj, ci, cj].real], [ens_id], idl=[idx])
+430        imag = Obs([rolled_array[si, sj, ci, cj].imag], [ens_id], idl=[idx])
+431        matrix[si, sj, ci, cj] = CObs(real, imag)
+432
+433    return Npr_matrix(matrix, mom_in=mom)
 
@@ -1473,63 +1484,63 @@ read Cobs-matrix
-
431def read_Bilinear_hd5(path, filestem, ens_id, idl=None):
-432    """Read hadrons Bilinear hdf5 file and output an array of CObs
-433
-434    Parameters
-435    ----------
-436    path : str
-437        path to the files to read
-438    filestem : str
-439        namestem of the files to read
-440    ens_id : str
-441        name of the ensemble, required for internal bookkeeping
-442    idl : range
-443        If specified only configurations in the given range are read in.
-444
-445    Returns
-446    -------
-447    result_dict: dict[Npr_matrix]
-448        extracted Bilinears
-449    """
-450
-451    files, idx = _get_files(path, filestem, idl)
-452
-453    mom_in = None
-454    mom_out = None
+            
436def read_Bilinear_hd5(path, filestem, ens_id, idl=None):
+437    """Read hadrons Bilinear hdf5 file and output an array of CObs
+438
+439    Parameters
+440    ----------
+441    path : str
+442        path to the files to read
+443    filestem : str
+444        namestem of the files to read
+445    ens_id : str
+446        name of the ensemble, required for internal bookkeeping
+447    idl : range
+448        If specified only configurations in the given range are read in.
+449
+450    Returns
+451    -------
+452    result_dict: dict[Npr_matrix]
+453        extracted Bilinears
+454    """
 455
-456    corr_data = {}
-457    for hd5_file in files:
-458        file = h5py.File(path + '/' + hd5_file, "r")
-459        for i in range(16):
-460            name = file['Bilinear/Bilinear_' + str(i) + '/info'].attrs['gamma'][0].decode('UTF-8')
-461            if name not in corr_data:
-462                corr_data[name] = []
-463            raw_data = file['Bilinear/Bilinear_' + str(i) + '/corr'][0][0].view('complex')
-464            corr_data[name].append(raw_data)
-465            if mom_in is None:
-466                mom_in = np.array(str(file['Bilinear/Bilinear_' + str(i) + '/info'].attrs['pIn'])[3:-2].strip().split(), dtype=float)
-467            if mom_out is None:
-468                mom_out = np.array(str(file['Bilinear/Bilinear_' + str(i) + '/info'].attrs['pOut'])[3:-2].strip().split(), dtype=float)
-469
-470        file.close()
-471
-472    result_dict = {}
-473
-474    for key, data in corr_data.items():
-475        local_data = np.array(data)
+456    files, idx = _get_files(path, filestem, idl)
+457
+458    mom_in = None
+459    mom_out = None
+460
+461    corr_data = {}
+462    for hd5_file in files:
+463        file = h5py.File(path + '/' + hd5_file, "r")
+464        for i in range(16):
+465            name = file['Bilinear/Bilinear_' + str(i) + '/info'].attrs['gamma'][0].decode('UTF-8')
+466            if name not in corr_data:
+467                corr_data[name] = []
+468            raw_data = file['Bilinear/Bilinear_' + str(i) + '/corr'][0][0].view('complex')
+469            corr_data[name].append(raw_data)
+470            if mom_in is None:
+471                mom_in = np.array(str(file['Bilinear/Bilinear_' + str(i) + '/info'].attrs['pIn'])[3:-2].strip().split(), dtype=float)
+472            if mom_out is None:
+473                mom_out = np.array(str(file['Bilinear/Bilinear_' + str(i) + '/info'].attrs['pOut'])[3:-2].strip().split(), dtype=float)
+474
+475        file.close()
 476
-477        rolled_array = np.rollaxis(local_data, 0, 5)
+477    result_dict = {}
 478
-479        matrix = np.empty((rolled_array.shape[:-1]), dtype=object)
-480        for si, sj, ci, cj in np.ndindex(rolled_array.shape[:-1]):
-481            real = Obs([rolled_array[si, sj, ci, cj].real], [ens_id], idl=[idx])
-482            imag = Obs([rolled_array[si, sj, ci, cj].imag], [ens_id], idl=[idx])
-483            matrix[si, sj, ci, cj] = CObs(real, imag)
-484
-485        result_dict[key] = Npr_matrix(matrix, mom_in=mom_in, mom_out=mom_out)
-486
-487    return result_dict
+479    for key, data in corr_data.items():
+480        local_data = np.array(data)
+481
+482        rolled_array = np.rollaxis(local_data, 0, 5)
+483
+484        matrix = np.empty((rolled_array.shape[:-1]), dtype=object)
+485        for si, sj, ci, cj in np.ndindex(rolled_array.shape[:-1]):
+486            real = Obs([rolled_array[si, sj, ci, cj].real], [ens_id], idl=[idx])
+487            imag = Obs([rolled_array[si, sj, ci, cj].imag], [ens_id], idl=[idx])
+488            matrix[si, sj, ci, cj] = CObs(real, imag)
+489
+490        result_dict[key] = Npr_matrix(matrix, mom_in=mom_in, mom_out=mom_out)
+491
+492    return result_dict
 
@@ -1563,96 +1574,99 @@ extracted Bilinears
def - read_Fourquark_hd5(path, filestem, ens_id, idl=None, vertices=['VA', 'AV']): + read_Fourquark_hd5(path, filestem, ens_id, idl=None, vertices=None):
-
490def read_Fourquark_hd5(path, filestem, ens_id, idl=None, vertices=["VA", "AV"]):
-491    """Read hadrons FourquarkFullyConnected hdf5 file and output an array of CObs
-492
-493    Parameters
-494    ----------
-495    path : str
-496        path to the files to read
-497    filestem : str
-498        namestem of the files to read
-499    ens_id : str
-500        name of the ensemble, required for internal bookkeeping
-501    idl : range
-502        If specified only configurations in the given range are read in.
-503    vertices : list
-504        Vertex functions to be extracted.
-505
-506    Returns
-507    -------
-508    result_dict : dict
-509        extracted fourquark matrizes
-510    """
-511
-512    files, idx = _get_files(path, filestem, idl)
-513
-514    mom_in = None
-515    mom_out = None
+            
495def read_Fourquark_hd5(path, filestem, ens_id, idl=None, vertices=None):
+496    """Read hadrons FourquarkFullyConnected hdf5 file and output an array of CObs
+497
+498    Parameters
+499    ----------
+500    path : str
+501        path to the files to read
+502    filestem : str
+503        namestem of the files to read
+504    ens_id : str
+505        name of the ensemble, required for internal bookkeeping
+506    idl : range
+507        If specified only configurations in the given range are read in.
+508    vertices : list
+509        Vertex functions to be extracted.
+510
+511    Returns
+512    -------
+513    result_dict : dict
+514        extracted fourquark matrizes
+515    """
 516
-517    vertex_names = []
-518    for vertex in vertices:
-519        vertex_names += _get_lorentz_names(vertex)
-520
-521    corr_data = {}
-522
-523    tree = 'FourQuarkFullyConnected/FourQuarkFullyConnected_'
+517    if vertices is None:
+518        vertices = ["VA", "AV"]
+519
+520    files, idx = _get_files(path, filestem, idl)
+521
+522    mom_in = None
+523    mom_out = None
 524
-525    for hd5_file in files:
-526        file = h5py.File(path + '/' + hd5_file, "r")
-527
-528        for i in range(32):
-529            name = (file[tree + str(i) + '/info'].attrs['gammaA'][0].decode('UTF-8'), file[tree + str(i) + '/info'].attrs['gammaB'][0].decode('UTF-8'))
-530            if name in vertex_names:
-531                if name not in corr_data:
-532                    corr_data[name] = []
-533                raw_data = file[tree + str(i) + '/corr'][0][0].view('complex')
-534                corr_data[name].append(raw_data)
-535                if mom_in is None:
-536                    mom_in = np.array(str(file[tree + str(i) + '/info'].attrs['pIn'])[3:-2].strip().split(), dtype=float)
-537                if mom_out is None:
-538                    mom_out = np.array(str(file[tree + str(i) + '/info'].attrs['pOut'])[3:-2].strip().split(), dtype=float)
-539
-540        file.close()
-541
-542    intermediate_dict = {}
-543
-544    for vertex in vertices:
-545        lorentz_names = _get_lorentz_names(vertex)
-546        for v_name in lorentz_names:
-547            if v_name in [('SigmaXY', 'SigmaZT'),
-548                          ('SigmaXT', 'SigmaYZ'),
-549                          ('SigmaYZ', 'SigmaXT'),
-550                          ('SigmaZT', 'SigmaXY')]:
-551                sign = -1
-552            else:
-553                sign = 1
-554            if vertex not in intermediate_dict:
-555                intermediate_dict[vertex] = sign * np.array(corr_data[v_name])
-556            else:
-557                intermediate_dict[vertex] += sign * np.array(corr_data[v_name])
-558
-559    result_dict = {}
-560
-561    for key, data in intermediate_dict.items():
-562
-563        rolled_array = np.moveaxis(data, 0, 8)
-564
-565        matrix = np.empty((rolled_array.shape[:-1]), dtype=object)
-566        for index in np.ndindex(rolled_array.shape[:-1]):
-567            real = Obs([rolled_array[index].real], [ens_id], idl=[idx])
-568            imag = Obs([rolled_array[index].imag], [ens_id], idl=[idx])
-569            matrix[index] = CObs(real, imag)
+525    vertex_names = []
+526    for vertex in vertices:
+527        vertex_names += _get_lorentz_names(vertex)
+528
+529    corr_data = {}
+530
+531    tree = 'FourQuarkFullyConnected/FourQuarkFullyConnected_'
+532
+533    for hd5_file in files:
+534        file = h5py.File(path + '/' + hd5_file, "r")
+535
+536        for i in range(32):
+537            name = (file[tree + str(i) + '/info'].attrs['gammaA'][0].decode('UTF-8'), file[tree + str(i) + '/info'].attrs['gammaB'][0].decode('UTF-8'))
+538            if name in vertex_names:
+539                if name not in corr_data:
+540                    corr_data[name] = []
+541                raw_data = file[tree + str(i) + '/corr'][0][0].view('complex')
+542                corr_data[name].append(raw_data)
+543                if mom_in is None:
+544                    mom_in = np.array(str(file[tree + str(i) + '/info'].attrs['pIn'])[3:-2].strip().split(), dtype=float)
+545                if mom_out is None:
+546                    mom_out = np.array(str(file[tree + str(i) + '/info'].attrs['pOut'])[3:-2].strip().split(), dtype=float)
+547
+548        file.close()
+549
+550    intermediate_dict = {}
+551
+552    for vertex in vertices:
+553        lorentz_names = _get_lorentz_names(vertex)
+554        for v_name in lorentz_names:
+555            if v_name in [('SigmaXY', 'SigmaZT'),
+556                          ('SigmaXT', 'SigmaYZ'),
+557                          ('SigmaYZ', 'SigmaXT'),
+558                          ('SigmaZT', 'SigmaXY')]:
+559                sign = -1
+560            else:
+561                sign = 1
+562            if vertex not in intermediate_dict:
+563                intermediate_dict[vertex] = sign * np.array(corr_data[v_name])
+564            else:
+565                intermediate_dict[vertex] += sign * np.array(corr_data[v_name])
+566
+567    result_dict = {}
+568
+569    for key, data in intermediate_dict.items():
 570
-571        result_dict[key] = Npr_matrix(matrix, mom_in=mom_in, mom_out=mom_out)
+571        rolled_array = np.moveaxis(data, 0, 8)
 572
-573    return result_dict
+573        matrix = np.empty((rolled_array.shape[:-1]), dtype=object)
+574        for index in np.ndindex(rolled_array.shape[:-1]):
+575            real = Obs([rolled_array[index].real], [ens_id], idl=[idx])
+576            imag = Obs([rolled_array[index].imag], [ens_id], idl=[idx])
+577            matrix[index] = CObs(real, imag)
+578
+579        result_dict[key] = Npr_matrix(matrix, mom_in=mom_in, mom_out=mom_out)
+580
+581    return result_dict
 
diff --git a/docs/pyerrors/input/json.html b/docs/pyerrors/input/json.html index cef5e9a5..4377c9be 100644 --- a/docs/pyerrors/input/json.html +++ b/docs/pyerrors/input/json.html @@ -91,774 +91,776 @@ -
  1import rapidjson as json
-  2import gzip
-  3import getpass
-  4import socket
-  5import datetime
-  6import platform
+                        
  1import datetime
+  2import getpass
+  3import gzip
+  4import platform
+  5import re
+  6import socket
   7import warnings
-  8import re
+  8
   9import numpy as np
- 10from ..obs import Obs
- 11from ..covobs import Covobs
- 12from ..correlators import Corr
- 13from ..misc import _assert_equal_properties
- 14from .. import version as pyerrorsversion
- 15
- 16
- 17def create_json_string(ol, description='', indent=1):
- 18    """Generate the string for the export of a list of Obs or structures containing Obs
- 19    to a .json(.gz) file
- 20
- 21    Parameters
- 22    ----------
- 23    ol : list
- 24        List of objects that will be exported. At the moment, these objects can be
- 25        either of: Obs, list, numpy.ndarray, Corr.
- 26        All Obs inside a structure have to be defined on the same set of configurations.
- 27    description : str
- 28        Optional string that describes the contents of the json file.
- 29    indent : int
- 30        Specify the indentation level of the json file. None or 0 is permissible and
- 31        saves disk space.
- 32
- 33    Returns
- 34    -------
- 35    json_string : str
- 36        String for export to .json(.gz) file
- 37    """
- 38
- 39    def _gen_data_d_from_list(ol):
- 40        dl = []
- 41        No = len(ol)
- 42        for name in ol[0].mc_names:
- 43            ed = {}
- 44            ed['id'] = name
- 45            ed['replica'] = []
- 46            for r_name in ol[0].e_content[name]:
- 47                rd = {}
- 48                rd['name'] = r_name
- 49                rd['deltas'] = []
- 50                offsets = [o.r_values[r_name] - o.value for o in ol]
- 51                deltas = np.column_stack([ol[oi].deltas[r_name] + offsets[oi] for oi in range(No)])
- 52                for i in range(len(ol[0].idl[r_name])):
- 53                    rd['deltas'].append([ol[0].idl[r_name][i]])
- 54                    rd['deltas'][-1] += deltas[i].tolist()
- 55                ed['replica'].append(rd)
- 56            dl.append(ed)
- 57        return dl
- 58
- 59    def _gen_cdata_d_from_list(ol):
- 60        dl = []
- 61        for name in ol[0].cov_names:
- 62            ed = {}
- 63            ed['id'] = name
- 64            ed['layout'] = str(ol[0].covobs[name].cov.shape).lstrip('(').rstrip(')').rstrip(',')
- 65            ed['cov'] = list(np.ravel(ol[0].covobs[name].cov))
- 66            ncov = ol[0].covobs[name].cov.shape[0]
- 67            ed['grad'] = []
- 68            for i in range(ncov):
- 69                ed['grad'].append([])
- 70                for o in ol:
- 71                    ed['grad'][-1].append(o.covobs[name].grad[i][0])
- 72            dl.append(ed)
- 73        return dl
- 74
- 75    def write_Obs_to_dict(o):
- 76        d = {}
- 77        d['type'] = 'Obs'
- 78        d['layout'] = '1'
- 79        if o.tag:
- 80            d['tag'] = [o.tag]
- 81        if o.reweighted:
- 82            d['reweighted'] = o.reweighted
- 83        d['value'] = [o.value]
- 84        data = _gen_data_d_from_list([o])
- 85        if len(data) > 0:
- 86            d['data'] = data
- 87        cdata = _gen_cdata_d_from_list([o])
- 88        if len(cdata) > 0:
- 89            d['cdata'] = cdata
- 90        return d
- 91
- 92    def write_List_to_dict(ol):
- 93        _assert_equal_properties(ol)
- 94        d = {}
- 95        d['type'] = 'List'
- 96        d['layout'] = '%d' % len(ol)
- 97        taglist = [o.tag for o in ol]
- 98        if np.any([tag is not None for tag in taglist]):
- 99            d['tag'] = taglist
-100        if ol[0].reweighted:
-101            d['reweighted'] = ol[0].reweighted
-102        d['value'] = [o.value for o in ol]
-103        data = _gen_data_d_from_list(ol)
-104        if len(data) > 0:
-105            d['data'] = data
-106        cdata = _gen_cdata_d_from_list(ol)
-107        if len(cdata) > 0:
-108            d['cdata'] = cdata
-109        return d
-110
-111    def write_Array_to_dict(oa):
-112        ol = np.ravel(oa)
-113        _assert_equal_properties(ol)
-114        d = {}
-115        d['type'] = 'Array'
-116        d['layout'] = str(oa.shape).lstrip('(').rstrip(')').rstrip(',')
-117        taglist = [o.tag for o in ol]
-118        if np.any([tag is not None for tag in taglist]):
-119            d['tag'] = taglist
-120        if ol[0].reweighted:
-121            d['reweighted'] = ol[0].reweighted
-122        d['value'] = [o.value for o in ol]
-123        data = _gen_data_d_from_list(ol)
-124        if len(data) > 0:
-125            d['data'] = data
-126        cdata = _gen_cdata_d_from_list(ol)
-127        if len(cdata) > 0:
-128            d['cdata'] = cdata
-129        return d
-130
-131    def _nan_Obs_like(obs):
-132        samples = []
-133        names = []
-134        idl = []
-135        for key, value in obs.idl.items():
-136            samples.append(np.array([np.nan] * len(value)))
-137            names.append(key)
-138            idl.append(value)
-139        my_obs = Obs(samples, names, idl, means=[np.nan for n in names])
-140        my_obs._value = np.nan
-141        my_obs._covobs = obs._covobs
-142        for name in obs._covobs:
-143            my_obs.names.append(name)
-144        my_obs.reweighted = obs.reweighted
-145        return my_obs
-146
-147    def write_Corr_to_dict(my_corr):
-148        first_not_none = next(i for i, j in enumerate(my_corr.content) if np.all(j))
-149        dummy_array = np.empty((my_corr.N, my_corr.N), dtype=object)
-150        dummy_array[:] = _nan_Obs_like(my_corr.content[first_not_none].ravel()[0])
-151        content = [o if o is not None else dummy_array for o in my_corr.content]
-152        dat = write_Array_to_dict(np.array(content, dtype=object))
-153        dat['type'] = 'Corr'
-154        corr_meta_data = str(my_corr.tag)
-155        if 'tag' in dat.keys():
-156            dat['tag'].append(corr_meta_data)
-157        else:
-158            dat['tag'] = [corr_meta_data]
-159        taglist = dat['tag']
-160        dat['tag'] = {}  # tag is now a dictionary, that contains the previous taglist in the key "tag"
-161        dat['tag']['tag'] = taglist
-162        if my_corr.prange is not None:
-163            dat['tag']['prange'] = my_corr.prange
-164        return dat
-165
-166    if not isinstance(ol, list):
-167        ol = [ol]
-168
-169    d = {}
-170    d['program'] = 'pyerrors %s' % (pyerrorsversion.__version__)
-171    d['version'] = '1.1'
-172    d['who'] = getpass.getuser()
-173    d['date'] = datetime.datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S %z')
-174    d['host'] = socket.gethostname() + ', ' + platform.platform()
-175
-176    if description:
-177        d['description'] = description
-178
-179    d['obsdata'] = []
-180    for io in ol:
-181        if isinstance(io, Obs):
-182            d['obsdata'].append(write_Obs_to_dict(io))
-183        elif isinstance(io, list):
-184            d['obsdata'].append(write_List_to_dict(io))
-185        elif isinstance(io, np.ndarray):
-186            d['obsdata'].append(write_Array_to_dict(io))
-187        elif isinstance(io, Corr):
-188            d['obsdata'].append(write_Corr_to_dict(io))
-189        else:
-190            raise Exception("Unkown datatype.")
-191
-192    def _jsonifier(obj):
-193        if isinstance(obj, dict):
-194            result = {}
-195            for key in obj:
-196                if key is True:
-197                    result['true'] = obj[key]
-198                elif key is False:
-199                    result['false'] = obj[key]
-200                elif key is None:
-201                    result['null'] = obj[key]
-202                elif isinstance(key, (int, float, np.floating, np.integer)):
-203                    result[str(key)] = obj[key]
-204                else:
-205                    raise TypeError('keys must be str, int, float, bool or None')
-206            return result
-207        elif isinstance(obj, np.integer):
-208            return int(obj)
-209        elif isinstance(obj, np.floating):
-210            return float(obj)
-211        else:
-212            raise ValueError('%r is not JSON serializable' % (obj,))
-213
-214    if indent:
-215        return json.dumps(d, indent=indent, ensure_ascii=False, default=_jsonifier, write_mode=json.WM_SINGLE_LINE_ARRAY)
-216    else:
-217        return json.dumps(d, indent=indent, ensure_ascii=False, default=_jsonifier, write_mode=json.WM_COMPACT)
-218
-219
-220def dump_to_json(ol, fname, description='', indent=1, gz=True):
-221    """Export a list of Obs or structures containing Obs to a .json(.gz) file.
-222    Dict keys that are not JSON-serializable such as floats are converted to strings.
-223
-224    Parameters
-225    ----------
-226    ol : list
-227        List of objects that will be exported. At the moment, these objects can be
-228        either of: Obs, list, numpy.ndarray, Corr.
-229        All Obs inside a structure have to be defined on the same set of configurations.
-230    fname : str
-231        Filename of the output file.
-232    description : str
-233        Optional string that describes the contents of the json file.
-234    indent : int
-235        Specify the indentation level of the json file. None or 0 is permissible and
-236        saves disk space.
-237    gz : bool
-238        If True, the output is a gzipped json. If False, the output is a json file.
-239
-240    Returns
-241    -------
-242    Null
-243    """
-244
-245    jsonstring = create_json_string(ol, description, indent)
+ 10import rapidjson as json
+ 11
+ 12from .. import version as pyerrorsversion
+ 13from ..correlators import Corr
+ 14from ..covobs import Covobs
+ 15from ..misc import _assert_equal_properties
+ 16from ..obs import Obs
+ 17
+ 18
+ 19def create_json_string(ol, description='', indent=1):
+ 20    """Generate the string for the export of a list of Obs or structures containing Obs
+ 21    to a .json(.gz) file
+ 22
+ 23    Parameters
+ 24    ----------
+ 25    ol : list
+ 26        List of objects that will be exported. At the moment, these objects can be
+ 27        either of: Obs, list, numpy.ndarray, Corr.
+ 28        All Obs inside a structure have to be defined on the same set of configurations.
+ 29    description : str
+ 30        Optional string that describes the contents of the json file.
+ 31    indent : int
+ 32        Specify the indentation level of the json file. None or 0 is permissible and
+ 33        saves disk space.
+ 34
+ 35    Returns
+ 36    -------
+ 37    json_string : str
+ 38        String for export to .json(.gz) file
+ 39    """
+ 40
+ 41    def _gen_data_d_from_list(ol):
+ 42        dl = []
+ 43        No = len(ol)
+ 44        for name in ol[0].mc_names:
+ 45            ed = {}
+ 46            ed['id'] = name
+ 47            ed['replica'] = []
+ 48            for r_name in ol[0].e_content[name]:
+ 49                rd = {}
+ 50                rd['name'] = r_name
+ 51                rd['deltas'] = []
+ 52                offsets = [o.r_values[r_name] - o.value for o in ol]
+ 53                deltas = np.column_stack([ol[oi].deltas[r_name] + offsets[oi] for oi in range(No)])
+ 54                for i in range(len(ol[0].idl[r_name])):
+ 55                    rd['deltas'].append([ol[0].idl[r_name][i]])
+ 56                    rd['deltas'][-1] += deltas[i].tolist()
+ 57                ed['replica'].append(rd)
+ 58            dl.append(ed)
+ 59        return dl
+ 60
+ 61    def _gen_cdata_d_from_list(ol):
+ 62        dl = []
+ 63        for name in ol[0].cov_names:
+ 64            ed = {}
+ 65            ed['id'] = name
+ 66            ed['layout'] = str(ol[0].covobs[name].cov.shape).lstrip('(').rstrip(')').rstrip(',')
+ 67            ed['cov'] = list(np.ravel(ol[0].covobs[name].cov))
+ 68            ncov = ol[0].covobs[name].cov.shape[0]
+ 69            ed['grad'] = []
+ 70            for i in range(ncov):
+ 71                ed['grad'].append([])
+ 72                for o in ol:
+ 73                    ed['grad'][-1].append(o.covobs[name].grad[i][0])
+ 74            dl.append(ed)
+ 75        return dl
+ 76
+ 77    def write_Obs_to_dict(o):
+ 78        d = {}
+ 79        d['type'] = 'Obs'
+ 80        d['layout'] = '1'
+ 81        if o.tag:
+ 82            d['tag'] = [o.tag]
+ 83        if o.reweighted:
+ 84            d['reweighted'] = o.reweighted
+ 85        d['value'] = [o.value]
+ 86        data = _gen_data_d_from_list([o])
+ 87        if len(data) > 0:
+ 88            d['data'] = data
+ 89        cdata = _gen_cdata_d_from_list([o])
+ 90        if len(cdata) > 0:
+ 91            d['cdata'] = cdata
+ 92        return d
+ 93
+ 94    def write_List_to_dict(ol):
+ 95        _assert_equal_properties(ol)
+ 96        d = {}
+ 97        d['type'] = 'List'
+ 98        d['layout'] = f'{len(ol)}'
+ 99        taglist = [o.tag for o in ol]
+100        if np.any([tag is not None for tag in taglist]):
+101            d['tag'] = taglist
+102        if ol[0].reweighted:
+103            d['reweighted'] = ol[0].reweighted
+104        d['value'] = [o.value for o in ol]
+105        data = _gen_data_d_from_list(ol)
+106        if len(data) > 0:
+107            d['data'] = data
+108        cdata = _gen_cdata_d_from_list(ol)
+109        if len(cdata) > 0:
+110            d['cdata'] = cdata
+111        return d
+112
+113    def write_Array_to_dict(oa):
+114        ol = np.ravel(oa)
+115        _assert_equal_properties(ol)
+116        d = {}
+117        d['type'] = 'Array'
+118        d['layout'] = str(oa.shape).lstrip('(').rstrip(')').rstrip(',')
+119        taglist = [o.tag for o in ol]
+120        if np.any([tag is not None for tag in taglist]):
+121            d['tag'] = taglist
+122        if ol[0].reweighted:
+123            d['reweighted'] = ol[0].reweighted
+124        d['value'] = [o.value for o in ol]
+125        data = _gen_data_d_from_list(ol)
+126        if len(data) > 0:
+127            d['data'] = data
+128        cdata = _gen_cdata_d_from_list(ol)
+129        if len(cdata) > 0:
+130            d['cdata'] = cdata
+131        return d
+132
+133    def _nan_Obs_like(obs):
+134        samples = []
+135        names = []
+136        idl = []
+137        for key, value in obs.idl.items():
+138            samples.append(np.array([np.nan] * len(value)))
+139            names.append(key)
+140            idl.append(value)
+141        my_obs = Obs(samples, names, idl, means=[np.nan for n in names])
+142        my_obs._value = np.nan
+143        my_obs._covobs = obs._covobs
+144        for name in obs._covobs:
+145            my_obs.names.append(name)
+146        my_obs.reweighted = obs.reweighted
+147        return my_obs
+148
+149    def write_Corr_to_dict(my_corr):
+150        first_not_none = next(i for i, j in enumerate(my_corr.content) if np.all(j))
+151        dummy_array = np.empty((my_corr.N, my_corr.N), dtype=object)
+152        dummy_array[:] = _nan_Obs_like(my_corr.content[first_not_none].ravel()[0])
+153        content = [o if o is not None else dummy_array for o in my_corr.content]
+154        dat = write_Array_to_dict(np.array(content, dtype=object))
+155        dat['type'] = 'Corr'
+156        corr_meta_data = str(my_corr.tag)
+157        if 'tag' in dat.keys():
+158            dat['tag'].append(corr_meta_data)
+159        else:
+160            dat['tag'] = [corr_meta_data]
+161        taglist = dat['tag']
+162        dat['tag'] = {}  # tag is now a dictionary, that contains the previous taglist in the key "tag"
+163        dat['tag']['tag'] = taglist
+164        if my_corr.prange is not None:
+165            dat['tag']['prange'] = my_corr.prange
+166        return dat
+167
+168    if not isinstance(ol, list):
+169        ol = [ol]
+170
+171    d = {}
+172    d['program'] = f'pyerrors {pyerrorsversion.__version__}'
+173    d['version'] = '1.1'
+174    d['who'] = getpass.getuser()
+175    d['date'] = datetime.datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S %z')
+176    d['host'] = socket.gethostname() + ', ' + platform.platform()
+177
+178    if description:
+179        d['description'] = description
+180
+181    d['obsdata'] = []
+182    for io in ol:
+183        if isinstance(io, Obs):
+184            d['obsdata'].append(write_Obs_to_dict(io))
+185        elif isinstance(io, list):
+186            d['obsdata'].append(write_List_to_dict(io))
+187        elif isinstance(io, np.ndarray):
+188            d['obsdata'].append(write_Array_to_dict(io))
+189        elif isinstance(io, Corr):
+190            d['obsdata'].append(write_Corr_to_dict(io))
+191        else:
+192            raise Exception("Unkown datatype.")
+193
+194    def _jsonifier(obj):
+195        if isinstance(obj, dict):
+196            result = {}
+197            for key in obj:
+198                if key is True:
+199                    result['true'] = obj[key]
+200                elif key is False:
+201                    result['false'] = obj[key]
+202                elif key is None:
+203                    result['null'] = obj[key]
+204                elif isinstance(key, (int, float, np.floating, np.integer)):
+205                    result[str(key)] = obj[key]
+206                else:
+207                    raise TypeError('keys must be str, int, float, bool or None')
+208            return result
+209        elif isinstance(obj, np.integer):
+210            return int(obj)
+211        elif isinstance(obj, np.floating):
+212            return float(obj)
+213        else:
+214            raise ValueError(f'{obj!r} is not JSON serializable')
+215
+216    if indent:
+217        return json.dumps(d, indent=indent, ensure_ascii=False, default=_jsonifier, write_mode=json.WM_SINGLE_LINE_ARRAY)
+218    else:
+219        return json.dumps(d, indent=indent, ensure_ascii=False, default=_jsonifier, write_mode=json.WM_COMPACT)
+220
+221
+222def dump_to_json(ol, fname, description='', indent=1, gz=True):
+223    """Export a list of Obs or structures containing Obs to a .json(.gz) file.
+224    Dict keys that are not JSON-serializable such as floats are converted to strings.
+225
+226    Parameters
+227    ----------
+228    ol : list
+229        List of objects that will be exported. At the moment, these objects can be
+230        either of: Obs, list, numpy.ndarray, Corr.
+231        All Obs inside a structure have to be defined on the same set of configurations.
+232    fname : str
+233        Filename of the output file.
+234    description : str
+235        Optional string that describes the contents of the json file.
+236    indent : int
+237        Specify the indentation level of the json file. None or 0 is permissible and
+238        saves disk space.
+239    gz : bool
+240        If True, the output is a gzipped json. If False, the output is a json file.
+241
+242    Returns
+243    -------
+244    Null
+245    """
 246
-247    if not fname.endswith('.json') and not fname.endswith('.gz'):
-248        fname += '.json'
-249
-250    if gz:
-251        if not fname.endswith('.gz'):
-252            fname += '.gz'
-253
-254        fp = gzip.open(fname, 'wb')
-255        fp.write(jsonstring.encode('utf-8'))
-256    else:
-257        fp = open(fname, 'w', encoding='utf-8')
-258        fp.write(jsonstring)
-259    fp.close()
-260
-261
-262def _parse_json_dict(json_dict, verbose=True, full_output=False):
-263    """Reconstruct a list of Obs or structures containing Obs from a dict that
-264    was built out of a json string.
-265
-266    The following structures are supported: Obs, list, numpy.ndarray, Corr
-267    If the list contains only one element, it is unpacked from the list.
-268
-269    Parameters
-270    ----------
-271    json_string : str
-272        json string containing the data.
-273    verbose : bool
-274        Print additional information that was written to the file.
-275    full_output : bool
-276        If True, a dict containing auxiliary information and the data is returned.
-277        If False, only the data is returned.
-278
-279    Returns
-280    -------
-281    result : list[Obs]
-282        reconstructed list of observables from the json string
-283    or
-284    result : Obs
-285        only one observable if the list only has one entry
-286    or
-287    result : dict
-288        if full_output=True
-289    """
-290
-291    def _gen_obsd_from_datad(d):
-292        retd = {}
-293        if d:
-294            retd['names'] = []
-295            retd['idl'] = []
-296            retd['deltas'] = []
-297            for ens in d:
-298                for rep in ens['replica']:
-299                    rep_name = rep['name']
-300                    if len(rep_name) > len(ens["id"]):
-301                        if rep_name[len(ens["id"])] != "|":
-302                            tmp_list = list(rep_name)
-303                            tmp_list = tmp_list[:len(ens["id"])] + ["|"] + tmp_list[len(ens["id"]):]
-304                            rep_name = ''.join(tmp_list)
-305                    retd['names'].append(rep_name)
-306                    retd['idl'].append([di[0] for di in rep['deltas']])
-307                    retd['deltas'].append(np.array([di[1:] for di in rep['deltas']]))
-308        return retd
-309
-310    def _gen_covobsd_from_cdatad(d):
-311        retd = {}
-312        for ens in d:
-313            retl = []
-314            name = ens['id']
-315            layouts = ens.get('layout', '1').strip()
-316            layout = [int(ls.strip()) for ls in layouts.split(',') if len(ls) > 0]
-317            cov = np.reshape(ens['cov'], layout)
-318            grad = ens['grad']
-319            nobs = len(grad[0])
-320            for i in range(nobs):
-321                retl.append({'name': name, 'cov': cov, 'grad': [g[i] for g in grad]})
-322            retd[name] = retl
-323        return retd
-324
-325    def get_Obs_from_dict(o):
-326        layouts = o.get('layout', '1').strip()
-327        if layouts != '1':
-328            raise Exception("layout is %s has to be 1 for type Obs." % (layouts), RuntimeWarning)
-329
-330        values = o['value']
-331        od = _gen_obsd_from_datad(o.get('data', {}))
-332        cd = _gen_covobsd_from_cdatad(o.get('cdata', {}))
-333
-334        if od:
-335            r_offsets = [np.average([ddi[0] for ddi in di]) for di in od['deltas']]
-336            ret = Obs([np.array([ddi[0] for ddi in od['deltas'][i]]) - r_offsets[i] for i in range(len(od['deltas']))], od['names'], idl=od['idl'], means=[ro + values[0] for ro in r_offsets])
-337            ret._value = values[0]
-338        else:
-339            ret = Obs([], [], means=[])
-340            ret._value = values[0]
-341        for name in cd:
-342            co = cd[name][0]
-343            ret._covobs[name] = Covobs(None, co['cov'], co['name'], grad=co['grad'])
-344            ret.names.append(co['name'])
-345
-346        ret.reweighted = o.get('reweighted', False)
-347        ret.tag = o.get('tag', [None])[0]
-348        return ret
-349
-350    def get_List_from_dict(o):
-351        layouts = o.get('layout', '1').strip()
-352        layout = int(layouts)
-353        values = o['value']
-354        od = _gen_obsd_from_datad(o.get('data', {}))
-355        cd = _gen_covobsd_from_cdatad(o.get('cdata', {}))
-356
-357        ret = []
-358        taglist = o.get('tag', layout * [None])
-359        for i in range(layout):
-360            if od:
-361                r_offsets = np.array([np.average(di[:, i]) for di in od['deltas']])
-362                ret.append(Obs([od['deltas'][j][:, i] - r_offsets[j] for j in range(len(od['deltas']))], od['names'], idl=od['idl'], means=[ro + values[i] for ro in r_offsets]))
-363                ret[-1]._value = values[i]
-364            else:
-365                ret.append(Obs([], [], means=[]))
-366                ret[-1]._value = values[i]
-367                print('Created Obs with means= ', values[i])
-368            for name in cd:
-369                co = cd[name][i]
-370                ret[-1]._covobs[name] = Covobs(None, co['cov'], co['name'], grad=co['grad'])
-371                ret[-1].names.append(co['name'])
-372
-373            ret[-1].reweighted = o.get('reweighted', False)
-374            ret[-1].tag = taglist[i]
-375        return ret
-376
-377    def get_Array_from_dict(o):
-378        layouts = o.get('layout', '1').strip()
-379        layout = [int(ls.strip()) for ls in layouts.split(',') if len(ls) > 0]
-380        N = np.prod(layout)
-381        values = o['value']
-382        od = _gen_obsd_from_datad(o.get('data', {}))
-383        cd = _gen_covobsd_from_cdatad(o.get('cdata', {}))
-384
-385        ret = []
-386        taglist = o.get('tag', N * [None])
-387        for i in range(N):
-388            if od:
-389                r_offsets = np.array([np.average(di[:, i]) for di in od['deltas']])
-390                ret.append(Obs([od['deltas'][j][:, i] - r_offsets[j] for j in range(len(od['deltas']))], od['names'], idl=od['idl'], means=[ro + values[i] for ro in r_offsets]))
-391                ret[-1]._value = values[i]
-392            else:
-393                ret.append(Obs([], [], means=[]))
-394                ret[-1]._value = values[i]
-395            for name in cd:
-396                co = cd[name][i]
-397                ret[-1]._covobs[name] = Covobs(None, co['cov'], co['name'], grad=co['grad'])
-398                ret[-1].names.append(co['name'])
-399            ret[-1].reweighted = o.get('reweighted', False)
-400            ret[-1].tag = taglist[i]
-401        return np.reshape(ret, layout)
-402
-403    def get_Corr_from_dict(o):
-404        if isinstance(o.get('tag'), list):  # supports the old way
-405            taglist = o.get('tag')  # This had to be modified to get the taglist from the dictionary
-406            temp_prange = None
-407        elif isinstance(o.get('tag'), dict):
-408            tagdic = o.get('tag')
-409            taglist = tagdic['tag']
-410            if 'prange' in tagdic:
-411                temp_prange = tagdic['prange']
-412            else:
-413                temp_prange = None
-414        else:
-415            raise Exception("The tag is not a list or dict")
-416
-417        corr_tag = taglist[-1]
-418        tmp_o = o
-419        tmp_o['tag'] = taglist[:-1]
-420        if len(tmp_o['tag']) == 0:
-421            del tmp_o['tag']
-422        dat = get_Array_from_dict(tmp_o)
-423        my_corr = Corr([None if np.isnan(o.ravel()[0].value) else o for o in list(dat)])
-424        if corr_tag != 'None':
-425            my_corr.tag = corr_tag
-426
-427        my_corr.prange = temp_prange
-428        return my_corr
-429
-430    prog = json_dict.get('program', '')
-431    version = json_dict.get('version', '')
-432    who = json_dict.get('who', '')
-433    date = json_dict.get('date', '')
-434    host = json_dict.get('host', '')
-435    if prog and verbose:
-436        print('Data has been written using %s.' % (prog))
-437    if version and verbose:
-438        print('Format version %s' % (version))
-439    if np.any([who, date, host] and verbose):
-440        print('Written by %s on %s on host %s' % (who, date, host))
-441    description = json_dict.get('description', '')
-442    if description and verbose:
-443        print()
-444        print('Description: ', description)
-445    obsdata = json_dict['obsdata']
-446    ol = []
-447    for io in obsdata:
-448        if io['type'] == 'Obs':
-449            ol.append(get_Obs_from_dict(io))
-450        elif io['type'] == 'List':
-451            ol.append(get_List_from_dict(io))
-452        elif io['type'] == 'Array':
-453            ol.append(get_Array_from_dict(io))
-454        elif io['type'] == 'Corr':
-455            ol.append(get_Corr_from_dict(io))
-456        else:
-457            raise Exception("Unknown datatype.")
-458
-459    if full_output:
-460        retd = {}
-461        retd['program'] = prog
-462        retd['version'] = version
-463        retd['who'] = who
-464        retd['date'] = date
-465        retd['host'] = host
-466        retd['description'] = description
-467        retd['obsdata'] = ol
-468
-469        return retd
-470    else:
-471        if len(obsdata) == 1:
-472            ol = ol[0]
-473
-474        return ol
+247    jsonstring = create_json_string(ol, description, indent)
+248
+249    if not fname.endswith('.json') and not fname.endswith('.gz'):
+250        fname += '.json'
+251
+252    if gz:
+253        if not fname.endswith('.gz'):
+254            fname += '.gz'
+255
+256        fp = gzip.open(fname, 'wb')
+257        fp.write(jsonstring.encode('utf-8'))
+258    else:
+259        fp = open(fname, 'w', encoding='utf-8')
+260        fp.write(jsonstring)
+261    fp.close()
+262
+263
+264def _parse_json_dict(json_dict, verbose=True, full_output=False):
+265    """Reconstruct a list of Obs or structures containing Obs from a dict that
+266    was built out of a json string.
+267
+268    The following structures are supported: Obs, list, numpy.ndarray, Corr
+269    If the list contains only one element, it is unpacked from the list.
+270
+271    Parameters
+272    ----------
+273    json_string : str
+274        json string containing the data.
+275    verbose : bool
+276        Print additional information that was written to the file.
+277    full_output : bool
+278        If True, a dict containing auxiliary information and the data is returned.
+279        If False, only the data is returned.
+280
+281    Returns
+282    -------
+283    result : list[Obs]
+284        reconstructed list of observables from the json string
+285    or
+286    result : Obs
+287        only one observable if the list only has one entry
+288    or
+289    result : dict
+290        if full_output=True
+291    """
+292
+293    def _gen_obsd_from_datad(d):
+294        retd = {}
+295        if d:
+296            retd['names'] = []
+297            retd['idl'] = []
+298            retd['deltas'] = []
+299            for ens in d:
+300                for rep in ens['replica']:
+301                    rep_name = rep['name']
+302                    if len(rep_name) > len(ens["id"]):
+303                        if rep_name[len(ens["id"])] != "|":
+304                            tmp_list = list(rep_name)
+305                            tmp_list = [*tmp_list[:len(ens["id"])], "|", *tmp_list[len(ens["id"]):]]
+306                            rep_name = ''.join(tmp_list)
+307                    retd['names'].append(rep_name)
+308                    retd['idl'].append([di[0] for di in rep['deltas']])
+309                    retd['deltas'].append(np.array([di[1:] for di in rep['deltas']]))
+310        return retd
+311
+312    def _gen_covobsd_from_cdatad(d):
+313        retd = {}
+314        for ens in d:
+315            retl = []
+316            name = ens['id']
+317            layouts = ens.get('layout', '1').strip()
+318            layout = [int(ls.strip()) for ls in layouts.split(',') if len(ls) > 0]
+319            cov = np.reshape(ens['cov'], layout)
+320            grad = ens['grad']
+321            nobs = len(grad[0])
+322            for i in range(nobs):
+323                retl.append({'name': name, 'cov': cov, 'grad': [g[i] for g in grad]})
+324            retd[name] = retl
+325        return retd
+326
+327    def get_Obs_from_dict(o):
+328        layouts = o.get('layout', '1').strip()
+329        if layouts != '1':
+330            raise Exception(f"layout is {layouts} has to be 1 for type Obs.", RuntimeWarning)
+331
+332        values = o['value']
+333        od = _gen_obsd_from_datad(o.get('data', {}))
+334        cd = _gen_covobsd_from_cdatad(o.get('cdata', {}))
+335
+336        if od:
+337            r_offsets = [np.average([ddi[0] for ddi in di]) for di in od['deltas']]
+338            ret = Obs([np.array([ddi[0] for ddi in od['deltas'][i]]) - r_offsets[i] for i in range(len(od['deltas']))], od['names'], idl=od['idl'], means=[ro + values[0] for ro in r_offsets])
+339            ret._value = values[0]
+340        else:
+341            ret = Obs([], [], means=[])
+342            ret._value = values[0]
+343        for name in cd:
+344            co = cd[name][0]
+345            ret._covobs[name] = Covobs(None, co['cov'], co['name'], grad=co['grad'])
+346            ret.names.append(co['name'])
+347
+348        ret.reweighted = o.get('reweighted', False)
+349        ret.tag = o.get('tag', [None])[0]
+350        return ret
+351
+352    def get_List_from_dict(o):
+353        layouts = o.get('layout', '1').strip()
+354        layout = int(layouts)
+355        values = o['value']
+356        od = _gen_obsd_from_datad(o.get('data', {}))
+357        cd = _gen_covobsd_from_cdatad(o.get('cdata', {}))
+358
+359        ret = []
+360        taglist = o.get('tag', layout * [None])
+361        for i in range(layout):
+362            if od:
+363                r_offsets = np.array([np.average(di[:, i]) for di in od['deltas']])
+364                ret.append(Obs([od['deltas'][j][:, i] - r_offsets[j] for j in range(len(od['deltas']))], od['names'], idl=od['idl'], means=[ro + values[i] for ro in r_offsets]))
+365                ret[-1]._value = values[i]
+366            else:
+367                ret.append(Obs([], [], means=[]))
+368                ret[-1]._value = values[i]
+369                print('Created Obs with means= ', values[i])
+370            for name in cd:
+371                co = cd[name][i]
+372                ret[-1]._covobs[name] = Covobs(None, co['cov'], co['name'], grad=co['grad'])
+373                ret[-1].names.append(co['name'])
+374
+375            ret[-1].reweighted = o.get('reweighted', False)
+376            ret[-1].tag = taglist[i]
+377        return ret
+378
+379    def get_Array_from_dict(o):
+380        layouts = o.get('layout', '1').strip()
+381        layout = [int(ls.strip()) for ls in layouts.split(',') if len(ls) > 0]
+382        N = np.prod(layout)
+383        values = o['value']
+384        od = _gen_obsd_from_datad(o.get('data', {}))
+385        cd = _gen_covobsd_from_cdatad(o.get('cdata', {}))
+386
+387        ret = []
+388        taglist = o.get('tag', N * [None])
+389        for i in range(N):
+390            if od:
+391                r_offsets = np.array([np.average(di[:, i]) for di in od['deltas']])
+392                ret.append(Obs([od['deltas'][j][:, i] - r_offsets[j] for j in range(len(od['deltas']))], od['names'], idl=od['idl'], means=[ro + values[i] for ro in r_offsets]))
+393                ret[-1]._value = values[i]
+394            else:
+395                ret.append(Obs([], [], means=[]))
+396                ret[-1]._value = values[i]
+397            for name in cd:
+398                co = cd[name][i]
+399                ret[-1]._covobs[name] = Covobs(None, co['cov'], co['name'], grad=co['grad'])
+400                ret[-1].names.append(co['name'])
+401            ret[-1].reweighted = o.get('reweighted', False)
+402            ret[-1].tag = taglist[i]
+403        return np.reshape(ret, layout)
+404
+405    def get_Corr_from_dict(o):
+406        if isinstance(o.get('tag'), list):  # supports the old way
+407            taglist = o.get('tag')  # This had to be modified to get the taglist from the dictionary
+408            temp_prange = None
+409        elif isinstance(o.get('tag'), dict):
+410            tagdic = o.get('tag')
+411            taglist = tagdic['tag']
+412            if 'prange' in tagdic:
+413                temp_prange = tagdic['prange']
+414            else:
+415                temp_prange = None
+416        else:
+417            raise Exception("The tag is not a list or dict")
+418
+419        corr_tag = taglist[-1]
+420        tmp_o = o
+421        tmp_o['tag'] = taglist[:-1]
+422        if len(tmp_o['tag']) == 0:
+423            del tmp_o['tag']
+424        dat = get_Array_from_dict(tmp_o)
+425        my_corr = Corr([None if np.isnan(o.ravel()[0].value) else o for o in list(dat)])
+426        if corr_tag != 'None':
+427            my_corr.tag = corr_tag
+428
+429        my_corr.prange = temp_prange
+430        return my_corr
+431
+432    prog = json_dict.get('program', '')
+433    version = json_dict.get('version', '')
+434    who = json_dict.get('who', '')
+435    date = json_dict.get('date', '')
+436    host = json_dict.get('host', '')
+437    if prog and verbose:
+438        print(f'Data has been written using {prog}.')
+439    if version and verbose:
+440        print(f'Format version {version}')
+441    if np.any([who, date, host] and verbose):
+442        print(f'Written by {who} on {date} on host {host}')
+443    description = json_dict.get('description', '')
+444    if description and verbose:
+445        print()
+446        print('Description: ', description)
+447    obsdata = json_dict['obsdata']
+448    ol = []
+449    for io in obsdata:
+450        if io['type'] == 'Obs':
+451            ol.append(get_Obs_from_dict(io))
+452        elif io['type'] == 'List':
+453            ol.append(get_List_from_dict(io))
+454        elif io['type'] == 'Array':
+455            ol.append(get_Array_from_dict(io))
+456        elif io['type'] == 'Corr':
+457            ol.append(get_Corr_from_dict(io))
+458        else:
+459            raise Exception("Unknown datatype.")
+460
+461    if full_output:
+462        retd = {}
+463        retd['program'] = prog
+464        retd['version'] = version
+465        retd['who'] = who
+466        retd['date'] = date
+467        retd['host'] = host
+468        retd['description'] = description
+469        retd['obsdata'] = ol
+470
+471        return retd
+472    else:
+473        if len(obsdata) == 1:
+474            ol = ol[0]
 475
-476
-477def import_json_string(json_string, verbose=True, full_output=False):
-478    """Reconstruct a list of Obs or structures containing Obs from a json string.
-479
-480    The following structures are supported: Obs, list, numpy.ndarray, Corr
-481    If the list contains only one element, it is unpacked from the list.
-482
-483    Parameters
-484    ----------
-485    json_string : str
-486        json string containing the data.
-487    verbose : bool
-488        Print additional information that was written to the file.
-489    full_output : bool
-490        If True, a dict containing auxiliary information and the data is returned.
-491        If False, only the data is returned.
-492
-493    Returns
-494    -------
-495    result : list[Obs]
-496        reconstructed list of observables from the json string
-497    or
-498    result : Obs
-499        only one observable if the list only has one entry
-500    or
-501    result : dict
-502        if full_output=True
-503    """
-504    return _parse_json_dict(json.loads(json_string), verbose, full_output)
-505
-506
-507def load_json(fname, verbose=True, gz=True, full_output=False):
-508    """Import a list of Obs or structures containing Obs from a .json(.gz) file.
-509
-510    The following structures are supported: Obs, list, numpy.ndarray, Corr
-511    If the list contains only one element, it is unpacked from the list.
-512
-513    Parameters
-514    ----------
-515    fname : str
-516        Filename of the input file.
-517    verbose : bool
-518        Print additional information that was written to the file.
-519    gz : bool
-520        If True, assumes that data is gzipped. If False, assumes JSON file.
-521    full_output : bool
-522        If True, a dict containing auxiliary information and the data is returned.
-523        If False, only the data is returned.
-524
-525    Returns
-526    -------
-527    result : list[Obs]
-528        reconstructed list of observables from the json string
-529    or
-530    result : Obs
-531        only one observable if the list only has one entry
-532    or
-533    result : dict
-534        if full_output=True
-535    """
-536    if not fname.endswith('.json') and not fname.endswith('.gz'):
-537        fname += '.json'
-538    if gz:
-539        if not fname.endswith('.gz'):
-540            fname += '.gz'
-541        with gzip.open(fname, 'r') as fin:
-542            d = json.load(fin)
-543    else:
-544        if fname.endswith('.gz'):
-545            warnings.warn("Trying to read from %s without unzipping!" % fname, UserWarning)
-546        with open(fname, 'r', encoding='utf-8') as fin:
-547            d = json.loads(fin.read())
-548
-549    return _parse_json_dict(d, verbose, full_output)
+476        return ol
+477
+478
+479def import_json_string(json_string, verbose=True, full_output=False):
+480    """Reconstruct a list of Obs or structures containing Obs from a json string.
+481
+482    The following structures are supported: Obs, list, numpy.ndarray, Corr
+483    If the list contains only one element, it is unpacked from the list.
+484
+485    Parameters
+486    ----------
+487    json_string : str
+488        json string containing the data.
+489    verbose : bool
+490        Print additional information that was written to the file.
+491    full_output : bool
+492        If True, a dict containing auxiliary information and the data is returned.
+493        If False, only the data is returned.
+494
+495    Returns
+496    -------
+497    result : list[Obs]
+498        reconstructed list of observables from the json string
+499    or
+500    result : Obs
+501        only one observable if the list only has one entry
+502    or
+503    result : dict
+504        if full_output=True
+505    """
+506    return _parse_json_dict(json.loads(json_string), verbose, full_output)
+507
+508
+509def load_json(fname, verbose=True, gz=True, full_output=False):
+510    """Import a list of Obs or structures containing Obs from a .json(.gz) file.
+511
+512    The following structures are supported: Obs, list, numpy.ndarray, Corr
+513    If the list contains only one element, it is unpacked from the list.
+514
+515    Parameters
+516    ----------
+517    fname : str
+518        Filename of the input file.
+519    verbose : bool
+520        Print additional information that was written to the file.
+521    gz : bool
+522        If True, assumes that data is gzipped. If False, assumes JSON file.
+523    full_output : bool
+524        If True, a dict containing auxiliary information and the data is returned.
+525        If False, only the data is returned.
+526
+527    Returns
+528    -------
+529    result : list[Obs]
+530        reconstructed list of observables from the json string
+531    or
+532    result : Obs
+533        only one observable if the list only has one entry
+534    or
+535    result : dict
+536        if full_output=True
+537    """
+538    if not fname.endswith('.json') and not fname.endswith('.gz'):
+539        fname += '.json'
+540    if gz:
+541        if not fname.endswith('.gz'):
+542            fname += '.gz'
+543        with gzip.open(fname, 'r') as fin:
+544            d = json.load(fin)
+545    else:
+546        if fname.endswith('.gz'):
+547            warnings.warn(f"Trying to read from {fname} without unzipping!", UserWarning, stacklevel=2)
+548        with open(fname, encoding='utf-8') as fin:
+549            d = json.loads(fin.read())
 550
-551
-552def _ol_from_dict(ind, reps='DICTOBS'):
-553    """Convert a dictionary of Obs objects to a list and a dictionary that contains
-554    placeholders instead of the Obs objects.
-555
-556    Parameters
-557    ----------
-558    ind : dict
-559        Dict of JSON valid structures and objects that will be exported.
-560        At the moment, these object can be either of: Obs, list, numpy.ndarray, Corr.
-561        All Obs inside a structure have to be defined on the same set of configurations.
-562    reps : str
-563        Specify the structure of the placeholder in exported dict to be reps[0-9]+.
-564    """
-565
-566    obstypes = (Obs, Corr, np.ndarray)
+551    return _parse_json_dict(d, verbose, full_output)
+552
+553
+554def _ol_from_dict(ind, reps='DICTOBS'):
+555    """Convert a dictionary of Obs objects to a list and a dictionary that contains
+556    placeholders instead of the Obs objects.
+557
+558    Parameters
+559    ----------
+560    ind : dict
+561        Dict of JSON valid structures and objects that will be exported.
+562        At the moment, these object can be either of: Obs, list, numpy.ndarray, Corr.
+563        All Obs inside a structure have to be defined on the same set of configurations.
+564    reps : str
+565        Specify the structure of the placeholder in exported dict to be reps[0-9]+.
+566    """
 567
-568    if not reps.isalnum():
-569        raise Exception('Placeholder string has to be alphanumeric!')
-570    ol = []
-571    counter = 0
-572
-573    def dict_replace_obs(d):
-574        nonlocal counter
-575        x = {}
-576        for k, v in d.items():
-577            if isinstance(v, dict):
-578                v = dict_replace_obs(v)
-579            elif isinstance(v, list) and all([isinstance(o, Obs) for o in v]):
-580                v = obslist_replace_obs(v)
-581            elif isinstance(v, list):
-582                v = list_replace_obs(v)
-583            elif isinstance(v, obstypes):
-584                ol.append(v)
-585                v = reps + '%d' % (counter)
-586                counter += 1
-587            elif isinstance(v, str):
-588                if bool(re.match(r'%s[0-9]+' % (reps), v)):
-589                    raise Exception('Dict contains string %s that matches the placeholder! %s Cannot be safely exported.' % (v, reps))
-590            x[k] = v
-591        return x
-592
-593    def list_replace_obs(li):
-594        nonlocal counter
-595        x = []
-596        for e in li:
-597            if isinstance(e, list):
-598                e = list_replace_obs(e)
-599            elif isinstance(e, list) and all([isinstance(o, Obs) for o in e]):
-600                e = obslist_replace_obs(e)
-601            elif isinstance(e, dict):
-602                e = dict_replace_obs(e)
-603            elif isinstance(e, obstypes):
-604                ol.append(e)
-605                e = reps + '%d' % (counter)
-606                counter += 1
-607            elif isinstance(e, str):
-608                if bool(re.match(r'%s[0-9]+' % (reps), e)):
-609                    raise Exception('Dict contains string %s that matches the placeholder! %s Cannot be safely exported.' % (e, reps))
-610            x.append(e)
-611        return x
-612
-613    def obslist_replace_obs(li):
-614        nonlocal counter
-615        il = []
-616        for e in li:
-617            il.append(e)
-618
-619        ol.append(il)
-620        x = reps + '%d' % (counter)
-621        counter += 1
-622        return x
-623
-624    nd = dict_replace_obs(ind)
+568    obstypes = (Obs, Corr, np.ndarray)
+569
+570    if not reps.isalnum():
+571        raise Exception('Placeholder string has to be alphanumeric!')
+572    ol = []
+573    counter = 0
+574
+575    def dict_replace_obs(d):
+576        nonlocal counter
+577        x = {}
+578        for k, v in d.items():
+579            if isinstance(v, dict):
+580                v = dict_replace_obs(v)
+581            elif isinstance(v, list) and all([isinstance(o, Obs) for o in v]):
+582                v = obslist_replace_obs(v)
+583            elif isinstance(v, list):
+584                v = list_replace_obs(v)
+585            elif isinstance(v, obstypes):
+586                ol.append(v)
+587                v = reps + f'{counter}'
+588                counter += 1
+589            elif isinstance(v, str):
+590                if bool(re.match(rf'{reps}[0-9]+', v)):
+591                    raise Exception(f'Dict contains string {v} that matches the placeholder! {reps} Cannot be safely exported.')
+592            x[k] = v
+593        return x
+594
+595    def list_replace_obs(li):
+596        nonlocal counter
+597        x = []
+598        for e in li:
+599            if isinstance(e, list):
+600                e = list_replace_obs(e)
+601            elif isinstance(e, list) and all([isinstance(o, Obs) for o in e]):
+602                e = obslist_replace_obs(e)
+603            elif isinstance(e, dict):
+604                e = dict_replace_obs(e)
+605            elif isinstance(e, obstypes):
+606                ol.append(e)
+607                e = reps + f'{counter}'
+608                counter += 1
+609            elif isinstance(e, str):
+610                if bool(re.match(rf'{reps}[0-9]+', e)):
+611                    raise Exception(f'Dict contains string {e} that matches the placeholder! {reps} Cannot be safely exported.')
+612            x.append(e)
+613        return x
+614
+615    def obslist_replace_obs(li):
+616        nonlocal counter
+617        il = []
+618        for e in li:
+619            il.append(e)
+620
+621        ol.append(il)
+622        x = reps + f'{counter}'
+623        counter += 1
+624        return x
 625
-626    return ol, nd
+626    nd = dict_replace_obs(ind)
 627
-628
-629def dump_dict_to_json(od, fname, description='', indent=1, reps='DICTOBS', gz=True):
-630    """Export a dict of Obs or structures containing Obs to a .json(.gz) file
-631
-632    Parameters
-633    ----------
-634    od : dict
-635        Dict of JSON valid structures and objects that will be exported.
-636        At the moment, these objects can be either of: Obs, list, numpy.ndarray, Corr.
-637        All Obs inside a structure have to be defined on the same set of configurations.
-638    fname : str
-639        Filename of the output file.
-640    description : str
-641        Optional string that describes the contents of the json file.
-642    indent : int
-643        Specify the indentation level of the json file. None or 0 is permissible and
-644        saves disk space.
-645    reps : str
-646        Specify the structure of the placeholder in exported dict to be reps[0-9]+.
-647    gz : bool
-648        If True, the output is a gzipped json. If False, the output is a json file.
-649
-650    Returns
-651    -------
-652    None
-653    """
-654
-655    if not isinstance(od, dict):
-656        raise Exception('od has to be a dictionary. Did you want to use dump_to_json?')
-657
-658    infostring = ('This JSON file contains a python dictionary that has been parsed to a list of structures. '
-659                  'OBSDICT contains the dictionary, where Obs or other structures have been replaced by '
-660                  '' + reps + '[0-9]+. The field description contains the additional description of this JSON file. '
-661                  'This file may be parsed to a dict with the pyerrors routine load_json_dict.')
-662
-663    desc_dict = {'INFO': infostring, 'OBSDICT': {}, 'description': description}
-664    ol, desc_dict['OBSDICT'] = _ol_from_dict(od, reps=reps)
-665
-666    dump_to_json(ol, fname, description=desc_dict, indent=indent, gz=gz)
+628    return ol, nd
+629
+630
+631def dump_dict_to_json(od, fname, description='', indent=1, reps='DICTOBS', gz=True):
+632    """Export a dict of Obs or structures containing Obs to a .json(.gz) file
+633
+634    Parameters
+635    ----------
+636    od : dict
+637        Dict of JSON valid structures and objects that will be exported.
+638        At the moment, these objects can be either of: Obs, list, numpy.ndarray, Corr.
+639        All Obs inside a structure have to be defined on the same set of configurations.
+640    fname : str
+641        Filename of the output file.
+642    description : str
+643        Optional string that describes the contents of the json file.
+644    indent : int
+645        Specify the indentation level of the json file. None or 0 is permissible and
+646        saves disk space.
+647    reps : str
+648        Specify the structure of the placeholder in exported dict to be reps[0-9]+.
+649    gz : bool
+650        If True, the output is a gzipped json. If False, the output is a json file.
+651
+652    Returns
+653    -------
+654    None
+655    """
+656
+657    if not isinstance(od, dict):
+658        raise Exception('od has to be a dictionary. Did you want to use dump_to_json?')
+659
+660    infostring = ('This JSON file contains a python dictionary that has been parsed to a list of structures. '
+661                  'OBSDICT contains the dictionary, where Obs or other structures have been replaced by '
+662                  '' + reps + '[0-9]+. The field description contains the additional description of this JSON file. '
+663                  'This file may be parsed to a dict with the pyerrors routine load_json_dict.')
+664
+665    desc_dict = {'INFO': infostring, 'OBSDICT': {}, 'description': description}
+666    ol, desc_dict['OBSDICT'] = _ol_from_dict(od, reps=reps)
 667
-668
-669def _od_from_list_and_dict(ol, ind, reps='DICTOBS'):
-670    """Parse a list of Obs or structures containing Obs and an accompanying
-671    dict, where the structures have been replaced by placeholders to a
-672    dict that contains the structures.
-673
-674    The following structures are supported: Obs, list, numpy.ndarray, Corr
+668    dump_to_json(ol, fname, description=desc_dict, indent=indent, gz=gz)
+669
+670
+671def _od_from_list_and_dict(ol, ind, reps='DICTOBS'):
+672    """Parse a list of Obs or structures containing Obs and an accompanying
+673    dict, where the structures have been replaced by placeholders to a
+674    dict that contains the structures.
 675
-676    Parameters
-677    ----------
-678    ol : list
-679        List of objects -
-680        At the moment, these objects can be either of: Obs, list, numpy.ndarray, Corr.
-681        All Obs inside a structure have to be defined on the same set of configurations.
-682    ind : dict
-683        Dict that defines the structure of the resulting dict and contains placeholders
-684    reps : str
-685        Specify the structure of the placeholder in imported dict to be reps[0-9]+.
-686    """
-687    if not reps.isalnum():
-688        raise Exception('Placeholder string has to be alphanumeric!')
-689
-690    counter = 0
+676    The following structures are supported: Obs, list, numpy.ndarray, Corr
+677
+678    Parameters
+679    ----------
+680    ol : list
+681        List of objects -
+682        At the moment, these objects can be either of: Obs, list, numpy.ndarray, Corr.
+683        All Obs inside a structure have to be defined on the same set of configurations.
+684    ind : dict
+685        Dict that defines the structure of the resulting dict and contains placeholders
+686    reps : str
+687        Specify the structure of the placeholder in imported dict to be reps[0-9]+.
+688    """
+689    if not reps.isalnum():
+690        raise Exception('Placeholder string has to be alphanumeric!')
 691
-692    def dict_replace_string(d):
-693        nonlocal counter
-694        x = {}
-695        for k, v in d.items():
-696            if isinstance(v, dict):
-697                v = dict_replace_string(v)
-698            elif isinstance(v, list):
-699                v = list_replace_string(v)
-700            elif isinstance(v, str) and bool(re.match(r'%s[0-9]+' % (reps), v)):
-701                index = int(v[len(reps):])
-702                v = ol[index]
-703                counter += 1
-704            x[k] = v
-705        return x
-706
-707    def list_replace_string(li):
-708        nonlocal counter
-709        x = []
-710        for e in li:
-711            if isinstance(e, list):
-712                e = list_replace_string(e)
-713            elif isinstance(e, dict):
-714                e = dict_replace_string(e)
-715            elif isinstance(e, str) and bool(re.match(r'%s[0-9]+' % (reps), e)):
-716                index = int(e[len(reps):])
-717                e = ol[index]
-718                counter += 1
-719            x.append(e)
-720        return x
-721
-722    nd = dict_replace_string(ind)
+692    counter = 0
+693
+694    def dict_replace_string(d):
+695        nonlocal counter
+696        x = {}
+697        for k, v in d.items():
+698            if isinstance(v, dict):
+699                v = dict_replace_string(v)
+700            elif isinstance(v, list):
+701                v = list_replace_string(v)
+702            elif isinstance(v, str) and bool(re.match(rf'{reps}[0-9]+', v)):
+703                index = int(v[len(reps):])
+704                v = ol[index]
+705                counter += 1
+706            x[k] = v
+707        return x
+708
+709    def list_replace_string(li):
+710        nonlocal counter
+711        x = []
+712        for e in li:
+713            if isinstance(e, list):
+714                e = list_replace_string(e)
+715            elif isinstance(e, dict):
+716                e = dict_replace_string(e)
+717            elif isinstance(e, str) and bool(re.match(rf'{reps}[0-9]+', e)):
+718                index = int(e[len(reps):])
+719                e = ol[index]
+720                counter += 1
+721            x.append(e)
+722        return x
 723
-724    if counter == 0:
-725        raise Exception('No placeholder has been replaced! Check if reps is set correctly.')
-726
-727    return nd
+724    nd = dict_replace_string(ind)
+725
+726    if counter == 0:
+727        raise Exception('No placeholder has been replaced! Check if reps is set correctly.')
 728
-729
-730def load_json_dict(fname, verbose=True, gz=True, full_output=False, reps='DICTOBS'):
-731    """Import a dict of Obs or structures containing Obs from a .json(.gz) file.
-732
-733    The following structures are supported: Obs, list, numpy.ndarray, Corr
+729    return nd
+730
+731
+732def load_json_dict(fname, verbose=True, gz=True, full_output=False, reps='DICTOBS'):
+733    """Import a dict of Obs or structures containing Obs from a .json(.gz) file.
 734
-735    Parameters
-736    ----------
-737    fname : str
-738        Filename of the input file.
-739    verbose : bool
-740        Print additional information that was written to the file.
-741    gz : bool
-742        If True, assumes that data is gzipped. If False, assumes JSON file.
-743    full_output : bool
-744        If True, a dict containing auxiliary information and the data is returned.
-745        If False, only the data is returned.
-746    reps : str
-747        Specify the structure of the placeholder in imported dict to be reps[0-9]+.
-748
-749    Returns
-750    -------
-751    data : Obs / list / Corr
-752        Read data
-753    or
-754    data : dict
-755        Read data and meta-data
-756    """
-757    indata = load_json(fname, verbose=verbose, gz=gz, full_output=True)
-758    description = indata['description']['description']
-759    indict = indata['description']['OBSDICT']
-760    ol = indata['obsdata']
-761    od = _od_from_list_and_dict(ol, indict, reps=reps)
-762
-763    if full_output:
-764        indata['description'] = description
-765        indata['obsdata'] = od
-766        return indata
-767    else:
-768        return od
+735    The following structures are supported: Obs, list, numpy.ndarray, Corr
+736
+737    Parameters
+738    ----------
+739    fname : str
+740        Filename of the input file.
+741    verbose : bool
+742        Print additional information that was written to the file.
+743    gz : bool
+744        If True, assumes that data is gzipped. If False, assumes JSON file.
+745    full_output : bool
+746        If True, a dict containing auxiliary information and the data is returned.
+747        If False, only the data is returned.
+748    reps : str
+749        Specify the structure of the placeholder in imported dict to be reps[0-9]+.
+750
+751    Returns
+752    -------
+753    data : Obs / list / Corr
+754        Read data
+755    or
+756    data : dict
+757        Read data and meta-data
+758    """
+759    indata = load_json(fname, verbose=verbose, gz=gz, full_output=True)
+760    description = indata['description']['description']
+761    indict = indata['description']['OBSDICT']
+762    ol = indata['obsdata']
+763    od = _od_from_list_and_dict(ol, indict, reps=reps)
+764
+765    if full_output:
+766        indata['description'] = description
+767        indata['obsdata'] = od
+768        return indata
+769    else:
+770        return od
 
@@ -874,207 +876,207 @@
-
 18def create_json_string(ol, description='', indent=1):
- 19    """Generate the string for the export of a list of Obs or structures containing Obs
- 20    to a .json(.gz) file
- 21
- 22    Parameters
- 23    ----------
- 24    ol : list
- 25        List of objects that will be exported. At the moment, these objects can be
- 26        either of: Obs, list, numpy.ndarray, Corr.
- 27        All Obs inside a structure have to be defined on the same set of configurations.
- 28    description : str
- 29        Optional string that describes the contents of the json file.
- 30    indent : int
- 31        Specify the indentation level of the json file. None or 0 is permissible and
- 32        saves disk space.
- 33
- 34    Returns
- 35    -------
- 36    json_string : str
- 37        String for export to .json(.gz) file
- 38    """
- 39
- 40    def _gen_data_d_from_list(ol):
- 41        dl = []
- 42        No = len(ol)
- 43        for name in ol[0].mc_names:
- 44            ed = {}
- 45            ed['id'] = name
- 46            ed['replica'] = []
- 47            for r_name in ol[0].e_content[name]:
- 48                rd = {}
- 49                rd['name'] = r_name
- 50                rd['deltas'] = []
- 51                offsets = [o.r_values[r_name] - o.value for o in ol]
- 52                deltas = np.column_stack([ol[oi].deltas[r_name] + offsets[oi] for oi in range(No)])
- 53                for i in range(len(ol[0].idl[r_name])):
- 54                    rd['deltas'].append([ol[0].idl[r_name][i]])
- 55                    rd['deltas'][-1] += deltas[i].tolist()
- 56                ed['replica'].append(rd)
- 57            dl.append(ed)
- 58        return dl
- 59
- 60    def _gen_cdata_d_from_list(ol):
- 61        dl = []
- 62        for name in ol[0].cov_names:
- 63            ed = {}
- 64            ed['id'] = name
- 65            ed['layout'] = str(ol[0].covobs[name].cov.shape).lstrip('(').rstrip(')').rstrip(',')
- 66            ed['cov'] = list(np.ravel(ol[0].covobs[name].cov))
- 67            ncov = ol[0].covobs[name].cov.shape[0]
- 68            ed['grad'] = []
- 69            for i in range(ncov):
- 70                ed['grad'].append([])
- 71                for o in ol:
- 72                    ed['grad'][-1].append(o.covobs[name].grad[i][0])
- 73            dl.append(ed)
- 74        return dl
- 75
- 76    def write_Obs_to_dict(o):
- 77        d = {}
- 78        d['type'] = 'Obs'
- 79        d['layout'] = '1'
- 80        if o.tag:
- 81            d['tag'] = [o.tag]
- 82        if o.reweighted:
- 83            d['reweighted'] = o.reweighted
- 84        d['value'] = [o.value]
- 85        data = _gen_data_d_from_list([o])
- 86        if len(data) > 0:
- 87            d['data'] = data
- 88        cdata = _gen_cdata_d_from_list([o])
- 89        if len(cdata) > 0:
- 90            d['cdata'] = cdata
- 91        return d
- 92
- 93    def write_List_to_dict(ol):
- 94        _assert_equal_properties(ol)
- 95        d = {}
- 96        d['type'] = 'List'
- 97        d['layout'] = '%d' % len(ol)
- 98        taglist = [o.tag for o in ol]
- 99        if np.any([tag is not None for tag in taglist]):
-100            d['tag'] = taglist
-101        if ol[0].reweighted:
-102            d['reweighted'] = ol[0].reweighted
-103        d['value'] = [o.value for o in ol]
-104        data = _gen_data_d_from_list(ol)
-105        if len(data) > 0:
-106            d['data'] = data
-107        cdata = _gen_cdata_d_from_list(ol)
-108        if len(cdata) > 0:
-109            d['cdata'] = cdata
-110        return d
-111
-112    def write_Array_to_dict(oa):
-113        ol = np.ravel(oa)
-114        _assert_equal_properties(ol)
-115        d = {}
-116        d['type'] = 'Array'
-117        d['layout'] = str(oa.shape).lstrip('(').rstrip(')').rstrip(',')
-118        taglist = [o.tag for o in ol]
-119        if np.any([tag is not None for tag in taglist]):
-120            d['tag'] = taglist
-121        if ol[0].reweighted:
-122            d['reweighted'] = ol[0].reweighted
-123        d['value'] = [o.value for o in ol]
-124        data = _gen_data_d_from_list(ol)
-125        if len(data) > 0:
-126            d['data'] = data
-127        cdata = _gen_cdata_d_from_list(ol)
-128        if len(cdata) > 0:
-129            d['cdata'] = cdata
-130        return d
-131
-132    def _nan_Obs_like(obs):
-133        samples = []
-134        names = []
-135        idl = []
-136        for key, value in obs.idl.items():
-137            samples.append(np.array([np.nan] * len(value)))
-138            names.append(key)
-139            idl.append(value)
-140        my_obs = Obs(samples, names, idl, means=[np.nan for n in names])
-141        my_obs._value = np.nan
-142        my_obs._covobs = obs._covobs
-143        for name in obs._covobs:
-144            my_obs.names.append(name)
-145        my_obs.reweighted = obs.reweighted
-146        return my_obs
-147
-148    def write_Corr_to_dict(my_corr):
-149        first_not_none = next(i for i, j in enumerate(my_corr.content) if np.all(j))
-150        dummy_array = np.empty((my_corr.N, my_corr.N), dtype=object)
-151        dummy_array[:] = _nan_Obs_like(my_corr.content[first_not_none].ravel()[0])
-152        content = [o if o is not None else dummy_array for o in my_corr.content]
-153        dat = write_Array_to_dict(np.array(content, dtype=object))
-154        dat['type'] = 'Corr'
-155        corr_meta_data = str(my_corr.tag)
-156        if 'tag' in dat.keys():
-157            dat['tag'].append(corr_meta_data)
-158        else:
-159            dat['tag'] = [corr_meta_data]
-160        taglist = dat['tag']
-161        dat['tag'] = {}  # tag is now a dictionary, that contains the previous taglist in the key "tag"
-162        dat['tag']['tag'] = taglist
-163        if my_corr.prange is not None:
-164            dat['tag']['prange'] = my_corr.prange
-165        return dat
-166
-167    if not isinstance(ol, list):
-168        ol = [ol]
-169
-170    d = {}
-171    d['program'] = 'pyerrors %s' % (pyerrorsversion.__version__)
-172    d['version'] = '1.1'
-173    d['who'] = getpass.getuser()
-174    d['date'] = datetime.datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S %z')
-175    d['host'] = socket.gethostname() + ', ' + platform.platform()
-176
-177    if description:
-178        d['description'] = description
-179
-180    d['obsdata'] = []
-181    for io in ol:
-182        if isinstance(io, Obs):
-183            d['obsdata'].append(write_Obs_to_dict(io))
-184        elif isinstance(io, list):
-185            d['obsdata'].append(write_List_to_dict(io))
-186        elif isinstance(io, np.ndarray):
-187            d['obsdata'].append(write_Array_to_dict(io))
-188        elif isinstance(io, Corr):
-189            d['obsdata'].append(write_Corr_to_dict(io))
-190        else:
-191            raise Exception("Unkown datatype.")
-192
-193    def _jsonifier(obj):
-194        if isinstance(obj, dict):
-195            result = {}
-196            for key in obj:
-197                if key is True:
-198                    result['true'] = obj[key]
-199                elif key is False:
-200                    result['false'] = obj[key]
-201                elif key is None:
-202                    result['null'] = obj[key]
-203                elif isinstance(key, (int, float, np.floating, np.integer)):
-204                    result[str(key)] = obj[key]
-205                else:
-206                    raise TypeError('keys must be str, int, float, bool or None')
-207            return result
-208        elif isinstance(obj, np.integer):
-209            return int(obj)
-210        elif isinstance(obj, np.floating):
-211            return float(obj)
-212        else:
-213            raise ValueError('%r is not JSON serializable' % (obj,))
-214
-215    if indent:
-216        return json.dumps(d, indent=indent, ensure_ascii=False, default=_jsonifier, write_mode=json.WM_SINGLE_LINE_ARRAY)
-217    else:
-218        return json.dumps(d, indent=indent, ensure_ascii=False, default=_jsonifier, write_mode=json.WM_COMPACT)
+            
 20def create_json_string(ol, description='', indent=1):
+ 21    """Generate the string for the export of a list of Obs or structures containing Obs
+ 22    to a .json(.gz) file
+ 23
+ 24    Parameters
+ 25    ----------
+ 26    ol : list
+ 27        List of objects that will be exported. At the moment, these objects can be
+ 28        either of: Obs, list, numpy.ndarray, Corr.
+ 29        All Obs inside a structure have to be defined on the same set of configurations.
+ 30    description : str
+ 31        Optional string that describes the contents of the json file.
+ 32    indent : int
+ 33        Specify the indentation level of the json file. None or 0 is permissible and
+ 34        saves disk space.
+ 35
+ 36    Returns
+ 37    -------
+ 38    json_string : str
+ 39        String for export to .json(.gz) file
+ 40    """
+ 41
+ 42    def _gen_data_d_from_list(ol):
+ 43        dl = []
+ 44        No = len(ol)
+ 45        for name in ol[0].mc_names:
+ 46            ed = {}
+ 47            ed['id'] = name
+ 48            ed['replica'] = []
+ 49            for r_name in ol[0].e_content[name]:
+ 50                rd = {}
+ 51                rd['name'] = r_name
+ 52                rd['deltas'] = []
+ 53                offsets = [o.r_values[r_name] - o.value for o in ol]
+ 54                deltas = np.column_stack([ol[oi].deltas[r_name] + offsets[oi] for oi in range(No)])
+ 55                for i in range(len(ol[0].idl[r_name])):
+ 56                    rd['deltas'].append([ol[0].idl[r_name][i]])
+ 57                    rd['deltas'][-1] += deltas[i].tolist()
+ 58                ed['replica'].append(rd)
+ 59            dl.append(ed)
+ 60        return dl
+ 61
+ 62    def _gen_cdata_d_from_list(ol):
+ 63        dl = []
+ 64        for name in ol[0].cov_names:
+ 65            ed = {}
+ 66            ed['id'] = name
+ 67            ed['layout'] = str(ol[0].covobs[name].cov.shape).lstrip('(').rstrip(')').rstrip(',')
+ 68            ed['cov'] = list(np.ravel(ol[0].covobs[name].cov))
+ 69            ncov = ol[0].covobs[name].cov.shape[0]
+ 70            ed['grad'] = []
+ 71            for i in range(ncov):
+ 72                ed['grad'].append([])
+ 73                for o in ol:
+ 74                    ed['grad'][-1].append(o.covobs[name].grad[i][0])
+ 75            dl.append(ed)
+ 76        return dl
+ 77
+ 78    def write_Obs_to_dict(o):
+ 79        d = {}
+ 80        d['type'] = 'Obs'
+ 81        d['layout'] = '1'
+ 82        if o.tag:
+ 83            d['tag'] = [o.tag]
+ 84        if o.reweighted:
+ 85            d['reweighted'] = o.reweighted
+ 86        d['value'] = [o.value]
+ 87        data = _gen_data_d_from_list([o])
+ 88        if len(data) > 0:
+ 89            d['data'] = data
+ 90        cdata = _gen_cdata_d_from_list([o])
+ 91        if len(cdata) > 0:
+ 92            d['cdata'] = cdata
+ 93        return d
+ 94
+ 95    def write_List_to_dict(ol):
+ 96        _assert_equal_properties(ol)
+ 97        d = {}
+ 98        d['type'] = 'List'
+ 99        d['layout'] = f'{len(ol)}'
+100        taglist = [o.tag for o in ol]
+101        if np.any([tag is not None for tag in taglist]):
+102            d['tag'] = taglist
+103        if ol[0].reweighted:
+104            d['reweighted'] = ol[0].reweighted
+105        d['value'] = [o.value for o in ol]
+106        data = _gen_data_d_from_list(ol)
+107        if len(data) > 0:
+108            d['data'] = data
+109        cdata = _gen_cdata_d_from_list(ol)
+110        if len(cdata) > 0:
+111            d['cdata'] = cdata
+112        return d
+113
+114    def write_Array_to_dict(oa):
+115        ol = np.ravel(oa)
+116        _assert_equal_properties(ol)
+117        d = {}
+118        d['type'] = 'Array'
+119        d['layout'] = str(oa.shape).lstrip('(').rstrip(')').rstrip(',')
+120        taglist = [o.tag for o in ol]
+121        if np.any([tag is not None for tag in taglist]):
+122            d['tag'] = taglist
+123        if ol[0].reweighted:
+124            d['reweighted'] = ol[0].reweighted
+125        d['value'] = [o.value for o in ol]
+126        data = _gen_data_d_from_list(ol)
+127        if len(data) > 0:
+128            d['data'] = data
+129        cdata = _gen_cdata_d_from_list(ol)
+130        if len(cdata) > 0:
+131            d['cdata'] = cdata
+132        return d
+133
+134    def _nan_Obs_like(obs):
+135        samples = []
+136        names = []
+137        idl = []
+138        for key, value in obs.idl.items():
+139            samples.append(np.array([np.nan] * len(value)))
+140            names.append(key)
+141            idl.append(value)
+142        my_obs = Obs(samples, names, idl, means=[np.nan for n in names])
+143        my_obs._value = np.nan
+144        my_obs._covobs = obs._covobs
+145        for name in obs._covobs:
+146            my_obs.names.append(name)
+147        my_obs.reweighted = obs.reweighted
+148        return my_obs
+149
+150    def write_Corr_to_dict(my_corr):
+151        first_not_none = next(i for i, j in enumerate(my_corr.content) if np.all(j))
+152        dummy_array = np.empty((my_corr.N, my_corr.N), dtype=object)
+153        dummy_array[:] = _nan_Obs_like(my_corr.content[first_not_none].ravel()[0])
+154        content = [o if o is not None else dummy_array for o in my_corr.content]
+155        dat = write_Array_to_dict(np.array(content, dtype=object))
+156        dat['type'] = 'Corr'
+157        corr_meta_data = str(my_corr.tag)
+158        if 'tag' in dat.keys():
+159            dat['tag'].append(corr_meta_data)
+160        else:
+161            dat['tag'] = [corr_meta_data]
+162        taglist = dat['tag']
+163        dat['tag'] = {}  # tag is now a dictionary, that contains the previous taglist in the key "tag"
+164        dat['tag']['tag'] = taglist
+165        if my_corr.prange is not None:
+166            dat['tag']['prange'] = my_corr.prange
+167        return dat
+168
+169    if not isinstance(ol, list):
+170        ol = [ol]
+171
+172    d = {}
+173    d['program'] = f'pyerrors {pyerrorsversion.__version__}'
+174    d['version'] = '1.1'
+175    d['who'] = getpass.getuser()
+176    d['date'] = datetime.datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S %z')
+177    d['host'] = socket.gethostname() + ', ' + platform.platform()
+178
+179    if description:
+180        d['description'] = description
+181
+182    d['obsdata'] = []
+183    for io in ol:
+184        if isinstance(io, Obs):
+185            d['obsdata'].append(write_Obs_to_dict(io))
+186        elif isinstance(io, list):
+187            d['obsdata'].append(write_List_to_dict(io))
+188        elif isinstance(io, np.ndarray):
+189            d['obsdata'].append(write_Array_to_dict(io))
+190        elif isinstance(io, Corr):
+191            d['obsdata'].append(write_Corr_to_dict(io))
+192        else:
+193            raise Exception("Unkown datatype.")
+194
+195    def _jsonifier(obj):
+196        if isinstance(obj, dict):
+197            result = {}
+198            for key in obj:
+199                if key is True:
+200                    result['true'] = obj[key]
+201                elif key is False:
+202                    result['false'] = obj[key]
+203                elif key is None:
+204                    result['null'] = obj[key]
+205                elif isinstance(key, (int, float, np.floating, np.integer)):
+206                    result[str(key)] = obj[key]
+207                else:
+208                    raise TypeError('keys must be str, int, float, bool or None')
+209            return result
+210        elif isinstance(obj, np.integer):
+211            return int(obj)
+212        elif isinstance(obj, np.floating):
+213            return float(obj)
+214        else:
+215            raise ValueError(f'{obj!r} is not JSON serializable')
+216
+217    if indent:
+218        return json.dumps(d, indent=indent, ensure_ascii=False, default=_jsonifier, write_mode=json.WM_SINGLE_LINE_ARRAY)
+219    else:
+220        return json.dumps(d, indent=indent, ensure_ascii=False, default=_jsonifier, write_mode=json.WM_COMPACT)
 
@@ -1116,46 +1118,46 @@ String for export to pyerrors.input.json(.gz) file
-
221def dump_to_json(ol, fname, description='', indent=1, gz=True):
-222    """Export a list of Obs or structures containing Obs to a .json(.gz) file.
-223    Dict keys that are not JSON-serializable such as floats are converted to strings.
-224
-225    Parameters
-226    ----------
-227    ol : list
-228        List of objects that will be exported. At the moment, these objects can be
-229        either of: Obs, list, numpy.ndarray, Corr.
-230        All Obs inside a structure have to be defined on the same set of configurations.
-231    fname : str
-232        Filename of the output file.
-233    description : str
-234        Optional string that describes the contents of the json file.
-235    indent : int
-236        Specify the indentation level of the json file. None or 0 is permissible and
-237        saves disk space.
-238    gz : bool
-239        If True, the output is a gzipped json. If False, the output is a json file.
-240
-241    Returns
-242    -------
-243    Null
-244    """
-245
-246    jsonstring = create_json_string(ol, description, indent)
+            
223def dump_to_json(ol, fname, description='', indent=1, gz=True):
+224    """Export a list of Obs or structures containing Obs to a .json(.gz) file.
+225    Dict keys that are not JSON-serializable such as floats are converted to strings.
+226
+227    Parameters
+228    ----------
+229    ol : list
+230        List of objects that will be exported. At the moment, these objects can be
+231        either of: Obs, list, numpy.ndarray, Corr.
+232        All Obs inside a structure have to be defined on the same set of configurations.
+233    fname : str
+234        Filename of the output file.
+235    description : str
+236        Optional string that describes the contents of the json file.
+237    indent : int
+238        Specify the indentation level of the json file. None or 0 is permissible and
+239        saves disk space.
+240    gz : bool
+241        If True, the output is a gzipped json. If False, the output is a json file.
+242
+243    Returns
+244    -------
+245    Null
+246    """
 247
-248    if not fname.endswith('.json') and not fname.endswith('.gz'):
-249        fname += '.json'
-250
-251    if gz:
-252        if not fname.endswith('.gz'):
-253            fname += '.gz'
-254
-255        fp = gzip.open(fname, 'wb')
-256        fp.write(jsonstring.encode('utf-8'))
-257    else:
-258        fp = open(fname, 'w', encoding='utf-8')
-259        fp.write(jsonstring)
-260    fp.close()
+248    jsonstring = create_json_string(ol, description, indent)
+249
+250    if not fname.endswith('.json') and not fname.endswith('.gz'):
+251        fname += '.json'
+252
+253    if gz:
+254        if not fname.endswith('.gz'):
+255            fname += '.gz'
+256
+257        fp = gzip.open(fname, 'wb')
+258        fp.write(jsonstring.encode('utf-8'))
+259    else:
+260        fp = open(fname, 'w', encoding='utf-8')
+261        fp.write(jsonstring)
+262    fp.close()
 
@@ -1200,34 +1202,34 @@ If True, the output is a gzipped json. If False, the output is a json file.
-
478def import_json_string(json_string, verbose=True, full_output=False):
-479    """Reconstruct a list of Obs or structures containing Obs from a json string.
-480
-481    The following structures are supported: Obs, list, numpy.ndarray, Corr
-482    If the list contains only one element, it is unpacked from the list.
-483
-484    Parameters
-485    ----------
-486    json_string : str
-487        json string containing the data.
-488    verbose : bool
-489        Print additional information that was written to the file.
-490    full_output : bool
-491        If True, a dict containing auxiliary information and the data is returned.
-492        If False, only the data is returned.
-493
-494    Returns
-495    -------
-496    result : list[Obs]
-497        reconstructed list of observables from the json string
-498    or
-499    result : Obs
-500        only one observable if the list only has one entry
-501    or
-502    result : dict
-503        if full_output=True
-504    """
-505    return _parse_json_dict(json.loads(json_string), verbose, full_output)
+            
480def import_json_string(json_string, verbose=True, full_output=False):
+481    """Reconstruct a list of Obs or structures containing Obs from a json string.
+482
+483    The following structures are supported: Obs, list, numpy.ndarray, Corr
+484    If the list contains only one element, it is unpacked from the list.
+485
+486    Parameters
+487    ----------
+488    json_string : str
+489        json string containing the data.
+490    verbose : bool
+491        Print additional information that was written to the file.
+492    full_output : bool
+493        If True, a dict containing auxiliary information and the data is returned.
+494        If False, only the data is returned.
+495
+496    Returns
+497    -------
+498    result : list[Obs]
+499        reconstructed list of observables from the json string
+500    or
+501    result : Obs
+502        only one observable if the list only has one entry
+503    or
+504    result : dict
+505        if full_output=True
+506    """
+507    return _parse_json_dict(json.loads(json_string), verbose, full_output)
 
@@ -1275,49 +1277,49 @@ if full_output=True
-
508def load_json(fname, verbose=True, gz=True, full_output=False):
-509    """Import a list of Obs or structures containing Obs from a .json(.gz) file.
-510
-511    The following structures are supported: Obs, list, numpy.ndarray, Corr
-512    If the list contains only one element, it is unpacked from the list.
-513
-514    Parameters
-515    ----------
-516    fname : str
-517        Filename of the input file.
-518    verbose : bool
-519        Print additional information that was written to the file.
-520    gz : bool
-521        If True, assumes that data is gzipped. If False, assumes JSON file.
-522    full_output : bool
-523        If True, a dict containing auxiliary information and the data is returned.
-524        If False, only the data is returned.
-525
-526    Returns
-527    -------
-528    result : list[Obs]
-529        reconstructed list of observables from the json string
-530    or
-531    result : Obs
-532        only one observable if the list only has one entry
-533    or
-534    result : dict
-535        if full_output=True
-536    """
-537    if not fname.endswith('.json') and not fname.endswith('.gz'):
-538        fname += '.json'
-539    if gz:
-540        if not fname.endswith('.gz'):
-541            fname += '.gz'
-542        with gzip.open(fname, 'r') as fin:
-543            d = json.load(fin)
-544    else:
-545        if fname.endswith('.gz'):
-546            warnings.warn("Trying to read from %s without unzipping!" % fname, UserWarning)
-547        with open(fname, 'r', encoding='utf-8') as fin:
-548            d = json.loads(fin.read())
-549
-550    return _parse_json_dict(d, verbose, full_output)
+            
510def load_json(fname, verbose=True, gz=True, full_output=False):
+511    """Import a list of Obs or structures containing Obs from a .json(.gz) file.
+512
+513    The following structures are supported: Obs, list, numpy.ndarray, Corr
+514    If the list contains only one element, it is unpacked from the list.
+515
+516    Parameters
+517    ----------
+518    fname : str
+519        Filename of the input file.
+520    verbose : bool
+521        Print additional information that was written to the file.
+522    gz : bool
+523        If True, assumes that data is gzipped. If False, assumes JSON file.
+524    full_output : bool
+525        If True, a dict containing auxiliary information and the data is returned.
+526        If False, only the data is returned.
+527
+528    Returns
+529    -------
+530    result : list[Obs]
+531        reconstructed list of observables from the json string
+532    or
+533    result : Obs
+534        only one observable if the list only has one entry
+535    or
+536    result : dict
+537        if full_output=True
+538    """
+539    if not fname.endswith('.json') and not fname.endswith('.gz'):
+540        fname += '.json'
+541    if gz:
+542        if not fname.endswith('.gz'):
+543            fname += '.gz'
+544        with gzip.open(fname, 'r') as fin:
+545            d = json.load(fin)
+546    else:
+547        if fname.endswith('.gz'):
+548            warnings.warn(f"Trying to read from {fname} without unzipping!", UserWarning, stacklevel=2)
+549        with open(fname, encoding='utf-8') as fin:
+550            d = json.loads(fin.read())
+551
+552    return _parse_json_dict(d, verbose, full_output)
 
@@ -1367,44 +1369,44 @@ if full_output=True
-
630def dump_dict_to_json(od, fname, description='', indent=1, reps='DICTOBS', gz=True):
-631    """Export a dict of Obs or structures containing Obs to a .json(.gz) file
-632
-633    Parameters
-634    ----------
-635    od : dict
-636        Dict of JSON valid structures and objects that will be exported.
-637        At the moment, these objects can be either of: Obs, list, numpy.ndarray, Corr.
-638        All Obs inside a structure have to be defined on the same set of configurations.
-639    fname : str
-640        Filename of the output file.
-641    description : str
-642        Optional string that describes the contents of the json file.
-643    indent : int
-644        Specify the indentation level of the json file. None or 0 is permissible and
-645        saves disk space.
-646    reps : str
-647        Specify the structure of the placeholder in exported dict to be reps[0-9]+.
-648    gz : bool
-649        If True, the output is a gzipped json. If False, the output is a json file.
-650
-651    Returns
-652    -------
-653    None
-654    """
-655
-656    if not isinstance(od, dict):
-657        raise Exception('od has to be a dictionary. Did you want to use dump_to_json?')
-658
-659    infostring = ('This JSON file contains a python dictionary that has been parsed to a list of structures. '
-660                  'OBSDICT contains the dictionary, where Obs or other structures have been replaced by '
-661                  '' + reps + '[0-9]+. The field description contains the additional description of this JSON file. '
-662                  'This file may be parsed to a dict with the pyerrors routine load_json_dict.')
-663
-664    desc_dict = {'INFO': infostring, 'OBSDICT': {}, 'description': description}
-665    ol, desc_dict['OBSDICT'] = _ol_from_dict(od, reps=reps)
-666
-667    dump_to_json(ol, fname, description=desc_dict, indent=indent, gz=gz)
+            
632def dump_dict_to_json(od, fname, description='', indent=1, reps='DICTOBS', gz=True):
+633    """Export a dict of Obs or structures containing Obs to a .json(.gz) file
+634
+635    Parameters
+636    ----------
+637    od : dict
+638        Dict of JSON valid structures and objects that will be exported.
+639        At the moment, these objects can be either of: Obs, list, numpy.ndarray, Corr.
+640        All Obs inside a structure have to be defined on the same set of configurations.
+641    fname : str
+642        Filename of the output file.
+643    description : str
+644        Optional string that describes the contents of the json file.
+645    indent : int
+646        Specify the indentation level of the json file. None or 0 is permissible and
+647        saves disk space.
+648    reps : str
+649        Specify the structure of the placeholder in exported dict to be reps[0-9]+.
+650    gz : bool
+651        If True, the output is a gzipped json. If False, the output is a json file.
+652
+653    Returns
+654    -------
+655    None
+656    """
+657
+658    if not isinstance(od, dict):
+659        raise Exception('od has to be a dictionary. Did you want to use dump_to_json?')
+660
+661    infostring = ('This JSON file contains a python dictionary that has been parsed to a list of structures. '
+662                  'OBSDICT contains the dictionary, where Obs or other structures have been replaced by '
+663                  '' + reps + '[0-9]+. The field description contains the additional description of this JSON file. '
+664                  'This file may be parsed to a dict with the pyerrors routine load_json_dict.')
+665
+666    desc_dict = {'INFO': infostring, 'OBSDICT': {}, 'description': description}
+667    ol, desc_dict['OBSDICT'] = _ol_from_dict(od, reps=reps)
+668
+669    dump_to_json(ol, fname, description=desc_dict, indent=indent, gz=gz)
 
@@ -1450,45 +1452,45 @@ If True, the output is a gzipped json. If False, the output is a json file.
-
731def load_json_dict(fname, verbose=True, gz=True, full_output=False, reps='DICTOBS'):
-732    """Import a dict of Obs or structures containing Obs from a .json(.gz) file.
-733
-734    The following structures are supported: Obs, list, numpy.ndarray, Corr
+            
733def load_json_dict(fname, verbose=True, gz=True, full_output=False, reps='DICTOBS'):
+734    """Import a dict of Obs or structures containing Obs from a .json(.gz) file.
 735
-736    Parameters
-737    ----------
-738    fname : str
-739        Filename of the input file.
-740    verbose : bool
-741        Print additional information that was written to the file.
-742    gz : bool
-743        If True, assumes that data is gzipped. If False, assumes JSON file.
-744    full_output : bool
-745        If True, a dict containing auxiliary information and the data is returned.
-746        If False, only the data is returned.
-747    reps : str
-748        Specify the structure of the placeholder in imported dict to be reps[0-9]+.
-749
-750    Returns
-751    -------
-752    data : Obs / list / Corr
-753        Read data
-754    or
-755    data : dict
-756        Read data and meta-data
-757    """
-758    indata = load_json(fname, verbose=verbose, gz=gz, full_output=True)
-759    description = indata['description']['description']
-760    indict = indata['description']['OBSDICT']
-761    ol = indata['obsdata']
-762    od = _od_from_list_and_dict(ol, indict, reps=reps)
-763
-764    if full_output:
-765        indata['description'] = description
-766        indata['obsdata'] = od
-767        return indata
-768    else:
-769        return od
+736    The following structures are supported: Obs, list, numpy.ndarray, Corr
+737
+738    Parameters
+739    ----------
+740    fname : str
+741        Filename of the input file.
+742    verbose : bool
+743        Print additional information that was written to the file.
+744    gz : bool
+745        If True, assumes that data is gzipped. If False, assumes JSON file.
+746    full_output : bool
+747        If True, a dict containing auxiliary information and the data is returned.
+748        If False, only the data is returned.
+749    reps : str
+750        Specify the structure of the placeholder in imported dict to be reps[0-9]+.
+751
+752    Returns
+753    -------
+754    data : Obs / list / Corr
+755        Read data
+756    or
+757    data : dict
+758        Read data and meta-data
+759    """
+760    indata = load_json(fname, verbose=verbose, gz=gz, full_output=True)
+761    description = indata['description']['description']
+762    indict = indata['description']['OBSDICT']
+763    ol = indata['obsdata']
+764    od = _od_from_list_and_dict(ol, indict, reps=reps)
+765
+766    if full_output:
+767        indata['description'] = description
+768        indata['obsdata'] = od
+769        return indata
+770    else:
+771        return od
 
diff --git a/docs/pyerrors/input/misc.html b/docs/pyerrors/input/misc.html index 7331f09f..66051087 100644 --- a/docs/pyerrors/input/misc.html +++ b/docs/pyerrors/input/misc.html @@ -79,224 +79,226 @@ -
  1import os
-  2import fnmatch
+                        
  1import fnmatch
+  2import os
   3import re
   4import struct
   5import warnings
-  6import numpy as np  # Thinly-wrapped numpy
+  6
   7import matplotlib.pyplot as plt
-  8from matplotlib import gridspec
-  9from ..obs import Obs
- 10from ..fits import fit_lin
- 11
- 12
- 13def fit_t0(t2E_dict, fit_range, plot_fit=False, observable='t0'):
- 14    """Compute the root of (flow-based) data based on a dictionary that contains
- 15    the necessary information in key-value pairs a la (flow time: observable at flow time).
- 16
- 17    It is assumed that the data is monotonically increasing and passes zero from below.
- 18    No exception is thrown if this is not the case (several roots, no monotonic increase).
- 19    An exception is thrown if no root can be found in the data.
- 20
- 21    A linear fit in the vicinity of the root is performed to exctract the root from the
- 22    two fit parameters.
- 23
- 24    Parameters
- 25    ----------
- 26    t2E_dict : dict
- 27        Dictionary with pairs of (flow time: observable at flow time) where the flow times
- 28        are of type float and the observables of type Obs.
- 29    fit_range : int
- 30        Number of data points left and right of the zero
- 31        crossing to be included in the linear fit.
- 32    plot_fit : bool
- 33        If true, the fit for the extraction of t0 is shown together with the data. (Default: False)
- 34    observable: str
- 35        Keyword to identify the observable to print the correct ylabel (if plot_fit is True)
- 36        for the observables 't0' and 'w0'. No y label is printed otherwise. (Default: 't0')
- 37
- 38    Returns
- 39    -------
- 40    root : Obs
- 41        The root of the data series.
- 42    """
- 43
- 44    zero_crossing = np.argmax(np.array(
- 45        [o.value for o in t2E_dict.values()]) > 0.0)
- 46
- 47    if zero_crossing == 0:
- 48        raise Exception('Desired flow time not in data')
- 49
- 50    x = list(t2E_dict.keys())[zero_crossing - fit_range:
- 51                              zero_crossing + fit_range]
- 52    y = list(t2E_dict.values())[zero_crossing - fit_range:
- 53                                zero_crossing + fit_range]
- 54    [o.gamma_method() for o in y]
- 55
- 56    if len(x) < 2 * fit_range:
- 57        warnings.warn('Fit range smaller than expected! Fitting from %1.2e to %1.2e' % (x[0], x[-1]))
- 58
- 59    fit_result = fit_lin(x, y)
+  8import numpy as np  # Thinly-wrapped numpy
+  9from matplotlib import gridspec
+ 10
+ 11from ..fits import fit_lin
+ 12from ..obs import Obs
+ 13
+ 14
+ 15def fit_t0(t2E_dict, fit_range, plot_fit=False, observable='t0'):
+ 16    """Compute the root of (flow-based) data based on a dictionary that contains
+ 17    the necessary information in key-value pairs a la (flow time: observable at flow time).
+ 18
+ 19    It is assumed that the data is monotonically increasing and passes zero from below.
+ 20    No exception is thrown if this is not the case (several roots, no monotonic increase).
+ 21    An exception is thrown if no root can be found in the data.
+ 22
+ 23    A linear fit in the vicinity of the root is performed to exctract the root from the
+ 24    two fit parameters.
+ 25
+ 26    Parameters
+ 27    ----------
+ 28    t2E_dict : dict
+ 29        Dictionary with pairs of (flow time: observable at flow time) where the flow times
+ 30        are of type float and the observables of type Obs.
+ 31    fit_range : int
+ 32        Number of data points left and right of the zero
+ 33        crossing to be included in the linear fit.
+ 34    plot_fit : bool
+ 35        If true, the fit for the extraction of t0 is shown together with the data. (Default: False)
+ 36    observable: str
+ 37        Keyword to identify the observable to print the correct ylabel (if plot_fit is True)
+ 38        for the observables 't0' and 'w0'. No y label is printed otherwise. (Default: 't0')
+ 39
+ 40    Returns
+ 41    -------
+ 42    root : Obs
+ 43        The root of the data series.
+ 44    """
+ 45
+ 46    zero_crossing = np.argmax(np.array(
+ 47        [o.value for o in t2E_dict.values()]) > 0.0)
+ 48
+ 49    if zero_crossing == 0:
+ 50        raise Exception('Desired flow time not in data')
+ 51
+ 52    x = list(t2E_dict.keys())[zero_crossing - fit_range:
+ 53                              zero_crossing + fit_range]
+ 54    y = list(t2E_dict.values())[zero_crossing - fit_range:
+ 55                                zero_crossing + fit_range]
+ 56    [o.gamma_method() for o in y]
+ 57
+ 58    if len(x) < 2 * fit_range:
+ 59        warnings.warn(f'Fit range smaller than expected! Fitting from {x[0]:1.2e} to {x[-1]:1.2e}', stacklevel=2)
  60
- 61    if plot_fit is True:
- 62        plt.figure()
- 63        gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0)
- 64        ax0 = plt.subplot(gs[0])
- 65        xmore = list(t2E_dict.keys())[zero_crossing - fit_range - 2: zero_crossing + fit_range + 2]
- 66        ymore = list(t2E_dict.values())[zero_crossing - fit_range - 2: zero_crossing + fit_range + 2]
- 67        [o.gamma_method() for o in ymore]
- 68        ax0.errorbar(xmore, [yi.value for yi in ymore], yerr=[yi.dvalue for yi in ymore], fmt='x')
- 69        xplot = np.linspace(np.min(x), np.max(x))
- 70        yplot = [fit_result[0] + fit_result[1] * xi for xi in xplot]
- 71        [yi.gamma_method() for yi in yplot]
- 72        ax0.fill_between(xplot, y1=[yi.value - yi.dvalue for yi in yplot], y2=[yi.value + yi.dvalue for yi in yplot])
- 73        retval = (-fit_result[0] / fit_result[1])
- 74        retval.gamma_method()
- 75        ylim = ax0.get_ylim()
- 76        ax0.fill_betweenx(ylim, x1=retval.value - retval.dvalue, x2=retval.value + retval.dvalue, color='gray', alpha=0.4)
- 77        ax0.set_ylim(ylim)
- 78        if observable == 't0':
- 79            ax0.set_ylabel(r'$t^2 \langle E(t) \rangle - 0.3 $')
- 80        elif observable == 'w0':
- 81            ax0.set_ylabel(r'$t d(t^2 \langle E(t) \rangle)/dt - 0.3 $')
- 82        xlim = ax0.get_xlim()
- 83
- 84        fit_res = [fit_result[0] + fit_result[1] * xi for xi in x]
- 85        residuals = (np.asarray([o.value for o in y]) - [o.value for o in fit_res]) / np.asarray([o.dvalue for o in y])
- 86        ax1 = plt.subplot(gs[1])
- 87        ax1.plot(x, residuals, 'ko', ls='none', markersize=5)
- 88        ax1.tick_params(direction='out')
- 89        ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True)
- 90        ax1.axhline(y=0.0, ls='--', color='k')
- 91        ax1.fill_between(xlim, -1.0, 1.0, alpha=0.1, facecolor='k')
- 92        ax1.set_xlim(xlim)
- 93        ax1.set_ylabel('Residuals')
- 94        ax1.set_xlabel(r'$t/a^2$')
- 95
- 96        plt.draw()
- 97    return -fit_result[0] / fit_result[1]
- 98
- 99
-100def read_pbp(path, prefix, **kwargs):
-101    """Read pbp format from given folder structure.
-102
-103    Parameters
-104    ----------
-105    r_start : list
-106        list which contains the first config to be read for each replicum
-107    r_stop : list
-108        list which contains the last config to be read for each replicum
-109
-110    Returns
-111    -------
-112    result : list[Obs]
-113        list of observables read
-114    """
-115
-116    ls = []
-117    for (dirpath, dirnames, filenames) in os.walk(path):
-118        ls.extend(filenames)
-119        break
-120
-121    if not ls:
-122        raise Exception('Error, directory not found')
-123
-124    # Exclude files with different names
-125    for exc in ls:
-126        if not fnmatch.fnmatch(exc, prefix + '*.dat'):
-127            ls = list(set(ls) - set([exc]))
-128    if len(ls) > 1:
-129        ls.sort(key=lambda x: int(re.findall(r'\d+', x[len(prefix):])[0]))
-130    replica = len(ls)
-131
-132    if 'r_start' in kwargs:
-133        r_start = kwargs.get('r_start')
-134        if len(r_start) != replica:
-135            raise Exception('r_start does not match number of replicas')
-136        # Adjust Configuration numbering to python index
-137        r_start = [o - 1 if o else None for o in r_start]
-138    else:
-139        r_start = [None] * replica
-140
-141    if 'r_stop' in kwargs:
-142        r_stop = kwargs.get('r_stop')
-143        if len(r_stop) != replica:
-144            raise Exception('r_stop does not match number of replicas')
-145    else:
-146        r_stop = [None] * replica
-147
-148    print(r'Read <bar{psi}\psi> from', prefix[:-1], ',', replica, 'replica', end='')
+ 61    fit_result = fit_lin(x, y)
+ 62
+ 63    if plot_fit is True:
+ 64        plt.figure()
+ 65        gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0)
+ 66        ax0 = plt.subplot(gs[0])
+ 67        xmore = list(t2E_dict.keys())[zero_crossing - fit_range - 2: zero_crossing + fit_range + 2]
+ 68        ymore = list(t2E_dict.values())[zero_crossing - fit_range - 2: zero_crossing + fit_range + 2]
+ 69        [o.gamma_method() for o in ymore]
+ 70        ax0.errorbar(xmore, [yi.value for yi in ymore], yerr=[yi.dvalue for yi in ymore], fmt='x')
+ 71        xplot = np.linspace(np.min(x), np.max(x))
+ 72        yplot = [fit_result[0] + fit_result[1] * xi for xi in xplot]
+ 73        [yi.gamma_method() for yi in yplot]
+ 74        ax0.fill_between(xplot, y1=[yi.value - yi.dvalue for yi in yplot], y2=[yi.value + yi.dvalue for yi in yplot])
+ 75        retval = (-fit_result[0] / fit_result[1])
+ 76        retval.gamma_method()
+ 77        ylim = ax0.get_ylim()
+ 78        ax0.fill_betweenx(ylim, x1=retval.value - retval.dvalue, x2=retval.value + retval.dvalue, color='gray', alpha=0.4)
+ 79        ax0.set_ylim(ylim)
+ 80        if observable == 't0':
+ 81            ax0.set_ylabel(r'$t^2 \langle E(t) \rangle - 0.3 $')
+ 82        elif observable == 'w0':
+ 83            ax0.set_ylabel(r'$t d(t^2 \langle E(t) \rangle)/dt - 0.3 $')
+ 84        xlim = ax0.get_xlim()
+ 85
+ 86        fit_res = [fit_result[0] + fit_result[1] * xi for xi in x]
+ 87        residuals = (np.asarray([o.value for o in y]) - [o.value for o in fit_res]) / np.asarray([o.dvalue for o in y])
+ 88        ax1 = plt.subplot(gs[1])
+ 89        ax1.plot(x, residuals, 'ko', ls='none', markersize=5)
+ 90        ax1.tick_params(direction='out')
+ 91        ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True)
+ 92        ax1.axhline(y=0.0, ls='--', color='k')
+ 93        ax1.fill_between(xlim, -1.0, 1.0, alpha=0.1, facecolor='k')
+ 94        ax1.set_xlim(xlim)
+ 95        ax1.set_ylabel('Residuals')
+ 96        ax1.set_xlabel(r'$t/a^2$')
+ 97
+ 98        plt.draw()
+ 99    return -fit_result[0] / fit_result[1]
+100
+101
+102def read_pbp(path, prefix, **kwargs):
+103    """Read pbp format from given folder structure.
+104
+105    Parameters
+106    ----------
+107    r_start : list
+108        list which contains the first config to be read for each replicum
+109    r_stop : list
+110        list which contains the last config to be read for each replicum
+111
+112    Returns
+113    -------
+114    result : list[Obs]
+115        list of observables read
+116    """
+117
+118    ls = []
+119    for (_dirpath, _dirnames, filenames) in os.walk(path):
+120        ls.extend(filenames)
+121        break
+122
+123    if not ls:
+124        raise Exception('Error, directory not found')
+125
+126    # Exclude files with different names
+127    for exc in ls:
+128        if not fnmatch.fnmatch(exc, prefix + '*.dat'):
+129            ls = list(set(ls) - set([exc]))
+130    if len(ls) > 1:
+131        ls.sort(key=lambda x: int(re.findall(r'\d+', x[len(prefix):])[0]))
+132    replica = len(ls)
+133
+134    if 'r_start' in kwargs:
+135        r_start = kwargs.get('r_start')
+136        if len(r_start) != replica:
+137            raise Exception('r_start does not match number of replicas')
+138        # Adjust Configuration numbering to python index
+139        r_start = [o - 1 if o else None for o in r_start]
+140    else:
+141        r_start = [None] * replica
+142
+143    if 'r_stop' in kwargs:
+144        r_stop = kwargs.get('r_stop')
+145        if len(r_stop) != replica:
+146            raise Exception('r_stop does not match number of replicas')
+147    else:
+148        r_stop = [None] * replica
 149
-150    print_err = 0
-151    if 'print_err' in kwargs:
-152        print_err = 1
-153        print()
-154
-155    deltas = []
+150    print(r'Read <bar{psi}\psi> from', prefix[:-1], ',', replica, 'replica', end='')
+151
+152    print_err = 0
+153    if 'print_err' in kwargs:
+154        print_err = 1
+155        print()
 156
-157    for rep in range(replica):
-158        tmp_array = []
-159        with open(path + '/' + ls[rep], 'rb') as fp:
-160
-161            t = fp.read(4)  # number of reweighting factors
-162            if rep == 0:
-163                nrw = struct.unpack('i', t)[0]
-164                for k in range(nrw):
-165                    deltas.append([])
-166            else:
-167                if nrw != struct.unpack('i', t)[0]:
-168                    raise Exception('Error: different number of factors for replicum', rep)
-169
-170            for k in range(nrw):
-171                tmp_array.append([])
-172
-173            # This block is necessary for openQCD1.6 ms1 files
-174            nfct = []
-175            for i in range(nrw):
-176                t = fp.read(4)
-177                nfct.append(struct.unpack('i', t)[0])
-178            print('nfct: ', nfct)  # Hasenbusch factor, 1 for rat reweighting
-179
-180            nsrc = []
-181            for i in range(nrw):
-182                t = fp.read(4)
-183                nsrc.append(struct.unpack('i', t)[0])
-184
-185            # body
-186            while True:
-187                t = fp.read(4)
-188                if len(t) < 4:
-189                    break
-190                if print_err:
-191                    config_no = struct.unpack('i', t)
-192                for i in range(nrw):
-193                    tmp_nfct = 1.0
-194                    for j in range(nfct[i]):
-195                        t = fp.read(8 * nsrc[i])
-196                        t = fp.read(8 * nsrc[i])
-197                        tmp_rw = struct.unpack('d' * nsrc[i], t)
-198                        tmp_nfct *= np.mean(np.asarray(tmp_rw))
-199                        if print_err:
-200                            print(config_no, i, j, np.mean(np.asarray(tmp_rw)), np.std(np.asarray(tmp_rw)))
-201                            print('Sources:', np.asarray(tmp_rw))
-202                            print('Partial factor:', tmp_nfct)
-203                    tmp_array[i].append(tmp_nfct)
-204
-205            for k in range(nrw):
-206                deltas[k].append(tmp_array[k][r_start[rep]:r_stop[rep]])
-207
-208    rep_names = []
-209    for entry in ls:
-210        truncated_entry = entry.split('.')[0]
-211        idx = truncated_entry.index('r')
-212        rep_names.append(truncated_entry[:idx] + '|' + truncated_entry[idx:])
-213    print(',', nrw, r'<bar{psi}\psi> with', nsrc, 'sources')
-214    result = []
-215    for t in range(nrw):
-216        result.append(Obs(deltas[t], rep_names))
-217
-218    return result
+157    deltas = []
+158
+159    for rep in range(replica):
+160        tmp_array = []
+161        with open(path + '/' + ls[rep], 'rb') as fp:
+162
+163            t = fp.read(4)  # number of reweighting factors
+164            if rep == 0:
+165                nrw = struct.unpack('i', t)[0]
+166                for _ in range(nrw):
+167                    deltas.append([])
+168            else:
+169                if nrw != struct.unpack('i', t)[0]:
+170                    raise Exception('Error: different number of factors for replicum', rep)
+171
+172            for _ in range(nrw):
+173                tmp_array.append([])
+174
+175            # This block is necessary for openQCD1.6 ms1 files
+176            nfct = []
+177            for _ in range(nrw):
+178                t = fp.read(4)
+179                nfct.append(struct.unpack('i', t)[0])
+180            print('nfct: ', nfct)  # Hasenbusch factor, 1 for rat reweighting
+181
+182            nsrc = []
+183            for _ in range(nrw):
+184                t = fp.read(4)
+185                nsrc.append(struct.unpack('i', t)[0])
+186
+187            # body
+188            while True:
+189                t = fp.read(4)
+190                if len(t) < 4:
+191                    break
+192                if print_err:
+193                    config_no = struct.unpack('i', t)
+194                for i in range(nrw):
+195                    tmp_nfct = 1.0
+196                    for j in range(nfct[i]):
+197                        t = fp.read(8 * nsrc[i])
+198                        t = fp.read(8 * nsrc[i])
+199                        tmp_rw = struct.unpack('d' * nsrc[i], t)
+200                        tmp_nfct *= np.mean(np.asarray(tmp_rw))
+201                        if print_err:
+202                            print(config_no, i, j, np.mean(np.asarray(tmp_rw)), np.std(np.asarray(tmp_rw)))
+203                            print('Sources:', np.asarray(tmp_rw))
+204                            print('Partial factor:', tmp_nfct)
+205                    tmp_array[i].append(tmp_nfct)
+206
+207            for k in range(nrw):
+208                deltas[k].append(tmp_array[k][r_start[rep]:r_stop[rep]])
+209
+210    rep_names = []
+211    for entry in ls:
+212        truncated_entry = entry.split('.')[0]
+213        idx = truncated_entry.index('r')
+214        rep_names.append(truncated_entry[:idx] + '|' + truncated_entry[idx:])
+215    print(',', nrw, r'<bar{psi}\psi> with', nsrc, 'sources')
+216    result = []
+217    for t in range(nrw):
+218        result.append(Obs(deltas[t], rep_names))
+219
+220    return result
 
@@ -312,91 +314,91 @@
-
14def fit_t0(t2E_dict, fit_range, plot_fit=False, observable='t0'):
-15    """Compute the root of (flow-based) data based on a dictionary that contains
-16    the necessary information in key-value pairs a la (flow time: observable at flow time).
-17
-18    It is assumed that the data is monotonically increasing and passes zero from below.
-19    No exception is thrown if this is not the case (several roots, no monotonic increase).
-20    An exception is thrown if no root can be found in the data.
-21
-22    A linear fit in the vicinity of the root is performed to exctract the root from the
-23    two fit parameters.
-24
-25    Parameters
-26    ----------
-27    t2E_dict : dict
-28        Dictionary with pairs of (flow time: observable at flow time) where the flow times
-29        are of type float and the observables of type Obs.
-30    fit_range : int
-31        Number of data points left and right of the zero
-32        crossing to be included in the linear fit.
-33    plot_fit : bool
-34        If true, the fit for the extraction of t0 is shown together with the data. (Default: False)
-35    observable: str
-36        Keyword to identify the observable to print the correct ylabel (if plot_fit is True)
-37        for the observables 't0' and 'w0'. No y label is printed otherwise. (Default: 't0')
-38
-39    Returns
-40    -------
-41    root : Obs
-42        The root of the data series.
-43    """
-44
-45    zero_crossing = np.argmax(np.array(
-46        [o.value for o in t2E_dict.values()]) > 0.0)
-47
-48    if zero_crossing == 0:
-49        raise Exception('Desired flow time not in data')
-50
-51    x = list(t2E_dict.keys())[zero_crossing - fit_range:
-52                              zero_crossing + fit_range]
-53    y = list(t2E_dict.values())[zero_crossing - fit_range:
-54                                zero_crossing + fit_range]
-55    [o.gamma_method() for o in y]
-56
-57    if len(x) < 2 * fit_range:
-58        warnings.warn('Fit range smaller than expected! Fitting from %1.2e to %1.2e' % (x[0], x[-1]))
-59
-60    fit_result = fit_lin(x, y)
-61
-62    if plot_fit is True:
-63        plt.figure()
-64        gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0)
-65        ax0 = plt.subplot(gs[0])
-66        xmore = list(t2E_dict.keys())[zero_crossing - fit_range - 2: zero_crossing + fit_range + 2]
-67        ymore = list(t2E_dict.values())[zero_crossing - fit_range - 2: zero_crossing + fit_range + 2]
-68        [o.gamma_method() for o in ymore]
-69        ax0.errorbar(xmore, [yi.value for yi in ymore], yerr=[yi.dvalue for yi in ymore], fmt='x')
-70        xplot = np.linspace(np.min(x), np.max(x))
-71        yplot = [fit_result[0] + fit_result[1] * xi for xi in xplot]
-72        [yi.gamma_method() for yi in yplot]
-73        ax0.fill_between(xplot, y1=[yi.value - yi.dvalue for yi in yplot], y2=[yi.value + yi.dvalue for yi in yplot])
-74        retval = (-fit_result[0] / fit_result[1])
-75        retval.gamma_method()
-76        ylim = ax0.get_ylim()
-77        ax0.fill_betweenx(ylim, x1=retval.value - retval.dvalue, x2=retval.value + retval.dvalue, color='gray', alpha=0.4)
-78        ax0.set_ylim(ylim)
-79        if observable == 't0':
-80            ax0.set_ylabel(r'$t^2 \langle E(t) \rangle - 0.3 $')
-81        elif observable == 'w0':
-82            ax0.set_ylabel(r'$t d(t^2 \langle E(t) \rangle)/dt - 0.3 $')
-83        xlim = ax0.get_xlim()
-84
-85        fit_res = [fit_result[0] + fit_result[1] * xi for xi in x]
-86        residuals = (np.asarray([o.value for o in y]) - [o.value for o in fit_res]) / np.asarray([o.dvalue for o in y])
-87        ax1 = plt.subplot(gs[1])
-88        ax1.plot(x, residuals, 'ko', ls='none', markersize=5)
-89        ax1.tick_params(direction='out')
-90        ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True)
-91        ax1.axhline(y=0.0, ls='--', color='k')
-92        ax1.fill_between(xlim, -1.0, 1.0, alpha=0.1, facecolor='k')
-93        ax1.set_xlim(xlim)
-94        ax1.set_ylabel('Residuals')
-95        ax1.set_xlabel(r'$t/a^2$')
-96
-97        plt.draw()
-98    return -fit_result[0] / fit_result[1]
+            
 16def fit_t0(t2E_dict, fit_range, plot_fit=False, observable='t0'):
+ 17    """Compute the root of (flow-based) data based on a dictionary that contains
+ 18    the necessary information in key-value pairs a la (flow time: observable at flow time).
+ 19
+ 20    It is assumed that the data is monotonically increasing and passes zero from below.
+ 21    No exception is thrown if this is not the case (several roots, no monotonic increase).
+ 22    An exception is thrown if no root can be found in the data.
+ 23
+ 24    A linear fit in the vicinity of the root is performed to exctract the root from the
+ 25    two fit parameters.
+ 26
+ 27    Parameters
+ 28    ----------
+ 29    t2E_dict : dict
+ 30        Dictionary with pairs of (flow time: observable at flow time) where the flow times
+ 31        are of type float and the observables of type Obs.
+ 32    fit_range : int
+ 33        Number of data points left and right of the zero
+ 34        crossing to be included in the linear fit.
+ 35    plot_fit : bool
+ 36        If true, the fit for the extraction of t0 is shown together with the data. (Default: False)
+ 37    observable: str
+ 38        Keyword to identify the observable to print the correct ylabel (if plot_fit is True)
+ 39        for the observables 't0' and 'w0'. No y label is printed otherwise. (Default: 't0')
+ 40
+ 41    Returns
+ 42    -------
+ 43    root : Obs
+ 44        The root of the data series.
+ 45    """
+ 46
+ 47    zero_crossing = np.argmax(np.array(
+ 48        [o.value for o in t2E_dict.values()]) > 0.0)
+ 49
+ 50    if zero_crossing == 0:
+ 51        raise Exception('Desired flow time not in data')
+ 52
+ 53    x = list(t2E_dict.keys())[zero_crossing - fit_range:
+ 54                              zero_crossing + fit_range]
+ 55    y = list(t2E_dict.values())[zero_crossing - fit_range:
+ 56                                zero_crossing + fit_range]
+ 57    [o.gamma_method() for o in y]
+ 58
+ 59    if len(x) < 2 * fit_range:
+ 60        warnings.warn(f'Fit range smaller than expected! Fitting from {x[0]:1.2e} to {x[-1]:1.2e}', stacklevel=2)
+ 61
+ 62    fit_result = fit_lin(x, y)
+ 63
+ 64    if plot_fit is True:
+ 65        plt.figure()
+ 66        gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0)
+ 67        ax0 = plt.subplot(gs[0])
+ 68        xmore = list(t2E_dict.keys())[zero_crossing - fit_range - 2: zero_crossing + fit_range + 2]
+ 69        ymore = list(t2E_dict.values())[zero_crossing - fit_range - 2: zero_crossing + fit_range + 2]
+ 70        [o.gamma_method() for o in ymore]
+ 71        ax0.errorbar(xmore, [yi.value for yi in ymore], yerr=[yi.dvalue for yi in ymore], fmt='x')
+ 72        xplot = np.linspace(np.min(x), np.max(x))
+ 73        yplot = [fit_result[0] + fit_result[1] * xi for xi in xplot]
+ 74        [yi.gamma_method() for yi in yplot]
+ 75        ax0.fill_between(xplot, y1=[yi.value - yi.dvalue for yi in yplot], y2=[yi.value + yi.dvalue for yi in yplot])
+ 76        retval = (-fit_result[0] / fit_result[1])
+ 77        retval.gamma_method()
+ 78        ylim = ax0.get_ylim()
+ 79        ax0.fill_betweenx(ylim, x1=retval.value - retval.dvalue, x2=retval.value + retval.dvalue, color='gray', alpha=0.4)
+ 80        ax0.set_ylim(ylim)
+ 81        if observable == 't0':
+ 82            ax0.set_ylabel(r'$t^2 \langle E(t) \rangle - 0.3 $')
+ 83        elif observable == 'w0':
+ 84            ax0.set_ylabel(r'$t d(t^2 \langle E(t) \rangle)/dt - 0.3 $')
+ 85        xlim = ax0.get_xlim()
+ 86
+ 87        fit_res = [fit_result[0] + fit_result[1] * xi for xi in x]
+ 88        residuals = (np.asarray([o.value for o in y]) - [o.value for o in fit_res]) / np.asarray([o.dvalue for o in y])
+ 89        ax1 = plt.subplot(gs[1])
+ 90        ax1.plot(x, residuals, 'ko', ls='none', markersize=5)
+ 91        ax1.tick_params(direction='out')
+ 92        ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True)
+ 93        ax1.axhline(y=0.0, ls='--', color='k')
+ 94        ax1.fill_between(xlim, -1.0, 1.0, alpha=0.1, facecolor='k')
+ 95        ax1.set_xlim(xlim)
+ 96        ax1.set_ylabel('Residuals')
+ 97        ax1.set_xlabel(r'$t/a^2$')
+ 98
+ 99        plt.draw()
+100    return -fit_result[0] / fit_result[1]
 
@@ -447,125 +449,125 @@ The root of the data series.
-
101def read_pbp(path, prefix, **kwargs):
-102    """Read pbp format from given folder structure.
-103
-104    Parameters
-105    ----------
-106    r_start : list
-107        list which contains the first config to be read for each replicum
-108    r_stop : list
-109        list which contains the last config to be read for each replicum
-110
-111    Returns
-112    -------
-113    result : list[Obs]
-114        list of observables read
-115    """
-116
-117    ls = []
-118    for (dirpath, dirnames, filenames) in os.walk(path):
-119        ls.extend(filenames)
-120        break
-121
-122    if not ls:
-123        raise Exception('Error, directory not found')
-124
-125    # Exclude files with different names
-126    for exc in ls:
-127        if not fnmatch.fnmatch(exc, prefix + '*.dat'):
-128            ls = list(set(ls) - set([exc]))
-129    if len(ls) > 1:
-130        ls.sort(key=lambda x: int(re.findall(r'\d+', x[len(prefix):])[0]))
-131    replica = len(ls)
-132
-133    if 'r_start' in kwargs:
-134        r_start = kwargs.get('r_start')
-135        if len(r_start) != replica:
-136            raise Exception('r_start does not match number of replicas')
-137        # Adjust Configuration numbering to python index
-138        r_start = [o - 1 if o else None for o in r_start]
-139    else:
-140        r_start = [None] * replica
-141
-142    if 'r_stop' in kwargs:
-143        r_stop = kwargs.get('r_stop')
-144        if len(r_stop) != replica:
-145            raise Exception('r_stop does not match number of replicas')
-146    else:
-147        r_stop = [None] * replica
-148
-149    print(r'Read <bar{psi}\psi> from', prefix[:-1], ',', replica, 'replica', end='')
+            
103def read_pbp(path, prefix, **kwargs):
+104    """Read pbp format from given folder structure.
+105
+106    Parameters
+107    ----------
+108    r_start : list
+109        list which contains the first config to be read for each replicum
+110    r_stop : list
+111        list which contains the last config to be read for each replicum
+112
+113    Returns
+114    -------
+115    result : list[Obs]
+116        list of observables read
+117    """
+118
+119    ls = []
+120    for (_dirpath, _dirnames, filenames) in os.walk(path):
+121        ls.extend(filenames)
+122        break
+123
+124    if not ls:
+125        raise Exception('Error, directory not found')
+126
+127    # Exclude files with different names
+128    for exc in ls:
+129        if not fnmatch.fnmatch(exc, prefix + '*.dat'):
+130            ls = list(set(ls) - set([exc]))
+131    if len(ls) > 1:
+132        ls.sort(key=lambda x: int(re.findall(r'\d+', x[len(prefix):])[0]))
+133    replica = len(ls)
+134
+135    if 'r_start' in kwargs:
+136        r_start = kwargs.get('r_start')
+137        if len(r_start) != replica:
+138            raise Exception('r_start does not match number of replicas')
+139        # Adjust Configuration numbering to python index
+140        r_start = [o - 1 if o else None for o in r_start]
+141    else:
+142        r_start = [None] * replica
+143
+144    if 'r_stop' in kwargs:
+145        r_stop = kwargs.get('r_stop')
+146        if len(r_stop) != replica:
+147            raise Exception('r_stop does not match number of replicas')
+148    else:
+149        r_stop = [None] * replica
 150
-151    print_err = 0
-152    if 'print_err' in kwargs:
-153        print_err = 1
-154        print()
-155
-156    deltas = []
+151    print(r'Read <bar{psi}\psi> from', prefix[:-1], ',', replica, 'replica', end='')
+152
+153    print_err = 0
+154    if 'print_err' in kwargs:
+155        print_err = 1
+156        print()
 157
-158    for rep in range(replica):
-159        tmp_array = []
-160        with open(path + '/' + ls[rep], 'rb') as fp:
-161
-162            t = fp.read(4)  # number of reweighting factors
-163            if rep == 0:
-164                nrw = struct.unpack('i', t)[0]
-165                for k in range(nrw):
-166                    deltas.append([])
-167            else:
-168                if nrw != struct.unpack('i', t)[0]:
-169                    raise Exception('Error: different number of factors for replicum', rep)
-170
-171            for k in range(nrw):
-172                tmp_array.append([])
-173
-174            # This block is necessary for openQCD1.6 ms1 files
-175            nfct = []
-176            for i in range(nrw):
-177                t = fp.read(4)
-178                nfct.append(struct.unpack('i', t)[0])
-179            print('nfct: ', nfct)  # Hasenbusch factor, 1 for rat reweighting
-180
-181            nsrc = []
-182            for i in range(nrw):
-183                t = fp.read(4)
-184                nsrc.append(struct.unpack('i', t)[0])
-185
-186            # body
-187            while True:
-188                t = fp.read(4)
-189                if len(t) < 4:
-190                    break
-191                if print_err:
-192                    config_no = struct.unpack('i', t)
-193                for i in range(nrw):
-194                    tmp_nfct = 1.0
-195                    for j in range(nfct[i]):
-196                        t = fp.read(8 * nsrc[i])
-197                        t = fp.read(8 * nsrc[i])
-198                        tmp_rw = struct.unpack('d' * nsrc[i], t)
-199                        tmp_nfct *= np.mean(np.asarray(tmp_rw))
-200                        if print_err:
-201                            print(config_no, i, j, np.mean(np.asarray(tmp_rw)), np.std(np.asarray(tmp_rw)))
-202                            print('Sources:', np.asarray(tmp_rw))
-203                            print('Partial factor:', tmp_nfct)
-204                    tmp_array[i].append(tmp_nfct)
-205
-206            for k in range(nrw):
-207                deltas[k].append(tmp_array[k][r_start[rep]:r_stop[rep]])
-208
-209    rep_names = []
-210    for entry in ls:
-211        truncated_entry = entry.split('.')[0]
-212        idx = truncated_entry.index('r')
-213        rep_names.append(truncated_entry[:idx] + '|' + truncated_entry[idx:])
-214    print(',', nrw, r'<bar{psi}\psi> with', nsrc, 'sources')
-215    result = []
-216    for t in range(nrw):
-217        result.append(Obs(deltas[t], rep_names))
-218
-219    return result
+158    deltas = []
+159
+160    for rep in range(replica):
+161        tmp_array = []
+162        with open(path + '/' + ls[rep], 'rb') as fp:
+163
+164            t = fp.read(4)  # number of reweighting factors
+165            if rep == 0:
+166                nrw = struct.unpack('i', t)[0]
+167                for _ in range(nrw):
+168                    deltas.append([])
+169            else:
+170                if nrw != struct.unpack('i', t)[0]:
+171                    raise Exception('Error: different number of factors for replicum', rep)
+172
+173            for _ in range(nrw):
+174                tmp_array.append([])
+175
+176            # This block is necessary for openQCD1.6 ms1 files
+177            nfct = []
+178            for _ in range(nrw):
+179                t = fp.read(4)
+180                nfct.append(struct.unpack('i', t)[0])
+181            print('nfct: ', nfct)  # Hasenbusch factor, 1 for rat reweighting
+182
+183            nsrc = []
+184            for _ in range(nrw):
+185                t = fp.read(4)
+186                nsrc.append(struct.unpack('i', t)[0])
+187
+188            # body
+189            while True:
+190                t = fp.read(4)
+191                if len(t) < 4:
+192                    break
+193                if print_err:
+194                    config_no = struct.unpack('i', t)
+195                for i in range(nrw):
+196                    tmp_nfct = 1.0
+197                    for j in range(nfct[i]):
+198                        t = fp.read(8 * nsrc[i])
+199                        t = fp.read(8 * nsrc[i])
+200                        tmp_rw = struct.unpack('d' * nsrc[i], t)
+201                        tmp_nfct *= np.mean(np.asarray(tmp_rw))
+202                        if print_err:
+203                            print(config_no, i, j, np.mean(np.asarray(tmp_rw)), np.std(np.asarray(tmp_rw)))
+204                            print('Sources:', np.asarray(tmp_rw))
+205                            print('Partial factor:', tmp_nfct)
+206                    tmp_array[i].append(tmp_nfct)
+207
+208            for k in range(nrw):
+209                deltas[k].append(tmp_array[k][r_start[rep]:r_stop[rep]])
+210
+211    rep_names = []
+212    for entry in ls:
+213        truncated_entry = entry.split('.')[0]
+214        idx = truncated_entry.index('r')
+215        rep_names.append(truncated_entry[:idx] + '|' + truncated_entry[idx:])
+216    print(',', nrw, r'<bar{psi}\psi> with', nsrc, 'sources')
+217    result = []
+218    for t in range(nrw):
+219        result.append(Obs(deltas[t], rep_names))
+220
+221    return result
 
diff --git a/docs/pyerrors/input/openQCD.html b/docs/pyerrors/input/openQCD.html index 25893579..2a1166f8 100644 --- a/docs/pyerrors/input/openQCD.html +++ b/docs/pyerrors/input/openQCD.html @@ -97,1321 +97,1329 @@ -
   1import os
-   2import fnmatch
+                        
   1import fnmatch
+   2import os
    3import struct
    4import warnings
-   5import numpy as np  # Thinly-wrapped numpy
-   6from ..obs import Obs
-   7from ..obs import CObs
+   5
+   6import numpy as np  # Thinly-wrapped numpy
+   7
    8from ..correlators import Corr
-   9from .misc import fit_t0
-  10from .utils import sort_names
-  11
+   9from ..obs import CObs, Obs
+  10from .misc import fit_t0
+  11from .utils import sort_names
   12
-  13def read_rwms(path, prefix, version='2.0', names=None, **kwargs):
-  14    """Read rwms format from given folder structure. Returns a list of length nrw
-  15
-  16    Parameters
-  17    ----------
-  18    path : str
-  19        path that contains the data files
-  20    prefix : str
-  21        all files in path that start with prefix are considered as input files.
-  22        May be used together postfix to consider only special file endings.
-  23        Prefix is ignored, if the keyword 'files' is used.
-  24    version : str
-  25        version of openQCD, default 2.0
-  26    names : list
-  27        list of names that is assigned to the data according according
-  28        to the order in the file list. Use careful, if you do not provide file names!
-  29    r_start : list
-  30        list which contains the first config to be read for each replicum
-  31    r_stop : list
-  32        list which contains the last config to be read for each replicum
-  33    r_step : int
-  34        integer that defines a fixed step size between two measurements (in units of configs)
-  35        If not given, r_step=1 is assumed.
-  36    postfix : str
-  37        postfix of the file to read, e.g. '.ms1' for openQCD-files
-  38    files : list
-  39        list which contains the filenames to be read. No automatic detection of
-  40        files performed if given.
-  41    print_err : bool
-  42        Print additional information that is useful for debugging.
-  43
-  44    Returns
-  45    -------
-  46    rwms : Obs
-  47        Reweighting factors read
-  48    """
-  49    known_oqcd_versions = ['1.4', '1.6', '2.0']
-  50    if version not in known_oqcd_versions:
-  51        raise Exception('Unknown openQCD version defined!')
-  52    print("Working with openQCD version " + version)
-  53    if 'postfix' in kwargs:
-  54        postfix = kwargs.get('postfix')
-  55    else:
-  56        postfix = ''
-  57
-  58    if 'files' in kwargs:
-  59        known_files = kwargs.get('files')
-  60    else:
-  61        known_files = []
-  62
-  63    ls = _find_files(path, prefix, postfix, 'dat', known_files=known_files)
-  64
-  65    replica = len(ls)
-  66
-  67    if 'r_start' in kwargs:
-  68        r_start = kwargs.get('r_start')
-  69        if len(r_start) != replica:
-  70            raise Exception('r_start does not match number of replicas')
-  71        r_start = [o if o else None for o in r_start]
-  72    else:
-  73        r_start = [None] * replica
-  74
-  75    if 'r_stop' in kwargs:
-  76        r_stop = kwargs.get('r_stop')
-  77        if len(r_stop) != replica:
-  78            raise Exception('r_stop does not match number of replicas')
-  79    else:
-  80        r_stop = [None] * replica
-  81
-  82    if 'r_step' in kwargs:
-  83        r_step = kwargs.get('r_step')
-  84    else:
-  85        r_step = 1
-  86
-  87    print('Read reweighting factors from', prefix[:-1], ',',
-  88          replica, 'replica', end='')
-  89
-  90    if names is None:
-  91        rep_names = []
-  92        for entry in ls:
-  93            truncated_entry = entry
-  94            suffixes = [".dat", ".rwms", ".ms1"]
-  95            for suffix in suffixes:
-  96                if truncated_entry.endswith(suffix):
-  97                    truncated_entry = truncated_entry[0:-len(suffix)]
-  98            idx = truncated_entry.index('r')
-  99            rep_names.append(truncated_entry[:idx] + '|' + truncated_entry[idx:])
- 100    else:
- 101        rep_names = names
- 102
- 103    rep_names = sort_names(rep_names)
- 104
- 105    print_err = 0
- 106    if 'print_err' in kwargs:
- 107        print_err = 1
- 108        print()
- 109
- 110    deltas = []
- 111
- 112    configlist = []
- 113    r_start_index = []
- 114    r_stop_index = []
- 115
- 116    for rep in range(replica):
- 117        tmp_array = []
- 118        with open(path + '/' + ls[rep], 'rb') as fp:
- 119
- 120            t = fp.read(4)  # number of reweighting factors
- 121            if rep == 0:
- 122                nrw = struct.unpack('i', t)[0]
- 123                if version == '2.0':
- 124                    nrw = int(nrw / 2)
- 125                for k in range(nrw):
- 126                    deltas.append([])
- 127            else:
- 128                if ((nrw != struct.unpack('i', t)[0] and (not version == '2.0')) or (nrw != struct.unpack('i', t)[0] / 2 and version == '2.0')):
- 129                    raise Exception('Error: different number of reweighting factors for replicum', rep)
- 130
- 131            for k in range(nrw):
- 132                tmp_array.append([])
- 133
- 134            # This block is necessary for openQCD1.6 and openQCD2.0 ms1 files
- 135            nfct = []
- 136            if version in ['1.6', '2.0']:
- 137                for i in range(nrw):
- 138                    t = fp.read(4)
- 139                    nfct.append(struct.unpack('i', t)[0])
- 140            else:
- 141                for i in range(nrw):
- 142                    nfct.append(1)
- 143
- 144            nsrc = []
- 145            for i in range(nrw):
- 146                t = fp.read(4)
- 147                nsrc.append(struct.unpack('i', t)[0])
- 148            if version == '2.0':
- 149                if not struct.unpack('i', fp.read(4))[0] == 0:
- 150                    raise Exception("You are using the input for openQCD version 2.0, this is not correct.")
- 151
- 152            configlist.append([])
- 153            while True:
- 154                t = fp.read(4)
- 155                if len(t) < 4:
- 156                    break
- 157                config_no = struct.unpack('i', t)[0]
- 158                configlist[-1].append(config_no)
- 159                for i in range(nrw):
- 160                    if (version == '2.0'):
- 161                        tmpd = _read_array_openQCD2(fp)
+  13
+  14def read_rwms(path, prefix, version='2.0', names=None, **kwargs):
+  15    """Read rwms format from given folder structure. Returns a list of length nrw
+  16
+  17    Parameters
+  18    ----------
+  19    path : str
+  20        path that contains the data files
+  21    prefix : str
+  22        all files in path that start with prefix are considered as input files.
+  23        May be used together postfix to consider only special file endings.
+  24        Prefix is ignored, if the keyword 'files' is used.
+  25    version : str
+  26        version of openQCD, default 2.0
+  27    names : list
+  28        list of names that is assigned to the data according according
+  29        to the order in the file list. Use careful, if you do not provide file names!
+  30    r_start : list
+  31        list which contains the first config to be read for each replicum
+  32    r_stop : list
+  33        list which contains the last config to be read for each replicum
+  34    r_step : int
+  35        integer that defines a fixed step size between two measurements (in units of configs)
+  36        If not given, r_step=1 is assumed.
+  37    postfix : str
+  38        postfix of the file to read, e.g. '.ms1' for openQCD-files
+  39    files : list
+  40        list which contains the filenames to be read. No automatic detection of
+  41        files performed if given.
+  42    print_err : bool
+  43        Print additional information that is useful for debugging.
+  44
+  45    Returns
+  46    -------
+  47    rwms : Obs
+  48        Reweighting factors read
+  49    """
+  50    known_oqcd_versions = ['1.4', '1.6', '2.0']
+  51    if version not in known_oqcd_versions:
+  52        raise Exception('Unknown openQCD version defined!')
+  53    print("Working with openQCD version " + version)
+  54    if 'postfix' in kwargs:
+  55        postfix = kwargs.get('postfix')
+  56    else:
+  57        postfix = ''
+  58
+  59    if 'files' in kwargs:
+  60        known_files = kwargs.get('files')
+  61    else:
+  62        known_files = []
+  63
+  64    ls = _find_files(path, prefix, postfix, 'dat', known_files=known_files)
+  65
+  66    replica = len(ls)
+  67
+  68    if 'r_start' in kwargs:
+  69        r_start = kwargs.get('r_start')
+  70        if len(r_start) != replica:
+  71            raise Exception('r_start does not match number of replicas')
+  72        r_start = [o if o else None for o in r_start]
+  73    else:
+  74        r_start = [None] * replica
+  75
+  76    if 'r_stop' in kwargs:
+  77        r_stop = kwargs.get('r_stop')
+  78        if len(r_stop) != replica:
+  79            raise Exception('r_stop does not match number of replicas')
+  80    else:
+  81        r_stop = [None] * replica
+  82
+  83    if 'r_step' in kwargs:
+  84        r_step = kwargs.get('r_step')
+  85    else:
+  86        r_step = 1
+  87
+  88    print('Read reweighting factors from', prefix[:-1], ',',
+  89          replica, 'replica', end='')
+  90
+  91    if names is None:
+  92        rep_names = []
+  93        for entry in ls:
+  94            truncated_entry = entry
+  95            suffixes = [".dat", ".rwms", ".ms1"]
+  96            for suffix in suffixes:
+  97                if truncated_entry.endswith(suffix):
+  98                    truncated_entry = truncated_entry[0:-len(suffix)]
+  99            idx = truncated_entry.index('r')
+ 100            rep_names.append(truncated_entry[:idx] + '|' + truncated_entry[idx:])
+ 101    else:
+ 102        rep_names = names
+ 103
+ 104    rep_names = sort_names(rep_names)
+ 105
+ 106    print_err = 0
+ 107    if 'print_err' in kwargs:
+ 108        print_err = 1
+ 109        print()
+ 110
+ 111    deltas = []
+ 112
+ 113    configlist = []
+ 114    r_start_index = []
+ 115    r_stop_index = []
+ 116
+ 117    for rep in range(replica):
+ 118        tmp_array = []
+ 119        with open(path + '/' + ls[rep], 'rb') as fp:
+ 120
+ 121            t = fp.read(4)  # number of reweighting factors
+ 122            if rep == 0:
+ 123                nrw = struct.unpack('i', t)[0]
+ 124                if version == '2.0':
+ 125                    nrw = int(nrw / 2)
+ 126                for _ in range(nrw):
+ 127                    deltas.append([])
+ 128            else:
+ 129                if ((nrw != struct.unpack('i', t)[0] and (not version == '2.0')) or (nrw != struct.unpack('i', t)[0] / 2 and version == '2.0')):
+ 130                    raise Exception('Error: different number of reweighting factors for replicum', rep)
+ 131
+ 132            for _ in range(nrw):
+ 133                tmp_array.append([])
+ 134
+ 135            # This block is necessary for openQCD1.6 and openQCD2.0 ms1 files
+ 136            nfct = []
+ 137            if version in ['1.6', '2.0']:
+ 138                for _ in range(nrw):
+ 139                    t = fp.read(4)
+ 140                    nfct.append(struct.unpack('i', t)[0])
+ 141            else:
+ 142                for _ in range(nrw):
+ 143                    nfct.append(1)
+ 144
+ 145            nsrc = []
+ 146            for _ in range(nrw):
+ 147                t = fp.read(4)
+ 148                nsrc.append(struct.unpack('i', t)[0])
+ 149            if version == '2.0':
+ 150                if not struct.unpack('i', fp.read(4))[0] == 0:
+ 151                    raise Exception("You are using the input for openQCD version 2.0, this is not correct.")
+ 152
+ 153            configlist.append([])
+ 154            while True:
+ 155                t = fp.read(4)
+ 156                if len(t) < 4:
+ 157                    break
+ 158                config_no = struct.unpack('i', t)[0]
+ 159                configlist[-1].append(config_no)
+ 160                for i in range(nrw):
+ 161                    if (version == '2.0'):
  162                        tmpd = _read_array_openQCD2(fp)
- 163                        tmp_rw = tmpd['arr']
- 164                        tmp_nfct = 1.0
- 165                        for j in range(tmpd['n'][0]):
- 166                            tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw[j])))
- 167                            if print_err:
- 168                                print(config_no, i, j,
- 169                                      np.mean(np.exp(-np.asarray(tmp_rw[j]))),
- 170                                      np.std(np.exp(-np.asarray(tmp_rw[j]))))
- 171                                print('Sources:',
- 172                                      np.exp(-np.asarray(tmp_rw[j])))
- 173                                print('Partial factor:', tmp_nfct)
- 174                    elif version == '1.6' or version == '1.4':
- 175                        tmp_nfct = 1.0
- 176                        for j in range(nfct[i]):
- 177                            t = fp.read(8 * nsrc[i])
+ 163                        tmpd = _read_array_openQCD2(fp)
+ 164                        tmp_rw = tmpd['arr']
+ 165                        tmp_nfct = 1.0
+ 166                        for j in range(tmpd['n'][0]):
+ 167                            tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw[j])))
+ 168                            if print_err:
+ 169                                print(config_no, i, j,
+ 170                                      np.mean(np.exp(-np.asarray(tmp_rw[j]))),
+ 171                                      np.std(np.exp(-np.asarray(tmp_rw[j]))))
+ 172                                print('Sources:',
+ 173                                      np.exp(-np.asarray(tmp_rw[j])))
+ 174                                print('Partial factor:', tmp_nfct)
+ 175                    elif version == '1.6' or version == '1.4':
+ 176                        tmp_nfct = 1.0
+ 177                        for j in range(nfct[i]):
  178                            t = fp.read(8 * nsrc[i])
- 179                            tmp_rw = struct.unpack('d' * nsrc[i], t)
- 180                            tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw)))
- 181                            if print_err:
- 182                                print(config_no, i, j,
- 183                                      np.mean(np.exp(-np.asarray(tmp_rw))),
- 184                                      np.std(np.exp(-np.asarray(tmp_rw))))
- 185                                print('Sources:', np.exp(-np.asarray(tmp_rw)))
- 186                                print('Partial factor:', tmp_nfct)
- 187                    tmp_array[i].append(tmp_nfct)
- 188
- 189            diffmeas = configlist[-1][-1] - configlist[-1][-2]
- 190            configlist[-1] = [item // diffmeas for item in configlist[-1]]
- 191            if configlist[-1][0] > 1 and diffmeas > 1:
- 192                warnings.warn('Assume thermalization and that the first measurement belongs to the first config.')
- 193                offset = configlist[-1][0] - 1
- 194                configlist[-1] = [item - offset for item in configlist[-1]]
- 195
- 196            if r_start[rep] is None:
- 197                r_start_index.append(0)
- 198            else:
- 199                try:
- 200                    r_start_index.append(configlist[-1].index(r_start[rep]))
- 201                except ValueError:
- 202                    raise Exception('Config %d not in file with range [%d, %d]' % (
- 203                        r_start[rep], configlist[-1][0], configlist[-1][-1])) from None
- 204
- 205            if r_stop[rep] is None:
- 206                r_stop_index.append(len(configlist[-1]) - 1)
- 207            else:
- 208                try:
- 209                    r_stop_index.append(configlist[-1].index(r_stop[rep]))
- 210                except ValueError:
- 211                    raise Exception('Config %d not in file with range [%d, %d]' % (
- 212                        r_stop[rep], configlist[-1][0], configlist[-1][-1])) from None
- 213
- 214            for k in range(nrw):
- 215                deltas[k].append(tmp_array[k][r_start_index[rep]:r_stop_index[rep] + 1][::r_step])
+ 179                            t = fp.read(8 * nsrc[i])
+ 180                            tmp_rw = struct.unpack('d' * nsrc[i], t)
+ 181                            tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw)))
+ 182                            if print_err:
+ 183                                print(config_no, i, j,
+ 184                                      np.mean(np.exp(-np.asarray(tmp_rw))),
+ 185                                      np.std(np.exp(-np.asarray(tmp_rw))))
+ 186                                print('Sources:', np.exp(-np.asarray(tmp_rw)))
+ 187                                print('Partial factor:', tmp_nfct)
+ 188                    tmp_array[i].append(tmp_nfct)
+ 189
+ 190            diffmeas = configlist[-1][-1] - configlist[-1][-2]
+ 191            configlist[-1] = [item // diffmeas for item in configlist[-1]]
+ 192            if configlist[-1][0] > 1 and diffmeas > 1:
+ 193                warnings.warn('Assume thermalization and that the first measurement belongs to the first config.', stacklevel=2)
+ 194                offset = configlist[-1][0] - 1
+ 195                configlist[-1] = [item - offset for item in configlist[-1]]
+ 196
+ 197            if r_start[rep] is None:
+ 198                r_start_index.append(0)
+ 199            else:
+ 200                try:
+ 201                    r_start_index.append(configlist[-1].index(r_start[rep]))
+ 202                except ValueError:
+ 203                    raise Exception(
+ 204                        f'Config {r_start[rep]} not in file with range [{configlist[-1][0]}, {configlist[-1][-1]}]'
+ 205                    ) from None
+ 206
+ 207            if r_stop[rep] is None:
+ 208                r_stop_index.append(len(configlist[-1]) - 1)
+ 209            else:
+ 210                try:
+ 211                    r_stop_index.append(configlist[-1].index(r_stop[rep]))
+ 212                except ValueError:
+ 213                    raise Exception(
+ 214                        f'Config {r_stop[rep]} not in file with range [{configlist[-1][0]}, {configlist[-1][-1]}]'
+ 215                    ) from None
  216
- 217    if np.any([len(np.unique(np.diff(cl))) != 1 for cl in configlist]):
- 218        raise Exception('Irregular spaced data in input file!', [len(np.unique(np.diff(cl))) for cl in configlist])
- 219    stepsizes = [list(np.unique(np.diff(cl)))[0] for cl in configlist]
- 220    if np.any([step != 1 for step in stepsizes]):
- 221        warnings.warn('Stepsize between configurations is greater than one!' + str(stepsizes), RuntimeWarning)
- 222
- 223    print(',', nrw, 'reweighting factors with', nsrc, 'sources')
- 224    result = []
- 225    idl = [range(configlist[rep][r_start_index[rep]], configlist[rep][r_stop_index[rep]] + 1, r_step) for rep in range(replica)]
- 226
- 227    for t in range(nrw):
- 228        result.append(Obs(deltas[t], rep_names, idl=idl))
- 229    return result
- 230
- 231
- 232def _extract_flowed_energy_density(path, prefix, dtr_read, xmin, spatial_extent, postfix='ms', **kwargs):
- 233    """Extract a dictionary with the flowed Yang-Mills action density from given .ms.dat files.
- 234    Returns a dictionary with Obs as values and flow times as keys.
- 235
- 236    It is assumed that all boundary effects have
- 237    sufficiently decayed at x0=xmin.
+ 217            for k in range(nrw):
+ 218                deltas[k].append(tmp_array[k][r_start_index[rep]:r_stop_index[rep] + 1][::r_step])
+ 219
+ 220    if np.any([len(np.unique(np.diff(cl))) != 1 for cl in configlist]):
+ 221        raise Exception('Irregular spaced data in input file!', [len(np.unique(np.diff(cl))) for cl in configlist])
+ 222    stepsizes = [next(iter(np.unique(np.diff(cl)))) for cl in configlist]
+ 223    if np.any([step != 1 for step in stepsizes]):
+ 224        warnings.warn('Stepsize between configurations is greater than one!' + str(stepsizes), RuntimeWarning, stacklevel=2)
+ 225
+ 226    print(',', nrw, 'reweighting factors with', nsrc, 'sources')
+ 227    result = []
+ 228    idl = [range(configlist[rep][r_start_index[rep]], configlist[rep][r_stop_index[rep]] + 1, r_step) for rep in range(replica)]
+ 229
+ 230    for t in range(nrw):
+ 231        result.append(Obs(deltas[t], rep_names, idl=idl))
+ 232    return result
+ 233
+ 234
+ 235def _extract_flowed_energy_density(path, prefix, dtr_read, xmin, spatial_extent, postfix='ms', **kwargs):
+ 236    """Extract a dictionary with the flowed Yang-Mills action density from given .ms.dat files.
+ 237    Returns a dictionary with Obs as values and flow times as keys.
  238
- 239    It is assumed that one measurement is performed for each config.
- 240    If this is not the case, the resulting idl, as well as the handling
- 241    of `r_start`, `r_stop` and `r_step` is wrong and the user has to correct
- 242    this in the resulting observable.
- 243    The function also assumes that `r_step` is the same across all replica.
- 244
- 245    Parameters
- 246    ----------
- 247    path : str
- 248        Path to .ms.dat files
- 249    prefix : str
- 250        Ensemble prefix
- 251    dtr_read : int
- 252        Determines how many trajectories should be skipped
- 253        when reading the ms.dat files.
- 254        Corresponds to dtr_cnfg (dncnfg) in the openQCD input file.
- 255    xmin : int
- 256        First timeslice where the boundary
- 257        effects have sufficiently decayed.
- 258    spatial_extent : int
- 259        spatial extent of the lattice, required for normalization.
- 260    postfix : str
- 261        Postfix of measurement file (Default: ms)
- 262    r_start : list
- 263        list which contains the first config to be read for each replicum.
- 264    r_stop : list
- 265        list which contains the last config to be read for each replicum.
- 266    r_step : int
- 267        integer that defines a fixed step size between two measurements (in units of configs)
- 268        If not given, r_step=1 is assumed.
- 269    plaquette : bool
- 270        If true extract the plaquette estimate of t0 instead.
- 271    names : list
- 272        list of names that is assigned to the data according according
- 273        to the order in the file list. Use careful, if you do not provide file names!
- 274    files : list
- 275        list which contains the filenames to be read. No automatic detection of
- 276        files performed if given.
- 277    assume_thermalization : bool
- 278        If True: If the first record divided by the distance between two measurements is larger than
- 279        1, it is assumed that this is due to thermalization and the first measurement belongs
- 280        to the first config (default).
- 281        If False: The config numbers are assumed to be traj_number // difference
- 282
- 283    Returns
- 284    -------
- 285    E_dict : dictionary
- 286        Dictionary with the flowed action density at flow times t
- 287    """
- 288
- 289    if 'files' in kwargs:
- 290        known_files = kwargs.get('files')
- 291    else:
- 292        known_files = []
- 293
- 294    ls = _find_files(path, prefix, postfix, 'dat', known_files=known_files)
- 295
- 296    replica = len(ls)
- 297
- 298    if 'r_start' in kwargs:
- 299        r_start = kwargs.get('r_start')
- 300        if len(r_start) != replica:
- 301            raise Exception('r_start does not match number of replicas')
- 302        r_start = [o if o else None for o in r_start]
- 303    else:
- 304        r_start = [None] * replica
- 305
- 306    if 'r_stop' in kwargs:
- 307        r_stop = kwargs.get('r_stop')
- 308        if len(r_stop) != replica:
- 309            raise Exception('r_stop does not match number of replicas')
- 310    else:
- 311        r_stop = [None] * replica
- 312
- 313    if 'r_step' in kwargs:
- 314        r_step = kwargs.get('r_step')
- 315    else:
- 316        r_step = 1
- 317
- 318    print('Extract flowed Yang-Mills action density from', prefix, ',', replica, 'replica')
- 319
- 320    if 'names' in kwargs:
- 321        rep_names = kwargs.get('names')
- 322    else:
- 323        rep_names = []
- 324        for entry in ls:
- 325            truncated_entry = entry.split('.')[0]
- 326            idx = truncated_entry.index('r')
- 327            rep_names.append(truncated_entry[:idx] + '|' + truncated_entry[idx:])
- 328
- 329    Ysum = []
- 330
- 331    configlist = []
- 332    r_start_index = []
- 333    r_stop_index = []
- 334
- 335    for rep in range(replica):
- 336
- 337        with open(path + '/' + ls[rep], 'rb') as fp:
- 338            t = fp.read(12)
- 339            header = struct.unpack('iii', t)
- 340            if rep == 0:
- 341                dn = header[0]
- 342                nn = header[1]
- 343                tmax = header[2]
- 344            elif dn != header[0] or nn != header[1] or tmax != header[2]:
- 345                raise Exception('Replica parameters do not match.')
- 346
- 347            t = fp.read(8)
- 348            if rep == 0:
- 349                eps = struct.unpack('d', t)[0]
- 350                print('Step size:', eps, ', Maximal t value:', dn * (nn) * eps)
- 351            elif eps != struct.unpack('d', t)[0]:
- 352                raise Exception('Values for eps do not match among replica.')
- 353
- 354            Ysl = []
- 355
- 356            configlist.append([])
- 357            while True:
- 358                t = fp.read(4)
- 359                if (len(t) < 4):
- 360                    break
- 361                nc = struct.unpack('i', t)[0]
- 362                if nc % dtr_read == 0:
- 363                    configlist[-1].append(nc)
- 364                t = fp.read(8 * tmax * (nn + 1))
- 365                if kwargs.get('plaquette'):
- 366                    if nc % dtr_read == 0:
- 367                        Ysl.append(struct.unpack('d' * tmax * (nn + 1), t))
- 368                t = fp.read(8 * tmax * (nn + 1))
- 369                if not kwargs.get('plaquette'):
- 370                    if nc % dtr_read == 0:
- 371                        Ysl.append(struct.unpack('d' * tmax * (nn + 1), t))
- 372                t = fp.read(8 * tmax * (nn + 1))
- 373
- 374        Ysum.append([])
- 375        for i, item in enumerate(Ysl):
- 376            Ysum[-1].append([np.mean(item[current + xmin:
- 377                             current + tmax - xmin])
- 378                            for current in range(0, len(item), tmax)])
- 379
- 380        diffmeas = configlist[-1][-1] - configlist[-1][-2]
- 381        if not all(c % diffmeas == 0 for c in configlist[-1]):
- 382            raise ValueError(f"Irregular spacing of configurations in {ls[rep]}, determined stepsize does not divide all trajectory steps.")
- 383        configlist[-1] = [item // diffmeas for item in configlist[-1]]
- 384        if kwargs.get('assume_thermalization', True) and configlist[-1][0] > 1:
- 385            warnings.warn('Assume thermalization and that the first measurement belongs to the first config.')
- 386            offset = configlist[-1][0] - 1
- 387            configlist[-1] = [item - offset for item in configlist[-1]]
- 388
- 389        if r_start[rep] is None:
- 390            r_start_index.append(0)
- 391        else:
- 392            try:
- 393                r_start_index.append(configlist[-1].index(r_start[rep]))
- 394            except ValueError:
- 395                raise Exception('Config %d not in file with range [%d, %d]' % (
- 396                    r_start[rep], configlist[-1][0], configlist[-1][-1])) from None
- 397
- 398        if r_stop[rep] is None:
- 399            r_stop_index.append(len(configlist[-1]) - 1)
- 400        else:
- 401            try:
- 402                r_stop_index.append(configlist[-1].index(r_stop[rep]))
- 403            except ValueError:
- 404                raise Exception('Config %d not in file with range [%d, %d]' % (
- 405                    r_stop[rep], configlist[-1][0], configlist[-1][-1])) from None
- 406
- 407    if np.any([len(np.unique(np.diff(cl))) != 1 for cl in configlist]):
- 408        raise Exception('Irregular spaced data in input file!', [len(np.unique(np.diff(cl))) for cl in configlist])
- 409    stepsizes = [list(np.unique(np.diff(cl)))[0] for cl in configlist]
- 410    if np.any([step != 1 for step in stepsizes]):
- 411        warnings.warn('Stepsize between configurations is greater than one!' + str(stepsizes), RuntimeWarning)
- 412
- 413    idl = [range(configlist[rep][r_start_index[rep]], configlist[rep][r_stop_index[rep]] + 1, r_step) for rep in range(replica)]
- 414    E_dict = {}
- 415    for n in range(nn + 1):
- 416        samples = []
- 417        for nrep, rep in enumerate(Ysum):
- 418            samples.append([])
- 419            for cnfg in rep:
- 420                samples[-1].append(cnfg[n])
- 421            samples[-1] = samples[-1][r_start_index[nrep]:r_stop_index[nrep] + 1][::r_step]
- 422        new_obs = Obs(samples, rep_names, idl=idl)
- 423        E_dict[n * dn * eps] = new_obs / (spatial_extent ** 3)
- 424
- 425    return E_dict
- 426
- 427
- 428def extract_t0(path, prefix, dtr_read, xmin, spatial_extent, fit_range=5, postfix='ms', c=0.3, **kwargs):
- 429    """Extract t0/a^2 from given .ms.dat files. Returns t0 as Obs.
- 430
- 431    It is assumed that all boundary effects have
- 432    sufficiently decayed at x0=xmin.
- 433    The data around the zero crossing of t^2<E> - c (where c=0.3 by default)
- 434    is fitted with a linear function
- 435    from which the exact root is extracted.
- 436
- 437    It is assumed that one measurement is performed for each config.
- 438    If this is not the case, the resulting idl, as well as the handling
- 439    of `r_start`, `r_stop` and `r_step` is wrong and the user has to correct
- 440    this in the resulting observable.
- 441    The function also assumes that `r_step` is the same across all replica.
- 442
- 443    Parameters
- 444    ----------
- 445    path : str
- 446        Path to .ms.dat files
- 447    prefix : str
- 448        Ensemble prefix
- 449    dtr_read : int
- 450        Determines how many trajectories should be skipped
- 451        when reading the ms.dat files.
- 452        Corresponds to dtr_cnfg / dtr_ms in the openQCD input file.
- 453    xmin : int
- 454        First timeslice where the boundary
- 455        effects have sufficiently decayed.
- 456    spatial_extent : int
- 457        spatial extent of the lattice, required for normalization.
- 458    fit_range : int
- 459        Number of data points left and right of the zero
- 460        crossing to be included in the linear fit. (Default: 5)
- 461    postfix : str
- 462        Postfix of measurement file (Default: ms)
- 463    c: float
- 464        Constant that defines the flow scale. Default 0.3 for t_0, choose 2./3 for t_1.
- 465    r_start : list
- 466        list which contains the first config to be read for each replicum.
- 467    r_stop : list
- 468        list which contains the last config to be read for each replicum.
- 469    r_step : int
- 470        integer that defines a fixed step size between two measurements (in units of configs)
- 471        If not given, r_step=1 is assumed.
- 472    plaquette : bool
- 473        If true extract the plaquette estimate of t0 instead.
- 474    names : list
- 475        list of names that is assigned to the data according according
- 476        to the order in the file list. Use careful, if you do not provide file names!
- 477    files : list
- 478        list which contains the filenames to be read. No automatic detection of
- 479        files performed if given.
- 480    plot_fit : bool
- 481        If true, the fit for the extraction of t0 is shown together with the data.
- 482    assume_thermalization : bool
- 483        If True: If the first record divided by the distance between two measurements is larger than
- 484        1, it is assumed that this is due to thermalization and the first measurement belongs
- 485        to the first config (default).
- 486        If False: The config numbers are assumed to be traj_number // difference
- 487
- 488    Returns
- 489    -------
- 490    t0 : Obs
- 491        Extracted t0
- 492    """
- 493
- 494    E_dict = _extract_flowed_energy_density(path, prefix, dtr_read, xmin, spatial_extent, postfix, **kwargs)
- 495    t2E_dict = {}
- 496    for t in sorted(E_dict.keys()):
- 497        t2E_dict[t] = t ** 2 * E_dict[t] - c
+ 239    It is assumed that all boundary effects have
+ 240    sufficiently decayed at x0=xmin.
+ 241
+ 242    It is assumed that one measurement is performed for each config.
+ 243    If this is not the case, the resulting idl, as well as the handling
+ 244    of `r_start`, `r_stop` and `r_step` is wrong and the user has to correct
+ 245    this in the resulting observable.
+ 246    The function also assumes that `r_step` is the same across all replica.
+ 247
+ 248    Parameters
+ 249    ----------
+ 250    path : str
+ 251        Path to .ms.dat files
+ 252    prefix : str
+ 253        Ensemble prefix
+ 254    dtr_read : int
+ 255        Determines how many trajectories should be skipped
+ 256        when reading the ms.dat files.
+ 257        Corresponds to dtr_cnfg (dncnfg) in the openQCD input file.
+ 258    xmin : int
+ 259        First timeslice where the boundary
+ 260        effects have sufficiently decayed.
+ 261    spatial_extent : int
+ 262        spatial extent of the lattice, required for normalization.
+ 263    postfix : str
+ 264        Postfix of measurement file (Default: ms)
+ 265    r_start : list
+ 266        list which contains the first config to be read for each replicum.
+ 267    r_stop : list
+ 268        list which contains the last config to be read for each replicum.
+ 269    r_step : int
+ 270        integer that defines a fixed step size between two measurements (in units of configs)
+ 271        If not given, r_step=1 is assumed.
+ 272    plaquette : bool
+ 273        If true extract the plaquette estimate of t0 instead.
+ 274    names : list
+ 275        list of names that is assigned to the data according according
+ 276        to the order in the file list. Use careful, if you do not provide file names!
+ 277    files : list
+ 278        list which contains the filenames to be read. No automatic detection of
+ 279        files performed if given.
+ 280    assume_thermalization : bool
+ 281        If True: If the first record divided by the distance between two measurements is larger than
+ 282        1, it is assumed that this is due to thermalization and the first measurement belongs
+ 283        to the first config (default).
+ 284        If False: The config numbers are assumed to be traj_number // difference
+ 285
+ 286    Returns
+ 287    -------
+ 288    E_dict : dictionary
+ 289        Dictionary with the flowed action density at flow times t
+ 290    """
+ 291
+ 292    if 'files' in kwargs:
+ 293        known_files = kwargs.get('files')
+ 294    else:
+ 295        known_files = []
+ 296
+ 297    ls = _find_files(path, prefix, postfix, 'dat', known_files=known_files)
+ 298
+ 299    replica = len(ls)
+ 300
+ 301    if 'r_start' in kwargs:
+ 302        r_start = kwargs.get('r_start')
+ 303        if len(r_start) != replica:
+ 304            raise Exception('r_start does not match number of replicas')
+ 305        r_start = [o if o else None for o in r_start]
+ 306    else:
+ 307        r_start = [None] * replica
+ 308
+ 309    if 'r_stop' in kwargs:
+ 310        r_stop = kwargs.get('r_stop')
+ 311        if len(r_stop) != replica:
+ 312            raise Exception('r_stop does not match number of replicas')
+ 313    else:
+ 314        r_stop = [None] * replica
+ 315
+ 316    if 'r_step' in kwargs:
+ 317        r_step = kwargs.get('r_step')
+ 318    else:
+ 319        r_step = 1
+ 320
+ 321    print('Extract flowed Yang-Mills action density from', prefix, ',', replica, 'replica')
+ 322
+ 323    if 'names' in kwargs:
+ 324        rep_names = kwargs.get('names')
+ 325    else:
+ 326        rep_names = []
+ 327        for entry in ls:
+ 328            truncated_entry = entry.split('.')[0]
+ 329            idx = truncated_entry.index('r')
+ 330            rep_names.append(truncated_entry[:idx] + '|' + truncated_entry[idx:])
+ 331
+ 332    Ysum = []
+ 333
+ 334    configlist = []
+ 335    r_start_index = []
+ 336    r_stop_index = []
+ 337
+ 338    for rep in range(replica):
+ 339
+ 340        with open(path + '/' + ls[rep], 'rb') as fp:
+ 341            t = fp.read(12)
+ 342            header = struct.unpack('iii', t)
+ 343            if rep == 0:
+ 344                dn = header[0]
+ 345                nn = header[1]
+ 346                tmax = header[2]
+ 347            elif dn != header[0] or nn != header[1] or tmax != header[2]:
+ 348                raise Exception('Replica parameters do not match.')
+ 349
+ 350            t = fp.read(8)
+ 351            if rep == 0:
+ 352                eps = struct.unpack('d', t)[0]
+ 353                print('Step size:', eps, ', Maximal t value:', dn * (nn) * eps)
+ 354            elif eps != struct.unpack('d', t)[0]:
+ 355                raise Exception('Values for eps do not match among replica.')
+ 356
+ 357            Ysl = []
+ 358
+ 359            configlist.append([])
+ 360            while True:
+ 361                t = fp.read(4)
+ 362                if (len(t) < 4):
+ 363                    break
+ 364                nc = struct.unpack('i', t)[0]
+ 365                if nc % dtr_read == 0:
+ 366                    configlist[-1].append(nc)
+ 367                t = fp.read(8 * tmax * (nn + 1))
+ 368                if kwargs.get('plaquette'):
+ 369                    if nc % dtr_read == 0:
+ 370                        Ysl.append(struct.unpack('d' * tmax * (nn + 1), t))
+ 371                t = fp.read(8 * tmax * (nn + 1))
+ 372                if not kwargs.get('plaquette'):
+ 373                    if nc % dtr_read == 0:
+ 374                        Ysl.append(struct.unpack('d' * tmax * (nn + 1), t))
+ 375                t = fp.read(8 * tmax * (nn + 1))
+ 376
+ 377        Ysum.append([])
+ 378        for _i, item in enumerate(Ysl):
+ 379            Ysum[-1].append([np.mean(item[current + xmin:
+ 380                             current + tmax - xmin])
+ 381                            for current in range(0, len(item), tmax)])
+ 382
+ 383        diffmeas = configlist[-1][-1] - configlist[-1][-2]
+ 384        if not all(c % diffmeas == 0 for c in configlist[-1]):
+ 385            raise ValueError(f"Irregular spacing of configurations in {ls[rep]}, determined stepsize does not divide all trajectory steps.")
+ 386        configlist[-1] = [item // diffmeas for item in configlist[-1]]
+ 387        if kwargs.get('assume_thermalization', True) and configlist[-1][0] > 1:
+ 388            warnings.warn('Assume thermalization and that the first measurement belongs to the first config.', stacklevel=2)
+ 389            offset = configlist[-1][0] - 1
+ 390            configlist[-1] = [item - offset for item in configlist[-1]]
+ 391
+ 392        if r_start[rep] is None:
+ 393            r_start_index.append(0)
+ 394        else:
+ 395            try:
+ 396                r_start_index.append(configlist[-1].index(r_start[rep]))
+ 397            except ValueError:
+ 398                raise Exception(
+ 399                    f'Config {r_start[rep]} not in file with range [{configlist[-1][0]}, {configlist[-1][-1]}]'
+ 400                ) from None
+ 401
+ 402        if r_stop[rep] is None:
+ 403            r_stop_index.append(len(configlist[-1]) - 1)
+ 404        else:
+ 405            try:
+ 406                r_stop_index.append(configlist[-1].index(r_stop[rep]))
+ 407            except ValueError:
+ 408                raise Exception(
+ 409                    f'Config {r_stop[rep]} not in file with range [{configlist[-1][0]}, {configlist[-1][-1]}]'
+ 410                ) from None
+ 411
+ 412    if np.any([len(np.unique(np.diff(cl))) != 1 for cl in configlist]):
+ 413        raise Exception('Irregular spaced data in input file!', [len(np.unique(np.diff(cl))) for cl in configlist])
+ 414    stepsizes = [next(iter(np.unique(np.diff(cl)))) for cl in configlist]
+ 415    if np.any([step != 1 for step in stepsizes]):
+ 416        warnings.warn('Stepsize between configurations is greater than one!' + str(stepsizes), RuntimeWarning, stacklevel=2)
+ 417
+ 418    idl = [range(configlist[rep][r_start_index[rep]], configlist[rep][r_stop_index[rep]] + 1, r_step) for rep in range(replica)]
+ 419    E_dict = {}
+ 420    for n in range(nn + 1):
+ 421        samples = []
+ 422        for nrep, rep in enumerate(Ysum):
+ 423            samples.append([])
+ 424            for cnfg in rep:
+ 425                samples[-1].append(cnfg[n])
+ 426            samples[-1] = samples[-1][r_start_index[nrep]:r_stop_index[nrep] + 1][::r_step]
+ 427        new_obs = Obs(samples, rep_names, idl=idl)
+ 428        E_dict[n * dn * eps] = new_obs / (spatial_extent ** 3)
+ 429
+ 430    return E_dict
+ 431
+ 432
+ 433def extract_t0(path, prefix, dtr_read, xmin, spatial_extent, fit_range=5, postfix='ms', c=0.3, **kwargs):
+ 434    """Extract t0/a^2 from given .ms.dat files. Returns t0 as Obs.
+ 435
+ 436    It is assumed that all boundary effects have
+ 437    sufficiently decayed at x0=xmin.
+ 438    The data around the zero crossing of t^2<E> - c (where c=0.3 by default)
+ 439    is fitted with a linear function
+ 440    from which the exact root is extracted.
+ 441
+ 442    It is assumed that one measurement is performed for each config.
+ 443    If this is not the case, the resulting idl, as well as the handling
+ 444    of `r_start`, `r_stop` and `r_step` is wrong and the user has to correct
+ 445    this in the resulting observable.
+ 446    The function also assumes that `r_step` is the same across all replica.
+ 447
+ 448    Parameters
+ 449    ----------
+ 450    path : str
+ 451        Path to .ms.dat files
+ 452    prefix : str
+ 453        Ensemble prefix
+ 454    dtr_read : int
+ 455        Determines how many trajectories should be skipped
+ 456        when reading the ms.dat files.
+ 457        Corresponds to dtr_cnfg / dtr_ms in the openQCD input file.
+ 458    xmin : int
+ 459        First timeslice where the boundary
+ 460        effects have sufficiently decayed.
+ 461    spatial_extent : int
+ 462        spatial extent of the lattice, required for normalization.
+ 463    fit_range : int
+ 464        Number of data points left and right of the zero
+ 465        crossing to be included in the linear fit. (Default: 5)
+ 466    postfix : str
+ 467        Postfix of measurement file (Default: ms)
+ 468    c: float
+ 469        Constant that defines the flow scale. Default 0.3 for t_0, choose 2./3 for t_1.
+ 470    r_start : list
+ 471        list which contains the first config to be read for each replicum.
+ 472    r_stop : list
+ 473        list which contains the last config to be read for each replicum.
+ 474    r_step : int
+ 475        integer that defines a fixed step size between two measurements (in units of configs)
+ 476        If not given, r_step=1 is assumed.
+ 477    plaquette : bool
+ 478        If true extract the plaquette estimate of t0 instead.
+ 479    names : list
+ 480        list of names that is assigned to the data according according
+ 481        to the order in the file list. Use careful, if you do not provide file names!
+ 482    files : list
+ 483        list which contains the filenames to be read. No automatic detection of
+ 484        files performed if given.
+ 485    plot_fit : bool
+ 486        If true, the fit for the extraction of t0 is shown together with the data.
+ 487    assume_thermalization : bool
+ 488        If True: If the first record divided by the distance between two measurements is larger than
+ 489        1, it is assumed that this is due to thermalization and the first measurement belongs
+ 490        to the first config (default).
+ 491        If False: The config numbers are assumed to be traj_number // difference
+ 492
+ 493    Returns
+ 494    -------
+ 495    t0 : Obs
+ 496        Extracted t0
+ 497    """
  498
- 499    return fit_t0(t2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'))
- 500
- 501
- 502def extract_w0(path, prefix, dtr_read, xmin, spatial_extent, fit_range=5, postfix='ms', c=0.3, **kwargs):
- 503    """Extract w0/a from given .ms.dat files. Returns w0 as Obs.
- 504
- 505    It is assumed that all boundary effects have
- 506    sufficiently decayed at x0=xmin.
- 507    The data around the zero crossing of t d(t^2<E>)/dt -  (where c=0.3 by default)
- 508    is fitted with a linear function
- 509    from which the exact root is extracted.
- 510
- 511    It is assumed that one measurement is performed for each config.
- 512    If this is not the case, the resulting idl, as well as the handling
- 513    of r_start, r_stop and r_step is wrong and the user has to correct
- 514    this in the resulting observable.
+ 499    E_dict = _extract_flowed_energy_density(path, prefix, dtr_read, xmin, spatial_extent, postfix, **kwargs)
+ 500    t2E_dict = {}
+ 501    for t in sorted(E_dict.keys()):
+ 502        t2E_dict[t] = t ** 2 * E_dict[t] - c
+ 503
+ 504    return fit_t0(t2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'))
+ 505
+ 506
+ 507def extract_w0(path, prefix, dtr_read, xmin, spatial_extent, fit_range=5, postfix='ms', c=0.3, **kwargs):
+ 508    """Extract w0/a from given .ms.dat files. Returns w0 as Obs.
+ 509
+ 510    It is assumed that all boundary effects have
+ 511    sufficiently decayed at x0=xmin.
+ 512    The data around the zero crossing of t d(t^2<E>)/dt -  (where c=0.3 by default)
+ 513    is fitted with a linear function
+ 514    from which the exact root is extracted.
  515
- 516    Parameters
- 517    ----------
- 518    path : str
- 519        Path to .ms.dat files
- 520    prefix : str
- 521        Ensemble prefix
- 522    dtr_read : int
- 523        Determines how many trajectories should be skipped
- 524        when reading the ms.dat files.
- 525        Corresponds to dtr_cnfg / dtr_ms in the openQCD input file.
- 526    xmin : int
- 527        First timeslice where the boundary
- 528        effects have sufficiently decayed.
- 529    spatial_extent : int
- 530        spatial extent of the lattice, required for normalization.
- 531    fit_range : int
- 532        Number of data points left and right of the zero
- 533        crossing to be included in the linear fit. (Default: 5)
- 534    postfix : str
- 535        Postfix of measurement file (Default: ms)
- 536    c: float
- 537        Constant that defines the flow scale. Default 0.3 for w_0, choose 2./3 for w_1.
- 538    r_start : list
- 539        list which contains the first config to be read for each replicum.
- 540    r_stop : list
- 541        list which contains the last config to be read for each replicum.
- 542    r_step : int
- 543        integer that defines a fixed step size between two measurements (in units of configs)
- 544        If not given, r_step=1 is assumed.
- 545    plaquette : bool
- 546        If true extract the plaquette estimate of w0 instead.
- 547    names : list
- 548        list of names that is assigned to the data according according
- 549        to the order in the file list. Use careful, if you do not provide file names!
- 550    files : list
- 551        list which contains the filenames to be read. No automatic detection of
- 552        files performed if given.
- 553    plot_fit : bool
- 554        If true, the fit for the extraction of w0 is shown together with the data.
- 555    assume_thermalization : bool
- 556        If True: If the first record divided by the distance between two measurements is larger than
- 557        1, it is assumed that this is due to thermalization and the first measurement belongs
- 558        to the first config (default).
- 559        If False: The config numbers are assumed to be traj_number // difference
- 560
- 561    Returns
- 562    -------
- 563    w0 : Obs
- 564        Extracted w0
- 565    """
- 566
- 567    E_dict = _extract_flowed_energy_density(path, prefix, dtr_read, xmin, spatial_extent, postfix, **kwargs)
- 568
- 569    ftimes = sorted(E_dict.keys())
- 570
- 571    t2E_dict = {}
- 572    for t in ftimes:
- 573        t2E_dict[t] = t ** 2 * E_dict[t]
- 574
- 575    tdtt2E_dict = {}
- 576    tdtt2E_dict[ftimes[0]] = ftimes[0] * (t2E_dict[ftimes[1]] - t2E_dict[ftimes[0]]) / (ftimes[1] - ftimes[0]) - c
- 577    for i in range(1, len(ftimes) - 1):
- 578        tdtt2E_dict[ftimes[i]] = ftimes[i] * (t2E_dict[ftimes[i + 1]] - t2E_dict[ftimes[i - 1]]) / (ftimes[i + 1] - ftimes[i - 1]) - c
- 579    tdtt2E_dict[ftimes[-1]] = ftimes[-1] * (t2E_dict[ftimes[-1]] - t2E_dict[ftimes[-2]]) / (ftimes[-1] - ftimes[-2]) - c
- 580
- 581    return np.sqrt(fit_t0(tdtt2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'), observable='w0'))
- 582
- 583
- 584def _parse_array_openQCD2(d, n, size, wa, quadrupel=False):
- 585    arr = []
- 586    if d == 2:
- 587        for i in range(n[0]):
- 588            tmp = wa[i * n[1]:(i + 1) * n[1]]
- 589            if quadrupel:
- 590                tmp2 = []
- 591                for j in range(0, len(tmp), 2):
- 592                    tmp2.append(tmp[j])
- 593                arr.append(tmp2)
- 594            else:
- 595                arr.append(np.asarray(tmp))
- 596
- 597    else:
- 598        raise Exception('Only two-dimensional arrays supported!')
- 599
- 600    return arr
+ 516    It is assumed that one measurement is performed for each config.
+ 517    If this is not the case, the resulting idl, as well as the handling
+ 518    of r_start, r_stop and r_step is wrong and the user has to correct
+ 519    this in the resulting observable.
+ 520
+ 521    Parameters
+ 522    ----------
+ 523    path : str
+ 524        Path to .ms.dat files
+ 525    prefix : str
+ 526        Ensemble prefix
+ 527    dtr_read : int
+ 528        Determines how many trajectories should be skipped
+ 529        when reading the ms.dat files.
+ 530        Corresponds to dtr_cnfg / dtr_ms in the openQCD input file.
+ 531    xmin : int
+ 532        First timeslice where the boundary
+ 533        effects have sufficiently decayed.
+ 534    spatial_extent : int
+ 535        spatial extent of the lattice, required for normalization.
+ 536    fit_range : int
+ 537        Number of data points left and right of the zero
+ 538        crossing to be included in the linear fit. (Default: 5)
+ 539    postfix : str
+ 540        Postfix of measurement file (Default: ms)
+ 541    c: float
+ 542        Constant that defines the flow scale. Default 0.3 for w_0, choose 2./3 for w_1.
+ 543    r_start : list
+ 544        list which contains the first config to be read for each replicum.
+ 545    r_stop : list
+ 546        list which contains the last config to be read for each replicum.
+ 547    r_step : int
+ 548        integer that defines a fixed step size between two measurements (in units of configs)
+ 549        If not given, r_step=1 is assumed.
+ 550    plaquette : bool
+ 551        If true extract the plaquette estimate of w0 instead.
+ 552    names : list
+ 553        list of names that is assigned to the data according according
+ 554        to the order in the file list. Use careful, if you do not provide file names!
+ 555    files : list
+ 556        list which contains the filenames to be read. No automatic detection of
+ 557        files performed if given.
+ 558    plot_fit : bool
+ 559        If true, the fit for the extraction of w0 is shown together with the data.
+ 560    assume_thermalization : bool
+ 561        If True: If the first record divided by the distance between two measurements is larger than
+ 562        1, it is assumed that this is due to thermalization and the first measurement belongs
+ 563        to the first config (default).
+ 564        If False: The config numbers are assumed to be traj_number // difference
+ 565
+ 566    Returns
+ 567    -------
+ 568    w0 : Obs
+ 569        Extracted w0
+ 570    """
+ 571
+ 572    E_dict = _extract_flowed_energy_density(path, prefix, dtr_read, xmin, spatial_extent, postfix, **kwargs)
+ 573
+ 574    ftimes = sorted(E_dict.keys())
+ 575
+ 576    t2E_dict = {}
+ 577    for t in ftimes:
+ 578        t2E_dict[t] = t ** 2 * E_dict[t]
+ 579
+ 580    tdtt2E_dict = {}
+ 581    tdtt2E_dict[ftimes[0]] = ftimes[0] * (t2E_dict[ftimes[1]] - t2E_dict[ftimes[0]]) / (ftimes[1] - ftimes[0]) - c
+ 582    for i in range(1, len(ftimes) - 1):
+ 583        tdtt2E_dict[ftimes[i]] = ftimes[i] * (t2E_dict[ftimes[i + 1]] - t2E_dict[ftimes[i - 1]]) / (ftimes[i + 1] - ftimes[i - 1]) - c
+ 584    tdtt2E_dict[ftimes[-1]] = ftimes[-1] * (t2E_dict[ftimes[-1]] - t2E_dict[ftimes[-2]]) / (ftimes[-1] - ftimes[-2]) - c
+ 585
+ 586    return np.sqrt(fit_t0(tdtt2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'), observable='w0'))
+ 587
+ 588
+ 589def _parse_array_openQCD2(d, n, size, wa, quadrupel=False):
+ 590    arr = []
+ 591    if d == 2:
+ 592        for i in range(n[0]):
+ 593            tmp = wa[i * n[1]:(i + 1) * n[1]]
+ 594            if quadrupel:
+ 595                tmp2 = []
+ 596                for j in range(0, len(tmp), 2):
+ 597                    tmp2.append(tmp[j])
+ 598                arr.append(tmp2)
+ 599            else:
+ 600                arr.append(np.asarray(tmp))
  601
- 602
- 603def _find_files(path, prefix, postfix, ext, known_files=[]):
- 604    found = []
- 605    files = []
+ 602    else:
+ 603        raise Exception('Only two-dimensional arrays supported!')
+ 604
+ 605    return arr
  606
- 607    if postfix != "":
- 608        if postfix[-1] != ".":
- 609            postfix = postfix + "."
- 610        if postfix[0] != ".":
- 611            postfix = "." + postfix
- 612
- 613    if ext[0] == ".":
- 614        ext = ext[1:]
- 615
- 616    pattern = prefix + "*" + postfix + ext
- 617
- 618    for (dirpath, dirnames, filenames) in os.walk(path + "/"):
- 619        found.extend(filenames)
- 620        break
- 621
- 622    if known_files != []:
- 623        for kf in known_files:
- 624            if kf not in found:
- 625                raise FileNotFoundError("Given file " + kf + " does not exist!")
- 626
- 627        return known_files
+ 607
+ 608def _find_files(path, prefix, postfix, ext, known_files=None):
+ 609    if known_files is None:
+ 610        known_files = []
+ 611    found = []
+ 612    files = []
+ 613
+ 614    if postfix != "":
+ 615        if postfix[-1] != ".":
+ 616            postfix = postfix + "."
+ 617        if postfix[0] != ".":
+ 618            postfix = "." + postfix
+ 619
+ 620    if ext[0] == ".":
+ 621        ext = ext[1:]
+ 622
+ 623    pattern = prefix + "*" + postfix + ext
+ 624
+ 625    for (_dirpath, _dirnames, filenames) in os.walk(path + "/"):
+ 626        found.extend(filenames)
+ 627        break
  628
- 629    if not found:
- 630        raise FileNotFoundError(f"Error, directory '{path}' not found")
- 631
- 632    for f in found:
- 633        if fnmatch.fnmatch(f, pattern):
- 634            files.append(f)
+ 629    if known_files != []:
+ 630        for kf in known_files:
+ 631            if kf not in found:
+ 632                raise FileNotFoundError("Given file " + kf + " does not exist!")
+ 633
+ 634        return known_files
  635
- 636    if files == []:
- 637        raise Exception("No files found after pattern filter!")
+ 636    if not found:
+ 637        raise FileNotFoundError(f"Error, directory '{path}' not found")
  638
- 639    files = sort_names(files)
- 640    return files
- 641
+ 639    for f in found:
+ 640        if fnmatch.fnmatch(f, pattern):
+ 641            files.append(f)
  642
- 643def _read_array_openQCD2(fp):
- 644    t = fp.read(4)
- 645    d = struct.unpack('i', t)[0]
- 646    t = fp.read(4 * d)
- 647    n = struct.unpack('%di' % (d), t)
- 648    t = fp.read(4)
- 649    size = struct.unpack('i', t)[0]
- 650    if size == 4:
- 651        types = 'i'
- 652    elif size == 8:
- 653        types = 'd'
- 654    elif size == 16:
- 655        types = 'dd'
- 656    else:
- 657        raise Exception("Type for size '" + str(size) + "' not known.")
- 658    m = n[0]
- 659    for i in range(1, d):
- 660        m *= n[i]
- 661
- 662    t = fp.read(m * size)
- 663    tmp = struct.unpack('%d%s' % (m, types), t)
- 664
- 665    arr = _parse_array_openQCD2(d, n, size, tmp, quadrupel=True)
- 666    return {'d': d, 'n': n, 'size': size, 'arr': arr}
- 667
+ 643    if files == []:
+ 644        raise Exception("No files found after pattern filter!")
+ 645
+ 646    files = sort_names(files)
+ 647    return files
+ 648
+ 649
+ 650def _read_array_openQCD2(fp):
+ 651    t = fp.read(4)
+ 652    d = struct.unpack('i', t)[0]
+ 653    t = fp.read(4 * d)
+ 654    n = struct.unpack(f'{d}i', t)
+ 655    t = fp.read(4)
+ 656    size = struct.unpack('i', t)[0]
+ 657    if size == 4:
+ 658        types = 'i'
+ 659    elif size == 8:
+ 660        types = 'd'
+ 661    elif size == 16:
+ 662        types = 'dd'
+ 663    else:
+ 664        raise Exception("Type for size '" + str(size) + "' not known.")
+ 665    m = n[0]
+ 666    for i in range(1, d):
+ 667        m *= n[i]
  668
- 669def read_qtop(path, prefix, c, dtr_cnfg=1, version="openQCD", **kwargs):
- 670    """Read the topologial charge based on openQCD gradient flow measurements.
+ 669    t = fp.read(m * size)
+ 670    tmp = struct.unpack(f'{m}{types}', t)
  671
- 672    Parameters
- 673    ----------
- 674    path : str
- 675        path of the measurement files
- 676    prefix : str
- 677        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat.
- 678        Ignored if file names are passed explicitly via keyword files.
- 679    c : double
- 680        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L.
- 681    dtr_cnfg : int
- 682        (optional) parameter that specifies the number of measurements
- 683        between two configs.
- 684        If it is not set, the distance between two measurements
- 685        in the file is assumed to be the distance between two configurations.
- 686    steps : int
- 687        (optional) Distance between two configurations in units of trajectories /
- 688         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
- 689    version : str
- 690        Either openQCD or sfqcd, depending on the data.
- 691    L : int
- 692        spatial length of the lattice in L/a.
- 693        HAS to be set if version != sfqcd, since openQCD does not provide
- 694        this in the header
- 695    r_start : list
- 696        list which contains the first config to be read for each replicum.
- 697    r_stop : list
- 698        list which contains the last config to be read for each replicum.
- 699    files : list
- 700        specify the exact files that need to be read
- 701        from path, practical if e.g. only one replicum is needed
- 702    postfix : str
- 703        postfix of the file to read, e.g. '.gfms.dat' for openQCD-files
- 704    names : list
- 705        Alternative labeling for replicas/ensembles.
- 706        Has to have the appropriate length.
- 707    Zeuthen_flow : bool
- 708        (optional) If True, the Zeuthen flow is used for Qtop. Only possible
- 709        for version=='sfqcd' If False, the Wilson flow is used.
- 710    integer_charge : bool
- 711        If True, the charge is rounded towards the nearest integer on each config.
- 712
- 713    Returns
- 714    -------
- 715    result : Obs
- 716        Read topological charge
- 717    """
- 718
- 719    return _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version=version, obspos=0, **kwargs)
- 720
- 721
- 722def read_gf_coupling(path, prefix, c, dtr_cnfg=1, Zeuthen_flow=True, **kwargs):
- 723    """Read the gradient flow coupling based on sfqcd gradient flow measurements. See 1607.06423 for details.
- 724
- 725    Note: The current implementation only works for c=0.3 and T=L. The definition of the coupling in 1607.06423 requires projection to topological charge zero which is not done within this function but has to be performed in a separate step.
- 726
- 727    Parameters
- 728    ----------
- 729    path : str
- 730        path of the measurement files
- 731    prefix : str
- 732        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat.
- 733        Ignored if file names are passed explicitly via keyword files.
- 734    c : double
- 735        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L.
- 736    dtr_cnfg : int
- 737        (optional) parameter that specifies the number of measurements
- 738        between two configs.
- 739        If it is not set, the distance between two measurements
- 740        in the file is assumed to be the distance between two configurations.
- 741    steps : int
- 742        (optional) Distance between two configurations in units of trajectories /
- 743         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
- 744    r_start : list
- 745        list which contains the first config to be read for each replicum.
- 746    r_stop : list
- 747        list which contains the last config to be read for each replicum.
- 748    files : list
- 749        specify the exact files that need to be read
- 750        from path, practical if e.g. only one replicum is needed
- 751    names : list
- 752        Alternative labeling for replicas/ensembles.
- 753        Has to have the appropriate length.
- 754    postfix : str
- 755        postfix of the file to read, e.g. '.gfms.dat' for openQCD-files
- 756    Zeuthen_flow : bool
- 757        (optional) If True, the Zeuthen flow is used for the coupling. If False, the Wilson flow is used.
- 758    """
- 759
- 760    if c != 0.3:
- 761        raise Exception("The required lattice norm is only implemented for c=0.3 at the moment.")
- 762
- 763    plaq = _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version="sfqcd", obspos=6, sum_t=False, Zeuthen_flow=Zeuthen_flow, integer_charge=False, **kwargs)
- 764    C2x1 = _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version="sfqcd", obspos=7, sum_t=False, Zeuthen_flow=Zeuthen_flow, integer_charge=False, **kwargs)
- 765    L = plaq.tag["L"]
- 766    T = plaq.tag["T"]
- 767
- 768    if T != L:
- 769        raise Exception("The required lattice norm is only implemented for T=L at the moment.")
- 770
- 771    if Zeuthen_flow is not True:
- 772        raise Exception("The required lattice norm is only implemented for the Zeuthen flow at the moment.")
- 773
- 774    t = (c * L) ** 2 / 8
- 775
- 776    normdict = {4: 0.012341170468270,
- 777                6: 0.010162691462430,
- 778                8: 0.009031614807931,
- 779                10: 0.008744966371393,
- 780                12: 0.008650917856809,
- 781                14: 8.611154391267955E-03,
- 782                16: 0.008591758449508,
- 783                20: 0.008575359627103,
- 784                24: 0.008569387847540,
- 785                28: 8.566803713382559E-03,
- 786                32: 0.008565541650006,
- 787                40: 8.564480684962046E-03,
- 788                48: 8.564098025073460E-03,
- 789                64: 8.563853943383087E-03}
- 790
- 791    return t * t * (5 / 3 * plaq - 1 / 12 * C2x1) / normdict[L]
- 792
- 793
- 794def _read_flow_obs(path, prefix, c, dtr_cnfg=1, version="openQCD", obspos=0, sum_t=True, **kwargs):
- 795    """Read a flow observable based on openQCD gradient flow measurements.
- 796
- 797    Parameters
- 798    ----------
- 799    path : str
- 800        path of the measurement files
- 801    prefix : str
- 802        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat.
- 803        Ignored if file names are passed explicitly via keyword files.
- 804    c : double
- 805        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L.
- 806    dtr_cnfg : int
- 807        (optional) parameter that specifies the number of measurements
- 808        between two configs.
- 809        If it is not set, the distance between two measurements
- 810        in the file is assumed to be the distance between two configurations.
- 811    steps : int
- 812        (optional) Distance between two configurations in units of trajectories /
- 813         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
- 814    version : str
- 815        Either openQCD or sfqcd, depending on the data.
- 816    obspos : int
- 817        position of the obeservable in the measurement file. Only relevant for sfqcd files.
- 818    sum_t : bool
- 819        If true sum over all timeslices, if false only take the value at T/2.
- 820    L : int
- 821        spatial length of the lattice in L/a.
- 822        HAS to be set if version != sfqcd, since openQCD does not provide
- 823        this in the header
- 824    r_start : list
- 825        list which contains the first config to be read for each replicum.
- 826    r_stop : list
- 827        list which contains the last config to be read for each replicum.
- 828    files : list
- 829        specify the exact files that need to be read
- 830        from path, practical if e.g. only one replicum is needed
- 831    names : list
- 832        Alternative labeling for replicas/ensembles.
- 833        Has to have the appropriate length.
- 834    postfix : str
- 835        postfix of the file to read, e.g. '.gfms.dat' for openQCD-files
- 836    Zeuthen_flow : bool
- 837        (optional) If True, the Zeuthen flow is used for Qtop. Only possible
- 838        for version=='sfqcd' If False, the Wilson flow is used.
- 839    integer_charge : bool
- 840        If True, the charge is rounded towards the nearest integer on each config.
- 841
- 842    Returns
- 843    -------
- 844    result : Obs
- 845        flow observable specified
- 846    """
- 847    known_versions = ["openQCD", "sfqcd"]
+ 672    arr = _parse_array_openQCD2(d, n, size, tmp, quadrupel=True)
+ 673    return {'d': d, 'n': n, 'size': size, 'arr': arr}
+ 674
+ 675
+ 676def read_qtop(path, prefix, c, dtr_cnfg=1, version="openQCD", **kwargs):
+ 677    """Read the topologial charge based on openQCD gradient flow measurements.
+ 678
+ 679    Parameters
+ 680    ----------
+ 681    path : str
+ 682        path of the measurement files
+ 683    prefix : str
+ 684        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat.
+ 685        Ignored if file names are passed explicitly via keyword files.
+ 686    c : double
+ 687        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L.
+ 688    dtr_cnfg : int
+ 689        (optional) parameter that specifies the number of measurements
+ 690        between two configs.
+ 691        If it is not set, the distance between two measurements
+ 692        in the file is assumed to be the distance between two configurations.
+ 693    steps : int
+ 694        (optional) Distance between two configurations in units of trajectories /
+ 695         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
+ 696    version : str
+ 697        Either openQCD or sfqcd, depending on the data.
+ 698    L : int
+ 699        spatial length of the lattice in L/a.
+ 700        HAS to be set if version != sfqcd, since openQCD does not provide
+ 701        this in the header
+ 702    r_start : list
+ 703        list which contains the first config to be read for each replicum.
+ 704    r_stop : list
+ 705        list which contains the last config to be read for each replicum.
+ 706    files : list
+ 707        specify the exact files that need to be read
+ 708        from path, practical if e.g. only one replicum is needed
+ 709    postfix : str
+ 710        postfix of the file to read, e.g. '.gfms.dat' for openQCD-files
+ 711    names : list
+ 712        Alternative labeling for replicas/ensembles.
+ 713        Has to have the appropriate length.
+ 714    Zeuthen_flow : bool
+ 715        (optional) If True, the Zeuthen flow is used for Qtop. Only possible
+ 716        for version=='sfqcd' If False, the Wilson flow is used.
+ 717    integer_charge : bool
+ 718        If True, the charge is rounded towards the nearest integer on each config.
+ 719
+ 720    Returns
+ 721    -------
+ 722    result : Obs
+ 723        Read topological charge
+ 724    """
+ 725
+ 726    return _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version=version, obspos=0, **kwargs)
+ 727
+ 728
+ 729def read_gf_coupling(path, prefix, c, dtr_cnfg=1, Zeuthen_flow=True, **kwargs):
+ 730    """Read the gradient flow coupling based on sfqcd gradient flow measurements. See 1607.06423 for details.
+ 731
+ 732    Note: The current implementation only works for c=0.3 and T=L. The definition of the coupling in 1607.06423 requires projection to topological charge zero which is not done within this function but has to be performed in a separate step.
+ 733
+ 734    Parameters
+ 735    ----------
+ 736    path : str
+ 737        path of the measurement files
+ 738    prefix : str
+ 739        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat.
+ 740        Ignored if file names are passed explicitly via keyword files.
+ 741    c : double
+ 742        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L.
+ 743    dtr_cnfg : int
+ 744        (optional) parameter that specifies the number of measurements
+ 745        between two configs.
+ 746        If it is not set, the distance between two measurements
+ 747        in the file is assumed to be the distance between two configurations.
+ 748    steps : int
+ 749        (optional) Distance between two configurations in units of trajectories /
+ 750         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
+ 751    r_start : list
+ 752        list which contains the first config to be read for each replicum.
+ 753    r_stop : list
+ 754        list which contains the last config to be read for each replicum.
+ 755    files : list
+ 756        specify the exact files that need to be read
+ 757        from path, practical if e.g. only one replicum is needed
+ 758    names : list
+ 759        Alternative labeling for replicas/ensembles.
+ 760        Has to have the appropriate length.
+ 761    postfix : str
+ 762        postfix of the file to read, e.g. '.gfms.dat' for openQCD-files
+ 763    Zeuthen_flow : bool
+ 764        (optional) If True, the Zeuthen flow is used for the coupling. If False, the Wilson flow is used.
+ 765    """
+ 766
+ 767    if c != 0.3:
+ 768        raise Exception("The required lattice norm is only implemented for c=0.3 at the moment.")
+ 769
+ 770    plaq = _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version="sfqcd", obspos=6, sum_t=False, Zeuthen_flow=Zeuthen_flow, integer_charge=False, **kwargs)
+ 771    C2x1 = _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version="sfqcd", obspos=7, sum_t=False, Zeuthen_flow=Zeuthen_flow, integer_charge=False, **kwargs)
+ 772    L = plaq.tag["L"]
+ 773    T = plaq.tag["T"]
+ 774
+ 775    if T != L:
+ 776        raise Exception("The required lattice norm is only implemented for T=L at the moment.")
+ 777
+ 778    if Zeuthen_flow is not True:
+ 779        raise Exception("The required lattice norm is only implemented for the Zeuthen flow at the moment.")
+ 780
+ 781    t = (c * L) ** 2 / 8
+ 782
+ 783    normdict = {4: 0.012341170468270,
+ 784                6: 0.010162691462430,
+ 785                8: 0.009031614807931,
+ 786                10: 0.008744966371393,
+ 787                12: 0.008650917856809,
+ 788                14: 8.611154391267955E-03,
+ 789                16: 0.008591758449508,
+ 790                20: 0.008575359627103,
+ 791                24: 0.008569387847540,
+ 792                28: 8.566803713382559E-03,
+ 793                32: 0.008565541650006,
+ 794                40: 8.564480684962046E-03,
+ 795                48: 8.564098025073460E-03,
+ 796                64: 8.563853943383087E-03}
+ 797
+ 798    return t * t * (5 / 3 * plaq - 1 / 12 * C2x1) / normdict[L]
+ 799
+ 800
+ 801def _read_flow_obs(path, prefix, c, dtr_cnfg=1, version="openQCD", obspos=0, sum_t=True, **kwargs):
+ 802    """Read a flow observable based on openQCD gradient flow measurements.
+ 803
+ 804    Parameters
+ 805    ----------
+ 806    path : str
+ 807        path of the measurement files
+ 808    prefix : str
+ 809        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat.
+ 810        Ignored if file names are passed explicitly via keyword files.
+ 811    c : double
+ 812        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L.
+ 813    dtr_cnfg : int
+ 814        (optional) parameter that specifies the number of measurements
+ 815        between two configs.
+ 816        If it is not set, the distance between two measurements
+ 817        in the file is assumed to be the distance between two configurations.
+ 818    steps : int
+ 819        (optional) Distance between two configurations in units of trajectories /
+ 820         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
+ 821    version : str
+ 822        Either openQCD or sfqcd, depending on the data.
+ 823    obspos : int
+ 824        position of the obeservable in the measurement file. Only relevant for sfqcd files.
+ 825    sum_t : bool
+ 826        If true sum over all timeslices, if false only take the value at T/2.
+ 827    L : int
+ 828        spatial length of the lattice in L/a.
+ 829        HAS to be set if version != sfqcd, since openQCD does not provide
+ 830        this in the header
+ 831    r_start : list
+ 832        list which contains the first config to be read for each replicum.
+ 833    r_stop : list
+ 834        list which contains the last config to be read for each replicum.
+ 835    files : list
+ 836        specify the exact files that need to be read
+ 837        from path, practical if e.g. only one replicum is needed
+ 838    names : list
+ 839        Alternative labeling for replicas/ensembles.
+ 840        Has to have the appropriate length.
+ 841    postfix : str
+ 842        postfix of the file to read, e.g. '.gfms.dat' for openQCD-files
+ 843    Zeuthen_flow : bool
+ 844        (optional) If True, the Zeuthen flow is used for Qtop. Only possible
+ 845        for version=='sfqcd' If False, the Wilson flow is used.
+ 846    integer_charge : bool
+ 847        If True, the charge is rounded towards the nearest integer on each config.
  848
- 849    if version not in known_versions:
- 850        raise Exception("Unknown openQCD version.")
- 851    if "steps" in kwargs:
- 852        steps = kwargs.get("steps")
- 853    if version == "sfqcd":
- 854        if "L" in kwargs:
- 855            supposed_L = kwargs.get("L")
- 856        else:
- 857            supposed_L = None
- 858        postfix = "gfms"
- 859    else:
- 860        if "L" not in kwargs:
- 861            raise Exception("This version of openQCD needs you to provide the spatial length of the lattice as parameter 'L'.")
- 862        else:
- 863            L = kwargs.get("L")
- 864        postfix = "ms"
- 865
- 866    if "postfix" in kwargs:
- 867        postfix = kwargs.get("postfix")
- 868
- 869    if "files" in kwargs:
- 870        known_files = kwargs.get("files")
- 871    else:
- 872        known_files = []
- 873
- 874    files = _find_files(path, prefix, postfix, "dat", known_files=known_files)
+ 849    Returns
+ 850    -------
+ 851    result : Obs
+ 852        flow observable specified
+ 853    """
+ 854    known_versions = ["openQCD", "sfqcd"]
+ 855
+ 856    if version not in known_versions:
+ 857        raise Exception("Unknown openQCD version.")
+ 858    if "steps" in kwargs:
+ 859        steps = kwargs.get("steps")
+ 860    if version == "sfqcd":
+ 861        if "L" in kwargs:
+ 862            supposed_L = kwargs.get("L")
+ 863        else:
+ 864            supposed_L = None
+ 865        postfix = "gfms"
+ 866    else:
+ 867        if "L" not in kwargs:
+ 868            raise Exception("This version of openQCD needs you to provide the spatial length of the lattice as parameter 'L'.")
+ 869        else:
+ 870            L = kwargs.get("L")
+ 871        postfix = "ms"
+ 872
+ 873    if "postfix" in kwargs:
+ 874        postfix = kwargs.get("postfix")
  875
- 876    if 'r_start' in kwargs:
- 877        r_start = kwargs.get('r_start')
- 878        if len(r_start) != len(files):
- 879            raise Exception('r_start does not match number of replicas')
- 880        r_start = [o if o else None for o in r_start]
- 881    else:
- 882        r_start = [None] * len(files)
- 883
- 884    if 'r_stop' in kwargs:
- 885        r_stop = kwargs.get('r_stop')
- 886        if len(r_stop) != len(files):
- 887            raise Exception('r_stop does not match number of replicas')
+ 876    if "files" in kwargs:
+ 877        known_files = kwargs.get("files")
+ 878    else:
+ 879        known_files = []
+ 880
+ 881    files = _find_files(path, prefix, postfix, "dat", known_files=known_files)
+ 882
+ 883    if 'r_start' in kwargs:
+ 884        r_start = kwargs.get('r_start')
+ 885        if len(r_start) != len(files):
+ 886            raise Exception('r_start does not match number of replicas')
+ 887        r_start = [o if o else None for o in r_start]
  888    else:
- 889        r_stop = [None] * len(files)
- 890    rep_names = []
- 891
- 892    zeuthen = kwargs.get('Zeuthen_flow', False)
- 893    if zeuthen and version not in ['sfqcd']:
- 894        raise Exception('Zeuthen flow can only be used for version==sfqcd')
- 895
- 896    r_start_index = []
- 897    r_stop_index = []
- 898    deltas = []
- 899    configlist = []
- 900    if not zeuthen:
- 901        obspos += 8
- 902    for rep, file in enumerate(files):
- 903        with open(path + "/" + file, "rb") as fp:
- 904
- 905            Q = []
- 906            traj_list = []
- 907            if version in ['sfqcd']:
- 908                t = fp.read(12)
- 909                header = struct.unpack('<iii', t)
- 910                zthfl = header[0]  # Zeuthen flow -> if it's equal to 2 it means that the Zeuthen flow is also 'measured' (apart from the Wilson flow)
- 911                ncs = header[1]  # number of different values for c in t_flow=1/8 c² L² -> measurements done for ncs c's
- 912                tmax = header[2]  # lattice T/a
- 913
- 914                t = fp.read(12)
- 915                Ls = struct.unpack('<iii', t)
- 916                if (Ls[0] == Ls[1] and Ls[1] == Ls[2]):
- 917                    L = Ls[0]
- 918                    if not (supposed_L == L) and supposed_L:
- 919                        raise Exception("It seems the length given in the header and by you contradict each other")
- 920                else:
- 921                    raise Exception("Found more than one spatial length in header!")
- 922
- 923                t = fp.read(16)
- 924                header2 = struct.unpack('<dd', t)
- 925                tol = header2[0]
- 926                cmax = header2[1]  # highest value of c used
- 927
- 928                if c > cmax:
- 929                    raise Exception('Flow has been determined between c=0 and c=%lf with tolerance %lf' % (cmax, tol))
- 930
- 931                if (zthfl == 2):
- 932                    nfl = 2  # number of flows
- 933                else:
- 934                    nfl = 1
- 935                iobs = 8 * nfl  # number of flow observables calculated
- 936
- 937                while True:
- 938                    t = fp.read(4)
- 939                    if (len(t) < 4):
- 940                        break
- 941                    traj_list.append(struct.unpack('i', t)[0])   # trajectory number when measurement was done
- 942
- 943                    for j in range(ncs + 1):
- 944                        for i in range(iobs):
- 945                            t = fp.read(8 * tmax)
- 946                            if (i == obspos):  # determines the flow observable -> i=0 <-> Zeuthen flow
- 947                                Q.append(struct.unpack('d' * tmax, t))
- 948
- 949            else:
- 950                t = fp.read(12)
- 951                header = struct.unpack('<iii', t)
- 952                # step size in integration steps "dnms"
- 953                dn = header[0]
- 954                # number of measurements, so "ntot"/dn
- 955                nn = header[1]
- 956                # lattice T/a
- 957                tmax = header[2]
- 958
- 959                t = fp.read(8)
- 960                eps = struct.unpack('d', t)[0]
- 961
- 962                while True:
- 963                    t = fp.read(4)
- 964                    if (len(t) < 4):
- 965                        break
- 966                    traj_list.append(struct.unpack('i', t)[0])
- 967                    # Wsl
- 968                    t = fp.read(8 * tmax * (nn + 1))
- 969                    # Ysl
- 970                    t = fp.read(8 * tmax * (nn + 1))
- 971                    # Qsl, which is asked for in this method
- 972                    t = fp.read(8 * tmax * (nn + 1))
- 973                    # unpack the array of Qtops,
- 974                    # on each timeslice t=0,...,tmax-1 and the
- 975                    # measurement number in = 0...nn (see README.qcd1)
- 976                    tmpd = struct.unpack('d' * tmax * (nn + 1), t)
- 977                    Q.append(tmpd)
- 978
- 979        if len(np.unique(np.diff(traj_list))) != 1:
- 980            raise Exception("Irregularities in stepsize found")
- 981        else:
- 982            if 'steps' in kwargs:
- 983                if steps != traj_list[1] - traj_list[0]:
- 984                    raise Exception("steps and the found stepsize are not the same")
- 985            else:
- 986                steps = traj_list[1] - traj_list[0]
- 987
- 988        configlist.append([tr // steps // dtr_cnfg for tr in traj_list])
- 989        if configlist[-1][0] > 1:
- 990            offset = configlist[-1][0] - 1
- 991            warnings.warn('Assume thermalization and that the first measurement belongs to the first config. Offset = %d configs (%d trajectories / cycles)' % (
- 992                offset, offset * steps))
- 993            configlist[-1] = [item - offset for item in configlist[-1]]
+ 889        r_start = [None] * len(files)
+ 890
+ 891    if 'r_stop' in kwargs:
+ 892        r_stop = kwargs.get('r_stop')
+ 893        if len(r_stop) != len(files):
+ 894            raise Exception('r_stop does not match number of replicas')
+ 895    else:
+ 896        r_stop = [None] * len(files)
+ 897    rep_names = []
+ 898
+ 899    zeuthen = kwargs.get('Zeuthen_flow', False)
+ 900    if zeuthen and version not in ['sfqcd']:
+ 901        raise Exception('Zeuthen flow can only be used for version==sfqcd')
+ 902
+ 903    r_start_index = []
+ 904    r_stop_index = []
+ 905    deltas = []
+ 906    configlist = []
+ 907    if not zeuthen:
+ 908        obspos += 8
+ 909    for rep, file in enumerate(files):
+ 910        with open(path + "/" + file, "rb") as fp:
+ 911
+ 912            Q = []
+ 913            traj_list = []
+ 914            if version in ['sfqcd']:
+ 915                t = fp.read(12)
+ 916                header = struct.unpack('<iii', t)
+ 917                zthfl = header[0]  # Zeuthen flow -> if it's equal to 2 it means that the Zeuthen flow is also 'measured' (apart from the Wilson flow)
+ 918                ncs = header[1]  # number of different values for c in t_flow=1/8 c² L² -> measurements done for ncs c's
+ 919                tmax = header[2]  # lattice T/a
+ 920
+ 921                t = fp.read(12)
+ 922                Ls = struct.unpack('<iii', t)
+ 923                if (Ls[0] == Ls[1] and Ls[1] == Ls[2]):
+ 924                    L = Ls[0]
+ 925                    if not (supposed_L == L) and supposed_L:
+ 926                        raise Exception("It seems the length given in the header and by you contradict each other")
+ 927                else:
+ 928                    raise Exception("Found more than one spatial length in header!")
+ 929
+ 930                t = fp.read(16)
+ 931                header2 = struct.unpack('<dd', t)
+ 932                tol = header2[0]
+ 933                cmax = header2[1]  # highest value of c used
+ 934
+ 935                if c > cmax:
+ 936                    raise Exception(f'Flow has been determined between c=0 and c={cmax:f} with tolerance {tol:f}')
+ 937
+ 938                if (zthfl == 2):
+ 939                    nfl = 2  # number of flows
+ 940                else:
+ 941                    nfl = 1
+ 942                iobs = 8 * nfl  # number of flow observables calculated
+ 943
+ 944                while True:
+ 945                    t = fp.read(4)
+ 946                    if (len(t) < 4):
+ 947                        break
+ 948                    traj_list.append(struct.unpack('i', t)[0])   # trajectory number when measurement was done
+ 949
+ 950                    for _j in range(ncs + 1):
+ 951                        for i in range(iobs):
+ 952                            t = fp.read(8 * tmax)
+ 953                            if (i == obspos):  # determines the flow observable -> i=0 <-> Zeuthen flow
+ 954                                Q.append(struct.unpack('d' * tmax, t))
+ 955
+ 956            else:
+ 957                t = fp.read(12)
+ 958                header = struct.unpack('<iii', t)
+ 959                # step size in integration steps "dnms"
+ 960                dn = header[0]
+ 961                # number of measurements, so "ntot"/dn
+ 962                nn = header[1]
+ 963                # lattice T/a
+ 964                tmax = header[2]
+ 965
+ 966                t = fp.read(8)
+ 967                eps = struct.unpack('d', t)[0]
+ 968
+ 969                while True:
+ 970                    t = fp.read(4)
+ 971                    if (len(t) < 4):
+ 972                        break
+ 973                    traj_list.append(struct.unpack('i', t)[0])
+ 974                    # Wsl
+ 975                    t = fp.read(8 * tmax * (nn + 1))
+ 976                    # Ysl
+ 977                    t = fp.read(8 * tmax * (nn + 1))
+ 978                    # Qsl, which is asked for in this method
+ 979                    t = fp.read(8 * tmax * (nn + 1))
+ 980                    # unpack the array of Qtops,
+ 981                    # on each timeslice t=0,...,tmax-1 and the
+ 982                    # measurement number in = 0...nn (see README.qcd1)
+ 983                    tmpd = struct.unpack('d' * tmax * (nn + 1), t)
+ 984                    Q.append(tmpd)
+ 985
+ 986        if len(np.unique(np.diff(traj_list))) != 1:
+ 987            raise Exception("Irregularities in stepsize found")
+ 988        else:
+ 989            if 'steps' in kwargs:
+ 990                if steps != traj_list[1] - traj_list[0]:
+ 991                    raise Exception("steps and the found stepsize are not the same")
+ 992            else:
+ 993                steps = traj_list[1] - traj_list[0]
  994
- 995        if r_start[rep] is None:
- 996            r_start_index.append(0)
- 997        else:
- 998            try:
- 999                r_start_index.append(configlist[-1].index(r_start[rep]))
-1000            except ValueError:
-1001                raise Exception('Config %d not in file with range [%d, %d]' % (
-1002                    r_start[rep], configlist[-1][0], configlist[-1][-1])) from None
-1003
-1004        if r_stop[rep] is None:
-1005            r_stop_index.append(len(configlist[-1]) - 1)
-1006        else:
-1007            try:
-1008                r_stop_index.append(configlist[-1].index(r_stop[rep]))
-1009            except ValueError:
-1010                raise Exception('Config %d not in file with range [%d, %d]' % (
-1011                    r_stop[rep], configlist[-1][0], configlist[-1][-1])) from None
-1012
-1013        if version in ['sfqcd']:
-1014            cstepsize = cmax / ncs
-1015            index_aim = round(c / cstepsize)
-1016        else:
-1017            t_aim = (c * L) ** 2 / 8
-1018            index_aim = round(t_aim / eps / dn)
-1019
-1020        Q_sum = []
-1021        for i, item in enumerate(Q):
-1022            if sum_t is True:
-1023                Q_sum.append([sum(item[current:current + tmax])
-1024                             for current in range(0, len(item), tmax)])
-1025            else:
-1026                Q_sum.append([item[int(tmax / 2)]])
-1027        Q_top = []
-1028        if version in ['sfqcd']:
-1029            for i in range(len(Q_sum) // (ncs + 1)):
-1030                Q_top.append(Q_sum[i * (ncs + 1) + index_aim][0])
-1031        else:
-1032            for i in range(len(Q) // dtr_cnfg):
-1033                Q_top.append(Q_sum[dtr_cnfg * i][index_aim])
-1034        if len(Q_top) != len(traj_list) // dtr_cnfg:
-1035            raise Exception("qtops and traj_list dont have the same length")
-1036
-1037        if kwargs.get('integer_charge', False):
-1038            Q_top = [round(q) for q in Q_top]
-1039
-1040        truncated_file = file[:-len(postfix)]
-1041
-1042        if "names" not in kwargs:
-1043            try:
-1044                idx = truncated_file.index('r')
-1045            except Exception:
-1046                if "names" not in kwargs:
-1047                    raise Exception("Automatic recognition of replicum failed, please enter the key word 'names'.")
-1048            ens_name = truncated_file[:idx]
-1049            rep_names.append(ens_name + '|' + truncated_file[idx:].split(".")[0])
-1050        else:
-1051            names = kwargs.get("names")
-1052            rep_names = names
-1053
-1054        deltas.append(Q_top)
-1055
-1056    rep_names = sort_names(rep_names)
-1057
-1058    idl = [range(int(configlist[rep][r_start_index[rep]]), int(configlist[rep][r_stop_index[rep]]) + 1, 1) for rep in range(len(deltas))]
-1059    deltas = [deltas[nrep][r_start_index[nrep]:r_stop_index[nrep] + 1] for nrep in range(len(deltas))]
-1060    result = Obs(deltas, rep_names, idl=idl)
-1061    result.tag = {"T": tmax - 1,
-1062                  "L": L}
-1063    return result
-1064
+ 995        configlist.append([tr // steps // dtr_cnfg for tr in traj_list])
+ 996        if configlist[-1][0] > 1:
+ 997            offset = configlist[-1][0] - 1
+ 998            warnings.warn(f'Assume thermalization and that the first measurement belongs to the first config. Offset = {offset} configs ({offset * steps} trajectories / cycles)', stacklevel=2)
+ 999            configlist[-1] = [item - offset for item in configlist[-1]]
+1000
+1001        if r_start[rep] is None:
+1002            r_start_index.append(0)
+1003        else:
+1004            try:
+1005                r_start_index.append(configlist[-1].index(r_start[rep]))
+1006            except ValueError:
+1007                raise Exception(
+1008                    f'Config {r_start[rep]} not in file with range [{configlist[-1][0]}, {configlist[-1][-1]}]'
+1009                ) from None
+1010
+1011        if r_stop[rep] is None:
+1012            r_stop_index.append(len(configlist[-1]) - 1)
+1013        else:
+1014            try:
+1015                r_stop_index.append(configlist[-1].index(r_stop[rep]))
+1016            except ValueError:
+1017                raise Exception(
+1018                    f'Config {r_stop[rep]} not in file with range [{configlist[-1][0]}, {configlist[-1][-1]}]'
+1019                ) from None
+1020
+1021        if version in ['sfqcd']:
+1022            cstepsize = cmax / ncs
+1023            index_aim = round(c / cstepsize)
+1024        else:
+1025            t_aim = (c * L) ** 2 / 8
+1026            index_aim = round(t_aim / eps / dn)
+1027
+1028        Q_sum = []
+1029        for item in Q:
+1030            if sum_t is True:
+1031                Q_sum.append([sum(item[current:current + tmax])
+1032                             for current in range(0, len(item), tmax)])
+1033            else:
+1034                Q_sum.append([item[int(tmax / 2)]])
+1035        Q_top = []
+1036        if version in ['sfqcd']:
+1037            for i in range(len(Q_sum) // (ncs + 1)):
+1038                Q_top.append(Q_sum[i * (ncs + 1) + index_aim][0])
+1039        else:
+1040            for i in range(len(Q) // dtr_cnfg):
+1041                Q_top.append(Q_sum[dtr_cnfg * i][index_aim])
+1042        if len(Q_top) != len(traj_list) // dtr_cnfg:
+1043            raise Exception("qtops and traj_list dont have the same length")
+1044
+1045        if kwargs.get('integer_charge', False):
+1046            Q_top = [round(q) for q in Q_top]
+1047
+1048        truncated_file = file[:-len(postfix)]
+1049
+1050        if "names" not in kwargs:
+1051            try:
+1052                idx = truncated_file.index('r')
+1053            except Exception as err:
+1054                if "names" not in kwargs:
+1055                    raise Exception("Automatic recognition of replicum failed, please enter the key word 'names'.") from err
+1056            ens_name = truncated_file[:idx]
+1057            rep_names.append(ens_name + '|' + truncated_file[idx:].split(".")[0])
+1058        else:
+1059            names = kwargs.get("names")
+1060            rep_names = names
+1061
+1062        deltas.append(Q_top)
+1063
+1064    rep_names = sort_names(rep_names)
 1065
-1066def qtop_projection(qtop, target=0):
-1067    """Returns the projection to the topological charge sector defined by target.
-1068
-1069    Parameters
-1070    ----------
-1071    path : Obs
-1072        Topological charge.
-1073    target : int
-1074        Specifies the topological sector to be reweighted to (default 0)
-1075
-1076    Returns
-1077    -------
-1078    reto : Obs
-1079        projection to the topological charge sector defined by target
-1080    """
-1081    if qtop.reweighted:
-1082        raise Exception('You can not use a reweighted observable for reweighting!')
+1066    idl = [range(int(configlist[rep][r_start_index[rep]]), int(configlist[rep][r_stop_index[rep]]) + 1, 1) for rep in range(len(deltas))]
+1067    deltas = [deltas[nrep][r_start_index[nrep]:r_stop_index[nrep] + 1] for nrep in range(len(deltas))]
+1068    result = Obs(deltas, rep_names, idl=idl)
+1069    result.tag = {"T": tmax - 1,
+1070                  "L": L}
+1071    return result
+1072
+1073
+1074def qtop_projection(qtop, target=0):
+1075    """Returns the projection to the topological charge sector defined by target.
+1076
+1077    Parameters
+1078    ----------
+1079    path : Obs
+1080        Topological charge.
+1081    target : int
+1082        Specifies the topological sector to be reweighted to (default 0)
 1083
-1084    proj_qtop = []
-1085    for n in qtop.deltas:
-1086        proj_qtop.append(np.array([1 if round(qtop.r_values[n] + q) == target else 0 for q in qtop.deltas[n]]))
-1087
-1088    reto = Obs(proj_qtop, qtop.names, idl=[qtop.idl[name] for name in qtop.names])
-1089    return reto
-1090
+1084    Returns
+1085    -------
+1086    reto : Obs
+1087        projection to the topological charge sector defined by target
+1088    """
+1089    if qtop.reweighted:
+1090        raise Exception('You can not use a reweighted observable for reweighting!')
 1091
-1092def read_qtop_sector(path, prefix, c, target=0, **kwargs):
-1093    """Constructs reweighting factors to a specified topological sector.
-1094
-1095    Parameters
-1096    ----------
-1097    path : str
-1098        path of the measurement files
-1099    prefix : str
-1100        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat
-1101    c : double
-1102        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L
-1103    target : int
-1104        Specifies the topological sector to be reweighted to (default 0)
-1105    dtr_cnfg : int
-1106        (optional) parameter that specifies the number of trajectories
-1107        between two configs.
-1108        if it is not set, the distance between two measurements
-1109        in the file is assumed to be the distance between two configurations.
-1110    steps : int
-1111        (optional) Distance between two configurations in units of trajectories /
-1112         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
-1113    version : str
-1114        version string of the openQCD (sfqcd) version used to create
-1115        the ensemble. Default is 2.0. May also be set to sfqcd.
-1116    L : int
-1117        spatial length of the lattice in L/a.
-1118        HAS to be set if version != sfqcd, since openQCD does not provide
-1119        this in the header
-1120    r_start : list
-1121        offset of the first ensemble, making it easier to match
-1122        later on with other Obs
-1123    r_stop : list
-1124        last configurations that need to be read (per replicum)
-1125    files : list
-1126        specify the exact files that need to be read
-1127        from path, practical if e.g. only one replicum is needed
-1128    names : list
-1129        Alternative labeling for replicas/ensembles.
-1130        Has to have the appropriate length
-1131    Zeuthen_flow : bool
-1132        (optional) If True, the Zeuthen flow is used for Qtop. Only possible
-1133        for version=='sfqcd' If False, the Wilson flow is used.
-1134
-1135    Returns
-1136    -------
-1137    reto : Obs
-1138        projection to the topological charge sector defined by target
-1139    """
-1140
-1141    if not isinstance(target, int):
-1142        raise Exception("'target' has to be an integer.")
-1143
-1144    kwargs['integer_charge'] = True
-1145    qtop = read_qtop(path, prefix, c, **kwargs)
-1146
-1147    return qtop_projection(qtop, target=target)
+1092    proj_qtop = []
+1093    for n in qtop.deltas:
+1094        proj_qtop.append(np.array([1 if round(qtop.r_values[n] + q) == target else 0 for q in qtop.deltas[n]]))
+1095
+1096    reto = Obs(proj_qtop, qtop.names, idl=[qtop.idl[name] for name in qtop.names])
+1097    return reto
+1098
+1099
+1100def read_qtop_sector(path, prefix, c, target=0, **kwargs):
+1101    """Constructs reweighting factors to a specified topological sector.
+1102
+1103    Parameters
+1104    ----------
+1105    path : str
+1106        path of the measurement files
+1107    prefix : str
+1108        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat
+1109    c : double
+1110        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L
+1111    target : int
+1112        Specifies the topological sector to be reweighted to (default 0)
+1113    dtr_cnfg : int
+1114        (optional) parameter that specifies the number of trajectories
+1115        between two configs.
+1116        if it is not set, the distance between two measurements
+1117        in the file is assumed to be the distance between two configurations.
+1118    steps : int
+1119        (optional) Distance between two configurations in units of trajectories /
+1120         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
+1121    version : str
+1122        version string of the openQCD (sfqcd) version used to create
+1123        the ensemble. Default is 2.0. May also be set to sfqcd.
+1124    L : int
+1125        spatial length of the lattice in L/a.
+1126        HAS to be set if version != sfqcd, since openQCD does not provide
+1127        this in the header
+1128    r_start : list
+1129        offset of the first ensemble, making it easier to match
+1130        later on with other Obs
+1131    r_stop : list
+1132        last configurations that need to be read (per replicum)
+1133    files : list
+1134        specify the exact files that need to be read
+1135        from path, practical if e.g. only one replicum is needed
+1136    names : list
+1137        Alternative labeling for replicas/ensembles.
+1138        Has to have the appropriate length
+1139    Zeuthen_flow : bool
+1140        (optional) If True, the Zeuthen flow is used for Qtop. Only possible
+1141        for version=='sfqcd' If False, the Wilson flow is used.
+1142
+1143    Returns
+1144    -------
+1145    reto : Obs
+1146        projection to the topological charge sector defined by target
+1147    """
 1148
-1149
-1150def read_ms5_xsf(path, prefix, qc, corr, sep="r", **kwargs):
-1151    """
-1152    Read data from files in the specified directory with the specified prefix and quark combination extension, and return a `Corr` object containing the data.
-1153
-1154    Parameters
-1155    ----------
-1156    path : str
-1157        The directory to search for the files in.
-1158    prefix : str
-1159        The prefix to match the files against.
-1160    qc : str
-1161        The quark combination extension to match the files against.
-1162    corr : str
-1163        The correlator to extract data for.
-1164    sep : str, optional
-1165        The separator to use when parsing the replika names.
-1166    **kwargs
-1167        Additional keyword arguments. The following keyword arguments are recognized:
-1168
-1169        - names (List[str]): A list of names to use for the replicas.
-1170        - files (List[str]): A list of files to read data from.
-1171        - idl (List[List[int]]): A list of idls per replicum, resticting data to the idls given.
-1172
-1173    Returns
-1174    -------
-1175    Corr
-1176        A complex valued `Corr` object containing the data read from the files. In case of boudary to bulk correlators.
-1177    or
-1178    CObs
-1179        A complex valued `CObs` object containing the data read from the files. In case of boudary to boundary correlators.
+1149    if not isinstance(target, int):
+1150        raise Exception("'target' has to be an integer.")
+1151
+1152    kwargs['integer_charge'] = True
+1153    qtop = read_qtop(path, prefix, c, **kwargs)
+1154
+1155    return qtop_projection(qtop, target=target)
+1156
+1157
+1158def read_ms5_xsf(path, prefix, qc, corr, sep="r", **kwargs):
+1159    """
+1160    Read data from files in the specified directory with the specified prefix and quark combination extension, and return a `Corr` object containing the data.
+1161
+1162    Parameters
+1163    ----------
+1164    path : str
+1165        The directory to search for the files in.
+1166    prefix : str
+1167        The prefix to match the files against.
+1168    qc : str
+1169        The quark combination extension to match the files against.
+1170    corr : str
+1171        The correlator to extract data for.
+1172    sep : str, optional
+1173        The separator to use when parsing the replika names.
+1174    **kwargs
+1175        Additional keyword arguments. The following keyword arguments are recognized:
+1176
+1177        - names (List[str]): A list of names to use for the replicas.
+1178        - files (List[str]): A list of files to read data from.
+1179        - idl (List[List[int]]): A list of idls per replicum, resticting data to the idls given.
 1180
-1181
-1182    Raises
-1183    ------
-1184    FileNotFoundError
-1185        If no files matching the specified prefix and quark combination extension are found in the specified directory.
-1186    IOError
-1187        If there is an error reading a file.
-1188    struct.error
-1189        If there is an error unpacking binary data.
-1190    """
-1191
-1192    # found = []
-1193    files = []
-1194    names = []
-1195
-1196    # test if the input is correct
-1197    if qc not in ['dd', 'ud', 'du', 'uu']:
-1198        raise Exception("Unknown quark conbination!")
+1181    Returns
+1182    -------
+1183    Corr
+1184        A complex valued `Corr` object containing the data read from the files. In case of boudary to bulk correlators.
+1185    or
+1186    CObs
+1187        A complex valued `CObs` object containing the data read from the files. In case of boudary to boundary correlators.
+1188
+1189
+1190    Raises
+1191    ------
+1192    FileNotFoundError
+1193        If no files matching the specified prefix and quark combination extension are found in the specified directory.
+1194    IOError
+1195        If there is an error reading a file.
+1196    struct.error
+1197        If there is an error unpacking binary data.
+1198    """
 1199
-1200    if corr not in ["gS", "gP", "gA", "gV", "gVt", "lA", "lV", "lVt", "lT", "lTt", "g1", "l1"]:
-1201        raise Exception("Unknown correlator!")
-1202
-1203    if "files" in kwargs:
-1204        known_files = kwargs.get("files")
-1205    else:
-1206        known_files = []
-1207    files = _find_files(path, prefix, "ms5_xsf_" + qc, "dat", known_files=known_files)
-1208
-1209    if "names" in kwargs:
-1210        names = kwargs.get("names")
-1211    else:
-1212        for f in files:
-1213            if not sep == "":
-1214                se = f.split(".")[0]
-1215                for s in f.split(".")[1:-2]:
-1216                    se += "." + s
-1217                names.append(se.split(sep)[0] + "|r" + se.split(sep)[1])
-1218            else:
-1219                names.append(prefix)
-1220    if 'idl' in kwargs:
-1221        expected_idl = kwargs.get('idl')
-1222    names = sorted(names)
-1223    files = sorted(files)
-1224
-1225    cnfgs = []
-1226    realsamples = []
-1227    imagsamples = []
-1228    repnum = 0
-1229    for file in files:
-1230        with open(path + "/" + file, "rb") as fp:
-1231
-1232            t = fp.read(8)
-1233            kappa = struct.unpack('d', t)[0]
-1234            t = fp.read(8)
-1235            csw = struct.unpack('d', t)[0]
-1236            t = fp.read(8)
-1237            dF = struct.unpack('d', t)[0]
-1238            t = fp.read(8)
-1239            zF = struct.unpack('d', t)[0]
-1240
-1241            t = fp.read(4)
-1242            tmax = struct.unpack('i', t)[0]
-1243            t = fp.read(4)
-1244            bnd = struct.unpack('i', t)[0]
-1245
-1246            placesBI = ["gS", "gP",
-1247                        "gA", "gV",
-1248                        "gVt", "lA",
-1249                        "lV", "lVt",
-1250                        "lT", "lTt"]
-1251            placesBB = ["g1", "l1"]
-1252
-1253            # the chunks have the following structure:
-1254            # confignumber, 10x timedependent complex correlators as doubles, 2x timeindependent complex correlators as doubles
-1255
-1256            chunksize = 4 + (8 * 2 * tmax * 10) + (8 * 2 * 2)
-1257            packstr = '=i' + ('d' * 2 * tmax * 10) + ('d' * 2 * 2)
-1258            cnfgs.append([])
-1259            realsamples.append([])
-1260            imagsamples.append([])
-1261            for t in range(tmax):
-1262                realsamples[repnum].append([])
-1263                imagsamples[repnum].append([])
-1264            if 'idl' in kwargs:
-1265                left_idl = set(expected_idl[repnum])
-1266            while True:
-1267                cnfgt = fp.read(chunksize)
-1268                if not cnfgt:
-1269                    break
-1270                asascii = struct.unpack(packstr, cnfgt)
-1271                cnfg = asascii[0]
-1272                idl_wanted = True
-1273                if 'idl' in kwargs:
-1274                    idl_wanted = (cnfg in expected_idl[repnum])
-1275                    left_idl = left_idl - set([cnfg])
-1276                if idl_wanted:
-1277                    cnfgs[repnum].append(cnfg)
-1278
-1279                    if corr not in placesBB:
-1280                        tmpcorr = asascii[1 + 2 * tmax * placesBI.index(corr):1 + 2 * tmax * placesBI.index(corr) + 2 * tmax]
-1281                    else:
-1282                        tmpcorr = asascii[1 + 2 * tmax * len(placesBI) + 2 * placesBB.index(corr):1 + 2 * tmax * len(placesBI) + 2 * placesBB.index(corr) + 2]
-1283
-1284                    corrres = [[], []]
-1285                    for i in range(len(tmpcorr)):
-1286                        corrres[i % 2].append(tmpcorr[i])
-1287                    for t in range(int(len(tmpcorr) / 2)):
-1288                        realsamples[repnum][t].append(corrres[0][t])
-1289                    for t in range(int(len(tmpcorr) / 2)):
-1290                        imagsamples[repnum][t].append(corrres[1][t])
-1291            if 'idl' in kwargs:
-1292                left_idl = list(left_idl)
-1293                if expected_idl[repnum] == left_idl:
-1294                    raise ValueError("None of the idls searched for were found in replikum of file " + file)
-1295                elif len(left_idl) > 0:
-1296                    warnings.warn('Could not find idls ' + str(left_idl) + ' in replikum of file ' + file, UserWarning)
-1297        repnum += 1
-1298    s = "Read correlator " + corr + " from " + str(repnum) + " replika with idls" + str(realsamples[0][t])
-1299    for rep in range(1, repnum):
-1300        s += ", " + str(realsamples[rep][t])
-1301    print(s)
-1302    print("Asserted run parameters:\n T:", tmax, "kappa:", kappa, "csw:", csw, "dF:", dF, "zF:", zF, "bnd:", bnd)
-1303
-1304    # we have the data now... but we need to re format the whole thing and put it into Corr objects.
-1305
-1306    compObs = []
-1307
-1308    for t in range(int(len(tmpcorr) / 2)):
-1309        compObs.append(CObs(Obs([realsamples[rep][t] for rep in range(repnum)], names=names, idl=cnfgs),
-1310                            Obs([imagsamples[rep][t] for rep in range(repnum)], names=names, idl=cnfgs)))
+1200    # found = []
+1201    files = []
+1202    names = []
+1203
+1204    # test if the input is correct
+1205    if qc not in ['dd', 'ud', 'du', 'uu']:
+1206        raise Exception("Unknown quark conbination!")
+1207
+1208    if corr not in ["gS", "gP", "gA", "gV", "gVt", "lA", "lV", "lVt", "lT", "lTt", "g1", "l1"]:
+1209        raise Exception("Unknown correlator!")
+1210
+1211    if "files" in kwargs:
+1212        known_files = kwargs.get("files")
+1213    else:
+1214        known_files = []
+1215    files = _find_files(path, prefix, "ms5_xsf_" + qc, "dat", known_files=known_files)
+1216
+1217    if "names" in kwargs:
+1218        names = kwargs.get("names")
+1219    else:
+1220        for f in files:
+1221            if not sep == "":
+1222                se = f.split(".")[0]
+1223                for s in f.split(".")[1:-2]:
+1224                    se += "." + s
+1225                names.append(se.split(sep)[0] + "|r" + se.split(sep)[1])
+1226            else:
+1227                names.append(prefix)
+1228    if 'idl' in kwargs:
+1229        expected_idl = kwargs.get('idl')
+1230    names = sorted(names)
+1231    files = sorted(files)
+1232
+1233    cnfgs = []
+1234    realsamples = []
+1235    imagsamples = []
+1236    repnum = 0
+1237    for file in files:
+1238        with open(path + "/" + file, "rb") as fp:
+1239
+1240            t = fp.read(8)
+1241            kappa = struct.unpack('d', t)[0]
+1242            t = fp.read(8)
+1243            csw = struct.unpack('d', t)[0]
+1244            t = fp.read(8)
+1245            dF = struct.unpack('d', t)[0]
+1246            t = fp.read(8)
+1247            zF = struct.unpack('d', t)[0]
+1248
+1249            t = fp.read(4)
+1250            tmax = struct.unpack('i', t)[0]
+1251            t = fp.read(4)
+1252            bnd = struct.unpack('i', t)[0]
+1253
+1254            placesBI = ["gS", "gP",
+1255                        "gA", "gV",
+1256                        "gVt", "lA",
+1257                        "lV", "lVt",
+1258                        "lT", "lTt"]
+1259            placesBB = ["g1", "l1"]
+1260
+1261            # the chunks have the following structure:
+1262            # confignumber, 10x timedependent complex correlators as doubles, 2x timeindependent complex correlators as doubles
+1263
+1264            chunksize = 4 + (8 * 2 * tmax * 10) + (8 * 2 * 2)
+1265            packstr = '=i' + ('d' * 2 * tmax * 10) + ('d' * 2 * 2)
+1266            cnfgs.append([])
+1267            realsamples.append([])
+1268            imagsamples.append([])
+1269            for _ in range(tmax):
+1270                realsamples[repnum].append([])
+1271                imagsamples[repnum].append([])
+1272            if 'idl' in kwargs:
+1273                left_idl = set(expected_idl[repnum])
+1274            while True:
+1275                cnfgt = fp.read(chunksize)
+1276                if not cnfgt:
+1277                    break
+1278                asascii = struct.unpack(packstr, cnfgt)
+1279                cnfg = asascii[0]
+1280                idl_wanted = True
+1281                if 'idl' in kwargs:
+1282                    idl_wanted = (cnfg in expected_idl[repnum])
+1283                    left_idl = left_idl - set([cnfg])
+1284                if idl_wanted:
+1285                    cnfgs[repnum].append(cnfg)
+1286
+1287                    if corr not in placesBB:
+1288                        tmpcorr = asascii[1 + 2 * tmax * placesBI.index(corr):1 + 2 * tmax * placesBI.index(corr) + 2 * tmax]
+1289                    else:
+1290                        tmpcorr = asascii[1 + 2 * tmax * len(placesBI) + 2 * placesBB.index(corr):1 + 2 * tmax * len(placesBI) + 2 * placesBB.index(corr) + 2]
+1291
+1292                    corrres = [[], []]
+1293                    for i in range(len(tmpcorr)):
+1294                        corrres[i % 2].append(tmpcorr[i])
+1295                    for t in range(int(len(tmpcorr) / 2)):
+1296                        realsamples[repnum][t].append(corrres[0][t])
+1297                    for t in range(int(len(tmpcorr) / 2)):
+1298                        imagsamples[repnum][t].append(corrres[1][t])
+1299            if 'idl' in kwargs:
+1300                left_idl = list(left_idl)
+1301                if expected_idl[repnum] == left_idl:
+1302                    raise ValueError("None of the idls searched for were found in replikum of file " + file)
+1303                elif len(left_idl) > 0:
+1304                    warnings.warn('Could not find idls ' + str(left_idl) + ' in replikum of file ' + file, UserWarning, stacklevel=2)
+1305        repnum += 1
+1306    s = "Read correlator " + corr + " from " + str(repnum) + " replika with idls" + str(realsamples[0][t])
+1307    for rep in range(1, repnum):
+1308        s += ", " + str(realsamples[rep][t])
+1309    print(s)
+1310    print("Asserted run parameters:\n T:", tmax, "kappa:", kappa, "csw:", csw, "dF:", dF, "zF:", zF, "bnd:", bnd)
 1311
-1312    if len(compObs) == 1:
-1313        return compObs[0]
-1314    else:
-1315        return Corr(compObs)
+1312    # we have the data now... but we need to re format the whole thing and put it into Corr objects.
+1313
+1314    compObs = []
+1315
+1316    for t in range(int(len(tmpcorr) / 2)):
+1317        compObs.append(CObs(Obs([realsamples[rep][t] for rep in range(repnum)], names=names, idl=cnfgs),
+1318                            Obs([imagsamples[rep][t] for rep in range(repnum)], names=names, idl=cnfgs)))
+1319
+1320    if len(compObs) == 1:
+1321        return compObs[0]
+1322    else:
+1323        return Corr(compObs)
 
@@ -1427,223 +1435,225 @@
-
 14def read_rwms(path, prefix, version='2.0', names=None, **kwargs):
- 15    """Read rwms format from given folder structure. Returns a list of length nrw
- 16
- 17    Parameters
- 18    ----------
- 19    path : str
- 20        path that contains the data files
- 21    prefix : str
- 22        all files in path that start with prefix are considered as input files.
- 23        May be used together postfix to consider only special file endings.
- 24        Prefix is ignored, if the keyword 'files' is used.
- 25    version : str
- 26        version of openQCD, default 2.0
- 27    names : list
- 28        list of names that is assigned to the data according according
- 29        to the order in the file list. Use careful, if you do not provide file names!
- 30    r_start : list
- 31        list which contains the first config to be read for each replicum
- 32    r_stop : list
- 33        list which contains the last config to be read for each replicum
- 34    r_step : int
- 35        integer that defines a fixed step size between two measurements (in units of configs)
- 36        If not given, r_step=1 is assumed.
- 37    postfix : str
- 38        postfix of the file to read, e.g. '.ms1' for openQCD-files
- 39    files : list
- 40        list which contains the filenames to be read. No automatic detection of
- 41        files performed if given.
- 42    print_err : bool
- 43        Print additional information that is useful for debugging.
- 44
- 45    Returns
- 46    -------
- 47    rwms : Obs
- 48        Reweighting factors read
- 49    """
- 50    known_oqcd_versions = ['1.4', '1.6', '2.0']
- 51    if version not in known_oqcd_versions:
- 52        raise Exception('Unknown openQCD version defined!')
- 53    print("Working with openQCD version " + version)
- 54    if 'postfix' in kwargs:
- 55        postfix = kwargs.get('postfix')
- 56    else:
- 57        postfix = ''
- 58
- 59    if 'files' in kwargs:
- 60        known_files = kwargs.get('files')
- 61    else:
- 62        known_files = []
- 63
- 64    ls = _find_files(path, prefix, postfix, 'dat', known_files=known_files)
- 65
- 66    replica = len(ls)
- 67
- 68    if 'r_start' in kwargs:
- 69        r_start = kwargs.get('r_start')
- 70        if len(r_start) != replica:
- 71            raise Exception('r_start does not match number of replicas')
- 72        r_start = [o if o else None for o in r_start]
- 73    else:
- 74        r_start = [None] * replica
- 75
- 76    if 'r_stop' in kwargs:
- 77        r_stop = kwargs.get('r_stop')
- 78        if len(r_stop) != replica:
- 79            raise Exception('r_stop does not match number of replicas')
- 80    else:
- 81        r_stop = [None] * replica
- 82
- 83    if 'r_step' in kwargs:
- 84        r_step = kwargs.get('r_step')
- 85    else:
- 86        r_step = 1
- 87
- 88    print('Read reweighting factors from', prefix[:-1], ',',
- 89          replica, 'replica', end='')
- 90
- 91    if names is None:
- 92        rep_names = []
- 93        for entry in ls:
- 94            truncated_entry = entry
- 95            suffixes = [".dat", ".rwms", ".ms1"]
- 96            for suffix in suffixes:
- 97                if truncated_entry.endswith(suffix):
- 98                    truncated_entry = truncated_entry[0:-len(suffix)]
- 99            idx = truncated_entry.index('r')
-100            rep_names.append(truncated_entry[:idx] + '|' + truncated_entry[idx:])
-101    else:
-102        rep_names = names
-103
-104    rep_names = sort_names(rep_names)
-105
-106    print_err = 0
-107    if 'print_err' in kwargs:
-108        print_err = 1
-109        print()
-110
-111    deltas = []
-112
-113    configlist = []
-114    r_start_index = []
-115    r_stop_index = []
-116
-117    for rep in range(replica):
-118        tmp_array = []
-119        with open(path + '/' + ls[rep], 'rb') as fp:
-120
-121            t = fp.read(4)  # number of reweighting factors
-122            if rep == 0:
-123                nrw = struct.unpack('i', t)[0]
-124                if version == '2.0':
-125                    nrw = int(nrw / 2)
-126                for k in range(nrw):
-127                    deltas.append([])
-128            else:
-129                if ((nrw != struct.unpack('i', t)[0] and (not version == '2.0')) or (nrw != struct.unpack('i', t)[0] / 2 and version == '2.0')):
-130                    raise Exception('Error: different number of reweighting factors for replicum', rep)
-131
-132            for k in range(nrw):
-133                tmp_array.append([])
-134
-135            # This block is necessary for openQCD1.6 and openQCD2.0 ms1 files
-136            nfct = []
-137            if version in ['1.6', '2.0']:
-138                for i in range(nrw):
-139                    t = fp.read(4)
-140                    nfct.append(struct.unpack('i', t)[0])
-141            else:
-142                for i in range(nrw):
-143                    nfct.append(1)
-144
-145            nsrc = []
-146            for i in range(nrw):
-147                t = fp.read(4)
-148                nsrc.append(struct.unpack('i', t)[0])
-149            if version == '2.0':
-150                if not struct.unpack('i', fp.read(4))[0] == 0:
-151                    raise Exception("You are using the input for openQCD version 2.0, this is not correct.")
-152
-153            configlist.append([])
-154            while True:
-155                t = fp.read(4)
-156                if len(t) < 4:
-157                    break
-158                config_no = struct.unpack('i', t)[0]
-159                configlist[-1].append(config_no)
-160                for i in range(nrw):
-161                    if (version == '2.0'):
-162                        tmpd = _read_array_openQCD2(fp)
+            
 15def read_rwms(path, prefix, version='2.0', names=None, **kwargs):
+ 16    """Read rwms format from given folder structure. Returns a list of length nrw
+ 17
+ 18    Parameters
+ 19    ----------
+ 20    path : str
+ 21        path that contains the data files
+ 22    prefix : str
+ 23        all files in path that start with prefix are considered as input files.
+ 24        May be used together postfix to consider only special file endings.
+ 25        Prefix is ignored, if the keyword 'files' is used.
+ 26    version : str
+ 27        version of openQCD, default 2.0
+ 28    names : list
+ 29        list of names that is assigned to the data according according
+ 30        to the order in the file list. Use careful, if you do not provide file names!
+ 31    r_start : list
+ 32        list which contains the first config to be read for each replicum
+ 33    r_stop : list
+ 34        list which contains the last config to be read for each replicum
+ 35    r_step : int
+ 36        integer that defines a fixed step size between two measurements (in units of configs)
+ 37        If not given, r_step=1 is assumed.
+ 38    postfix : str
+ 39        postfix of the file to read, e.g. '.ms1' for openQCD-files
+ 40    files : list
+ 41        list which contains the filenames to be read. No automatic detection of
+ 42        files performed if given.
+ 43    print_err : bool
+ 44        Print additional information that is useful for debugging.
+ 45
+ 46    Returns
+ 47    -------
+ 48    rwms : Obs
+ 49        Reweighting factors read
+ 50    """
+ 51    known_oqcd_versions = ['1.4', '1.6', '2.0']
+ 52    if version not in known_oqcd_versions:
+ 53        raise Exception('Unknown openQCD version defined!')
+ 54    print("Working with openQCD version " + version)
+ 55    if 'postfix' in kwargs:
+ 56        postfix = kwargs.get('postfix')
+ 57    else:
+ 58        postfix = ''
+ 59
+ 60    if 'files' in kwargs:
+ 61        known_files = kwargs.get('files')
+ 62    else:
+ 63        known_files = []
+ 64
+ 65    ls = _find_files(path, prefix, postfix, 'dat', known_files=known_files)
+ 66
+ 67    replica = len(ls)
+ 68
+ 69    if 'r_start' in kwargs:
+ 70        r_start = kwargs.get('r_start')
+ 71        if len(r_start) != replica:
+ 72            raise Exception('r_start does not match number of replicas')
+ 73        r_start = [o if o else None for o in r_start]
+ 74    else:
+ 75        r_start = [None] * replica
+ 76
+ 77    if 'r_stop' in kwargs:
+ 78        r_stop = kwargs.get('r_stop')
+ 79        if len(r_stop) != replica:
+ 80            raise Exception('r_stop does not match number of replicas')
+ 81    else:
+ 82        r_stop = [None] * replica
+ 83
+ 84    if 'r_step' in kwargs:
+ 85        r_step = kwargs.get('r_step')
+ 86    else:
+ 87        r_step = 1
+ 88
+ 89    print('Read reweighting factors from', prefix[:-1], ',',
+ 90          replica, 'replica', end='')
+ 91
+ 92    if names is None:
+ 93        rep_names = []
+ 94        for entry in ls:
+ 95            truncated_entry = entry
+ 96            suffixes = [".dat", ".rwms", ".ms1"]
+ 97            for suffix in suffixes:
+ 98                if truncated_entry.endswith(suffix):
+ 99                    truncated_entry = truncated_entry[0:-len(suffix)]
+100            idx = truncated_entry.index('r')
+101            rep_names.append(truncated_entry[:idx] + '|' + truncated_entry[idx:])
+102    else:
+103        rep_names = names
+104
+105    rep_names = sort_names(rep_names)
+106
+107    print_err = 0
+108    if 'print_err' in kwargs:
+109        print_err = 1
+110        print()
+111
+112    deltas = []
+113
+114    configlist = []
+115    r_start_index = []
+116    r_stop_index = []
+117
+118    for rep in range(replica):
+119        tmp_array = []
+120        with open(path + '/' + ls[rep], 'rb') as fp:
+121
+122            t = fp.read(4)  # number of reweighting factors
+123            if rep == 0:
+124                nrw = struct.unpack('i', t)[0]
+125                if version == '2.0':
+126                    nrw = int(nrw / 2)
+127                for _ in range(nrw):
+128                    deltas.append([])
+129            else:
+130                if ((nrw != struct.unpack('i', t)[0] and (not version == '2.0')) or (nrw != struct.unpack('i', t)[0] / 2 and version == '2.0')):
+131                    raise Exception('Error: different number of reweighting factors for replicum', rep)
+132
+133            for _ in range(nrw):
+134                tmp_array.append([])
+135
+136            # This block is necessary for openQCD1.6 and openQCD2.0 ms1 files
+137            nfct = []
+138            if version in ['1.6', '2.0']:
+139                for _ in range(nrw):
+140                    t = fp.read(4)
+141                    nfct.append(struct.unpack('i', t)[0])
+142            else:
+143                for _ in range(nrw):
+144                    nfct.append(1)
+145
+146            nsrc = []
+147            for _ in range(nrw):
+148                t = fp.read(4)
+149                nsrc.append(struct.unpack('i', t)[0])
+150            if version == '2.0':
+151                if not struct.unpack('i', fp.read(4))[0] == 0:
+152                    raise Exception("You are using the input for openQCD version 2.0, this is not correct.")
+153
+154            configlist.append([])
+155            while True:
+156                t = fp.read(4)
+157                if len(t) < 4:
+158                    break
+159                config_no = struct.unpack('i', t)[0]
+160                configlist[-1].append(config_no)
+161                for i in range(nrw):
+162                    if (version == '2.0'):
 163                        tmpd = _read_array_openQCD2(fp)
-164                        tmp_rw = tmpd['arr']
-165                        tmp_nfct = 1.0
-166                        for j in range(tmpd['n'][0]):
-167                            tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw[j])))
-168                            if print_err:
-169                                print(config_no, i, j,
-170                                      np.mean(np.exp(-np.asarray(tmp_rw[j]))),
-171                                      np.std(np.exp(-np.asarray(tmp_rw[j]))))
-172                                print('Sources:',
-173                                      np.exp(-np.asarray(tmp_rw[j])))
-174                                print('Partial factor:', tmp_nfct)
-175                    elif version == '1.6' or version == '1.4':
-176                        tmp_nfct = 1.0
-177                        for j in range(nfct[i]):
-178                            t = fp.read(8 * nsrc[i])
+164                        tmpd = _read_array_openQCD2(fp)
+165                        tmp_rw = tmpd['arr']
+166                        tmp_nfct = 1.0
+167                        for j in range(tmpd['n'][0]):
+168                            tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw[j])))
+169                            if print_err:
+170                                print(config_no, i, j,
+171                                      np.mean(np.exp(-np.asarray(tmp_rw[j]))),
+172                                      np.std(np.exp(-np.asarray(tmp_rw[j]))))
+173                                print('Sources:',
+174                                      np.exp(-np.asarray(tmp_rw[j])))
+175                                print('Partial factor:', tmp_nfct)
+176                    elif version == '1.6' or version == '1.4':
+177                        tmp_nfct = 1.0
+178                        for j in range(nfct[i]):
 179                            t = fp.read(8 * nsrc[i])
-180                            tmp_rw = struct.unpack('d' * nsrc[i], t)
-181                            tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw)))
-182                            if print_err:
-183                                print(config_no, i, j,
-184                                      np.mean(np.exp(-np.asarray(tmp_rw))),
-185                                      np.std(np.exp(-np.asarray(tmp_rw))))
-186                                print('Sources:', np.exp(-np.asarray(tmp_rw)))
-187                                print('Partial factor:', tmp_nfct)
-188                    tmp_array[i].append(tmp_nfct)
-189
-190            diffmeas = configlist[-1][-1] - configlist[-1][-2]
-191            configlist[-1] = [item // diffmeas for item in configlist[-1]]
-192            if configlist[-1][0] > 1 and diffmeas > 1:
-193                warnings.warn('Assume thermalization and that the first measurement belongs to the first config.')
-194                offset = configlist[-1][0] - 1
-195                configlist[-1] = [item - offset for item in configlist[-1]]
-196
-197            if r_start[rep] is None:
-198                r_start_index.append(0)
-199            else:
-200                try:
-201                    r_start_index.append(configlist[-1].index(r_start[rep]))
-202                except ValueError:
-203                    raise Exception('Config %d not in file with range [%d, %d]' % (
-204                        r_start[rep], configlist[-1][0], configlist[-1][-1])) from None
-205
-206            if r_stop[rep] is None:
-207                r_stop_index.append(len(configlist[-1]) - 1)
-208            else:
-209                try:
-210                    r_stop_index.append(configlist[-1].index(r_stop[rep]))
-211                except ValueError:
-212                    raise Exception('Config %d not in file with range [%d, %d]' % (
-213                        r_stop[rep], configlist[-1][0], configlist[-1][-1])) from None
-214
-215            for k in range(nrw):
-216                deltas[k].append(tmp_array[k][r_start_index[rep]:r_stop_index[rep] + 1][::r_step])
+180                            t = fp.read(8 * nsrc[i])
+181                            tmp_rw = struct.unpack('d' * nsrc[i], t)
+182                            tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw)))
+183                            if print_err:
+184                                print(config_no, i, j,
+185                                      np.mean(np.exp(-np.asarray(tmp_rw))),
+186                                      np.std(np.exp(-np.asarray(tmp_rw))))
+187                                print('Sources:', np.exp(-np.asarray(tmp_rw)))
+188                                print('Partial factor:', tmp_nfct)
+189                    tmp_array[i].append(tmp_nfct)
+190
+191            diffmeas = configlist[-1][-1] - configlist[-1][-2]
+192            configlist[-1] = [item // diffmeas for item in configlist[-1]]
+193            if configlist[-1][0] > 1 and diffmeas > 1:
+194                warnings.warn('Assume thermalization and that the first measurement belongs to the first config.', stacklevel=2)
+195                offset = configlist[-1][0] - 1
+196                configlist[-1] = [item - offset for item in configlist[-1]]
+197
+198            if r_start[rep] is None:
+199                r_start_index.append(0)
+200            else:
+201                try:
+202                    r_start_index.append(configlist[-1].index(r_start[rep]))
+203                except ValueError:
+204                    raise Exception(
+205                        f'Config {r_start[rep]} not in file with range [{configlist[-1][0]}, {configlist[-1][-1]}]'
+206                    ) from None
+207
+208            if r_stop[rep] is None:
+209                r_stop_index.append(len(configlist[-1]) - 1)
+210            else:
+211                try:
+212                    r_stop_index.append(configlist[-1].index(r_stop[rep]))
+213                except ValueError:
+214                    raise Exception(
+215                        f'Config {r_stop[rep]} not in file with range [{configlist[-1][0]}, {configlist[-1][-1]}]'
+216                    ) from None
 217
-218    if np.any([len(np.unique(np.diff(cl))) != 1 for cl in configlist]):
-219        raise Exception('Irregular spaced data in input file!', [len(np.unique(np.diff(cl))) for cl in configlist])
-220    stepsizes = [list(np.unique(np.diff(cl)))[0] for cl in configlist]
-221    if np.any([step != 1 for step in stepsizes]):
-222        warnings.warn('Stepsize between configurations is greater than one!' + str(stepsizes), RuntimeWarning)
-223
-224    print(',', nrw, 'reweighting factors with', nsrc, 'sources')
-225    result = []
-226    idl = [range(configlist[rep][r_start_index[rep]], configlist[rep][r_stop_index[rep]] + 1, r_step) for rep in range(replica)]
-227
-228    for t in range(nrw):
-229        result.append(Obs(deltas[t], rep_names, idl=idl))
-230    return result
+218            for k in range(nrw):
+219                deltas[k].append(tmp_array[k][r_start_index[rep]:r_stop_index[rep] + 1][::r_step])
+220
+221    if np.any([len(np.unique(np.diff(cl))) != 1 for cl in configlist]):
+222        raise Exception('Irregular spaced data in input file!', [len(np.unique(np.diff(cl))) for cl in configlist])
+223    stepsizes = [next(iter(np.unique(np.diff(cl)))) for cl in configlist]
+224    if np.any([step != 1 for step in stepsizes]):
+225        warnings.warn('Stepsize between configurations is greater than one!' + str(stepsizes), RuntimeWarning, stacklevel=2)
+226
+227    print(',', nrw, 'reweighting factors with', nsrc, 'sources')
+228    result = []
+229    idl = [range(configlist[rep][r_start_index[rep]], configlist[rep][r_stop_index[rep]] + 1, r_step) for rep in range(replica)]
+230
+231    for t in range(nrw):
+232        result.append(Obs(deltas[t], rep_names, idl=idl))
+233    return result
 
@@ -1700,78 +1710,78 @@ Reweighting factors read
-
429def extract_t0(path, prefix, dtr_read, xmin, spatial_extent, fit_range=5, postfix='ms', c=0.3, **kwargs):
-430    """Extract t0/a^2 from given .ms.dat files. Returns t0 as Obs.
-431
-432    It is assumed that all boundary effects have
-433    sufficiently decayed at x0=xmin.
-434    The data around the zero crossing of t^2<E> - c (where c=0.3 by default)
-435    is fitted with a linear function
-436    from which the exact root is extracted.
-437
-438    It is assumed that one measurement is performed for each config.
-439    If this is not the case, the resulting idl, as well as the handling
-440    of `r_start`, `r_stop` and `r_step` is wrong and the user has to correct
-441    this in the resulting observable.
-442    The function also assumes that `r_step` is the same across all replica.
-443
-444    Parameters
-445    ----------
-446    path : str
-447        Path to .ms.dat files
-448    prefix : str
-449        Ensemble prefix
-450    dtr_read : int
-451        Determines how many trajectories should be skipped
-452        when reading the ms.dat files.
-453        Corresponds to dtr_cnfg / dtr_ms in the openQCD input file.
-454    xmin : int
-455        First timeslice where the boundary
-456        effects have sufficiently decayed.
-457    spatial_extent : int
-458        spatial extent of the lattice, required for normalization.
-459    fit_range : int
-460        Number of data points left and right of the zero
-461        crossing to be included in the linear fit. (Default: 5)
-462    postfix : str
-463        Postfix of measurement file (Default: ms)
-464    c: float
-465        Constant that defines the flow scale. Default 0.3 for t_0, choose 2./3 for t_1.
-466    r_start : list
-467        list which contains the first config to be read for each replicum.
-468    r_stop : list
-469        list which contains the last config to be read for each replicum.
-470    r_step : int
-471        integer that defines a fixed step size between two measurements (in units of configs)
-472        If not given, r_step=1 is assumed.
-473    plaquette : bool
-474        If true extract the plaquette estimate of t0 instead.
-475    names : list
-476        list of names that is assigned to the data according according
-477        to the order in the file list. Use careful, if you do not provide file names!
-478    files : list
-479        list which contains the filenames to be read. No automatic detection of
-480        files performed if given.
-481    plot_fit : bool
-482        If true, the fit for the extraction of t0 is shown together with the data.
-483    assume_thermalization : bool
-484        If True: If the first record divided by the distance between two measurements is larger than
-485        1, it is assumed that this is due to thermalization and the first measurement belongs
-486        to the first config (default).
-487        If False: The config numbers are assumed to be traj_number // difference
-488
-489    Returns
-490    -------
-491    t0 : Obs
-492        Extracted t0
-493    """
-494
-495    E_dict = _extract_flowed_energy_density(path, prefix, dtr_read, xmin, spatial_extent, postfix, **kwargs)
-496    t2E_dict = {}
-497    for t in sorted(E_dict.keys()):
-498        t2E_dict[t] = t ** 2 * E_dict[t] - c
+            
434def extract_t0(path, prefix, dtr_read, xmin, spatial_extent, fit_range=5, postfix='ms', c=0.3, **kwargs):
+435    """Extract t0/a^2 from given .ms.dat files. Returns t0 as Obs.
+436
+437    It is assumed that all boundary effects have
+438    sufficiently decayed at x0=xmin.
+439    The data around the zero crossing of t^2<E> - c (where c=0.3 by default)
+440    is fitted with a linear function
+441    from which the exact root is extracted.
+442
+443    It is assumed that one measurement is performed for each config.
+444    If this is not the case, the resulting idl, as well as the handling
+445    of `r_start`, `r_stop` and `r_step` is wrong and the user has to correct
+446    this in the resulting observable.
+447    The function also assumes that `r_step` is the same across all replica.
+448
+449    Parameters
+450    ----------
+451    path : str
+452        Path to .ms.dat files
+453    prefix : str
+454        Ensemble prefix
+455    dtr_read : int
+456        Determines how many trajectories should be skipped
+457        when reading the ms.dat files.
+458        Corresponds to dtr_cnfg / dtr_ms in the openQCD input file.
+459    xmin : int
+460        First timeslice where the boundary
+461        effects have sufficiently decayed.
+462    spatial_extent : int
+463        spatial extent of the lattice, required for normalization.
+464    fit_range : int
+465        Number of data points left and right of the zero
+466        crossing to be included in the linear fit. (Default: 5)
+467    postfix : str
+468        Postfix of measurement file (Default: ms)
+469    c: float
+470        Constant that defines the flow scale. Default 0.3 for t_0, choose 2./3 for t_1.
+471    r_start : list
+472        list which contains the first config to be read for each replicum.
+473    r_stop : list
+474        list which contains the last config to be read for each replicum.
+475    r_step : int
+476        integer that defines a fixed step size between two measurements (in units of configs)
+477        If not given, r_step=1 is assumed.
+478    plaquette : bool
+479        If true extract the plaquette estimate of t0 instead.
+480    names : list
+481        list of names that is assigned to the data according according
+482        to the order in the file list. Use careful, if you do not provide file names!
+483    files : list
+484        list which contains the filenames to be read. No automatic detection of
+485        files performed if given.
+486    plot_fit : bool
+487        If true, the fit for the extraction of t0 is shown together with the data.
+488    assume_thermalization : bool
+489        If True: If the first record divided by the distance between two measurements is larger than
+490        1, it is assumed that this is due to thermalization and the first measurement belongs
+491        to the first config (default).
+492        If False: The config numbers are assumed to be traj_number // difference
+493
+494    Returns
+495    -------
+496    t0 : Obs
+497        Extracted t0
+498    """
 499
-500    return fit_t0(t2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'))
+500    E_dict = _extract_flowed_energy_density(path, prefix, dtr_read, xmin, spatial_extent, postfix, **kwargs)
+501    t2E_dict = {}
+502    for t in sorted(E_dict.keys()):
+503        t2E_dict[t] = t ** 2 * E_dict[t] - c
+504
+505    return fit_t0(t2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'))
 
@@ -1857,86 +1867,86 @@ Extracted t0
-
503def extract_w0(path, prefix, dtr_read, xmin, spatial_extent, fit_range=5, postfix='ms', c=0.3, **kwargs):
-504    """Extract w0/a from given .ms.dat files. Returns w0 as Obs.
-505
-506    It is assumed that all boundary effects have
-507    sufficiently decayed at x0=xmin.
-508    The data around the zero crossing of t d(t^2<E>)/dt -  (where c=0.3 by default)
-509    is fitted with a linear function
-510    from which the exact root is extracted.
-511
-512    It is assumed that one measurement is performed for each config.
-513    If this is not the case, the resulting idl, as well as the handling
-514    of r_start, r_stop and r_step is wrong and the user has to correct
-515    this in the resulting observable.
+            
508def extract_w0(path, prefix, dtr_read, xmin, spatial_extent, fit_range=5, postfix='ms', c=0.3, **kwargs):
+509    """Extract w0/a from given .ms.dat files. Returns w0 as Obs.
+510
+511    It is assumed that all boundary effects have
+512    sufficiently decayed at x0=xmin.
+513    The data around the zero crossing of t d(t^2<E>)/dt -  (where c=0.3 by default)
+514    is fitted with a linear function
+515    from which the exact root is extracted.
 516
-517    Parameters
-518    ----------
-519    path : str
-520        Path to .ms.dat files
-521    prefix : str
-522        Ensemble prefix
-523    dtr_read : int
-524        Determines how many trajectories should be skipped
-525        when reading the ms.dat files.
-526        Corresponds to dtr_cnfg / dtr_ms in the openQCD input file.
-527    xmin : int
-528        First timeslice where the boundary
-529        effects have sufficiently decayed.
-530    spatial_extent : int
-531        spatial extent of the lattice, required for normalization.
-532    fit_range : int
-533        Number of data points left and right of the zero
-534        crossing to be included in the linear fit. (Default: 5)
-535    postfix : str
-536        Postfix of measurement file (Default: ms)
-537    c: float
-538        Constant that defines the flow scale. Default 0.3 for w_0, choose 2./3 for w_1.
-539    r_start : list
-540        list which contains the first config to be read for each replicum.
-541    r_stop : list
-542        list which contains the last config to be read for each replicum.
-543    r_step : int
-544        integer that defines a fixed step size between two measurements (in units of configs)
-545        If not given, r_step=1 is assumed.
-546    plaquette : bool
-547        If true extract the plaquette estimate of w0 instead.
-548    names : list
-549        list of names that is assigned to the data according according
-550        to the order in the file list. Use careful, if you do not provide file names!
-551    files : list
-552        list which contains the filenames to be read. No automatic detection of
-553        files performed if given.
-554    plot_fit : bool
-555        If true, the fit for the extraction of w0 is shown together with the data.
-556    assume_thermalization : bool
-557        If True: If the first record divided by the distance between two measurements is larger than
-558        1, it is assumed that this is due to thermalization and the first measurement belongs
-559        to the first config (default).
-560        If False: The config numbers are assumed to be traj_number // difference
-561
-562    Returns
-563    -------
-564    w0 : Obs
-565        Extracted w0
-566    """
-567
-568    E_dict = _extract_flowed_energy_density(path, prefix, dtr_read, xmin, spatial_extent, postfix, **kwargs)
-569
-570    ftimes = sorted(E_dict.keys())
-571
-572    t2E_dict = {}
-573    for t in ftimes:
-574        t2E_dict[t] = t ** 2 * E_dict[t]
-575
-576    tdtt2E_dict = {}
-577    tdtt2E_dict[ftimes[0]] = ftimes[0] * (t2E_dict[ftimes[1]] - t2E_dict[ftimes[0]]) / (ftimes[1] - ftimes[0]) - c
-578    for i in range(1, len(ftimes) - 1):
-579        tdtt2E_dict[ftimes[i]] = ftimes[i] * (t2E_dict[ftimes[i + 1]] - t2E_dict[ftimes[i - 1]]) / (ftimes[i + 1] - ftimes[i - 1]) - c
-580    tdtt2E_dict[ftimes[-1]] = ftimes[-1] * (t2E_dict[ftimes[-1]] - t2E_dict[ftimes[-2]]) / (ftimes[-1] - ftimes[-2]) - c
-581
-582    return np.sqrt(fit_t0(tdtt2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'), observable='w0'))
+517    It is assumed that one measurement is performed for each config.
+518    If this is not the case, the resulting idl, as well as the handling
+519    of r_start, r_stop and r_step is wrong and the user has to correct
+520    this in the resulting observable.
+521
+522    Parameters
+523    ----------
+524    path : str
+525        Path to .ms.dat files
+526    prefix : str
+527        Ensemble prefix
+528    dtr_read : int
+529        Determines how many trajectories should be skipped
+530        when reading the ms.dat files.
+531        Corresponds to dtr_cnfg / dtr_ms in the openQCD input file.
+532    xmin : int
+533        First timeslice where the boundary
+534        effects have sufficiently decayed.
+535    spatial_extent : int
+536        spatial extent of the lattice, required for normalization.
+537    fit_range : int
+538        Number of data points left and right of the zero
+539        crossing to be included in the linear fit. (Default: 5)
+540    postfix : str
+541        Postfix of measurement file (Default: ms)
+542    c: float
+543        Constant that defines the flow scale. Default 0.3 for w_0, choose 2./3 for w_1.
+544    r_start : list
+545        list which contains the first config to be read for each replicum.
+546    r_stop : list
+547        list which contains the last config to be read for each replicum.
+548    r_step : int
+549        integer that defines a fixed step size between two measurements (in units of configs)
+550        If not given, r_step=1 is assumed.
+551    plaquette : bool
+552        If true extract the plaquette estimate of w0 instead.
+553    names : list
+554        list of names that is assigned to the data according according
+555        to the order in the file list. Use careful, if you do not provide file names!
+556    files : list
+557        list which contains the filenames to be read. No automatic detection of
+558        files performed if given.
+559    plot_fit : bool
+560        If true, the fit for the extraction of w0 is shown together with the data.
+561    assume_thermalization : bool
+562        If True: If the first record divided by the distance between two measurements is larger than
+563        1, it is assumed that this is due to thermalization and the first measurement belongs
+564        to the first config (default).
+565        If False: The config numbers are assumed to be traj_number // difference
+566
+567    Returns
+568    -------
+569    w0 : Obs
+570        Extracted w0
+571    """
+572
+573    E_dict = _extract_flowed_energy_density(path, prefix, dtr_read, xmin, spatial_extent, postfix, **kwargs)
+574
+575    ftimes = sorted(E_dict.keys())
+576
+577    t2E_dict = {}
+578    for t in ftimes:
+579        t2E_dict[t] = t ** 2 * E_dict[t]
+580
+581    tdtt2E_dict = {}
+582    tdtt2E_dict[ftimes[0]] = ftimes[0] * (t2E_dict[ftimes[1]] - t2E_dict[ftimes[0]]) / (ftimes[1] - ftimes[0]) - c
+583    for i in range(1, len(ftimes) - 1):
+584        tdtt2E_dict[ftimes[i]] = ftimes[i] * (t2E_dict[ftimes[i + 1]] - t2E_dict[ftimes[i - 1]]) / (ftimes[i + 1] - ftimes[i - 1]) - c
+585    tdtt2E_dict[ftimes[-1]] = ftimes[-1] * (t2E_dict[ftimes[-1]] - t2E_dict[ftimes[-2]]) / (ftimes[-1] - ftimes[-2]) - c
+586
+587    return np.sqrt(fit_t0(tdtt2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'), observable='w0'))
 
@@ -2021,57 +2031,57 @@ Extracted w0
-
670def read_qtop(path, prefix, c, dtr_cnfg=1, version="openQCD", **kwargs):
-671    """Read the topologial charge based on openQCD gradient flow measurements.
-672
-673    Parameters
-674    ----------
-675    path : str
-676        path of the measurement files
-677    prefix : str
-678        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat.
-679        Ignored if file names are passed explicitly via keyword files.
-680    c : double
-681        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L.
-682    dtr_cnfg : int
-683        (optional) parameter that specifies the number of measurements
-684        between two configs.
-685        If it is not set, the distance between two measurements
-686        in the file is assumed to be the distance between two configurations.
-687    steps : int
-688        (optional) Distance between two configurations in units of trajectories /
-689         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
-690    version : str
-691        Either openQCD or sfqcd, depending on the data.
-692    L : int
-693        spatial length of the lattice in L/a.
-694        HAS to be set if version != sfqcd, since openQCD does not provide
-695        this in the header
-696    r_start : list
-697        list which contains the first config to be read for each replicum.
-698    r_stop : list
-699        list which contains the last config to be read for each replicum.
-700    files : list
-701        specify the exact files that need to be read
-702        from path, practical if e.g. only one replicum is needed
-703    postfix : str
-704        postfix of the file to read, e.g. '.gfms.dat' for openQCD-files
-705    names : list
-706        Alternative labeling for replicas/ensembles.
-707        Has to have the appropriate length.
-708    Zeuthen_flow : bool
-709        (optional) If True, the Zeuthen flow is used for Qtop. Only possible
-710        for version=='sfqcd' If False, the Wilson flow is used.
-711    integer_charge : bool
-712        If True, the charge is rounded towards the nearest integer on each config.
-713
-714    Returns
-715    -------
-716    result : Obs
-717        Read topological charge
-718    """
-719
-720    return _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version=version, obspos=0, **kwargs)
+            
677def read_qtop(path, prefix, c, dtr_cnfg=1, version="openQCD", **kwargs):
+678    """Read the topologial charge based on openQCD gradient flow measurements.
+679
+680    Parameters
+681    ----------
+682    path : str
+683        path of the measurement files
+684    prefix : str
+685        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat.
+686        Ignored if file names are passed explicitly via keyword files.
+687    c : double
+688        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L.
+689    dtr_cnfg : int
+690        (optional) parameter that specifies the number of measurements
+691        between two configs.
+692        If it is not set, the distance between two measurements
+693        in the file is assumed to be the distance between two configurations.
+694    steps : int
+695        (optional) Distance between two configurations in units of trajectories /
+696         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
+697    version : str
+698        Either openQCD or sfqcd, depending on the data.
+699    L : int
+700        spatial length of the lattice in L/a.
+701        HAS to be set if version != sfqcd, since openQCD does not provide
+702        this in the header
+703    r_start : list
+704        list which contains the first config to be read for each replicum.
+705    r_stop : list
+706        list which contains the last config to be read for each replicum.
+707    files : list
+708        specify the exact files that need to be read
+709        from path, practical if e.g. only one replicum is needed
+710    postfix : str
+711        postfix of the file to read, e.g. '.gfms.dat' for openQCD-files
+712    names : list
+713        Alternative labeling for replicas/ensembles.
+714        Has to have the appropriate length.
+715    Zeuthen_flow : bool
+716        (optional) If True, the Zeuthen flow is used for Qtop. Only possible
+717        for version=='sfqcd' If False, the Wilson flow is used.
+718    integer_charge : bool
+719        If True, the charge is rounded towards the nearest integer on each config.
+720
+721    Returns
+722    -------
+723    result : Obs
+724        Read topological charge
+725    """
+726
+727    return _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version=version, obspos=0, **kwargs)
 
@@ -2141,76 +2151,76 @@ Read topological charge
-
723def read_gf_coupling(path, prefix, c, dtr_cnfg=1, Zeuthen_flow=True, **kwargs):
-724    """Read the gradient flow coupling based on sfqcd gradient flow measurements. See 1607.06423 for details.
-725
-726    Note: The current implementation only works for c=0.3 and T=L. The definition of the coupling in 1607.06423 requires projection to topological charge zero which is not done within this function but has to be performed in a separate step.
-727
-728    Parameters
-729    ----------
-730    path : str
-731        path of the measurement files
-732    prefix : str
-733        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat.
-734        Ignored if file names are passed explicitly via keyword files.
-735    c : double
-736        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L.
-737    dtr_cnfg : int
-738        (optional) parameter that specifies the number of measurements
-739        between two configs.
-740        If it is not set, the distance between two measurements
-741        in the file is assumed to be the distance between two configurations.
-742    steps : int
-743        (optional) Distance between two configurations in units of trajectories /
-744         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
-745    r_start : list
-746        list which contains the first config to be read for each replicum.
-747    r_stop : list
-748        list which contains the last config to be read for each replicum.
-749    files : list
-750        specify the exact files that need to be read
-751        from path, practical if e.g. only one replicum is needed
-752    names : list
-753        Alternative labeling for replicas/ensembles.
-754        Has to have the appropriate length.
-755    postfix : str
-756        postfix of the file to read, e.g. '.gfms.dat' for openQCD-files
-757    Zeuthen_flow : bool
-758        (optional) If True, the Zeuthen flow is used for the coupling. If False, the Wilson flow is used.
-759    """
-760
-761    if c != 0.3:
-762        raise Exception("The required lattice norm is only implemented for c=0.3 at the moment.")
-763
-764    plaq = _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version="sfqcd", obspos=6, sum_t=False, Zeuthen_flow=Zeuthen_flow, integer_charge=False, **kwargs)
-765    C2x1 = _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version="sfqcd", obspos=7, sum_t=False, Zeuthen_flow=Zeuthen_flow, integer_charge=False, **kwargs)
-766    L = plaq.tag["L"]
-767    T = plaq.tag["T"]
-768
-769    if T != L:
-770        raise Exception("The required lattice norm is only implemented for T=L at the moment.")
-771
-772    if Zeuthen_flow is not True:
-773        raise Exception("The required lattice norm is only implemented for the Zeuthen flow at the moment.")
-774
-775    t = (c * L) ** 2 / 8
-776
-777    normdict = {4: 0.012341170468270,
-778                6: 0.010162691462430,
-779                8: 0.009031614807931,
-780                10: 0.008744966371393,
-781                12: 0.008650917856809,
-782                14: 8.611154391267955E-03,
-783                16: 0.008591758449508,
-784                20: 0.008575359627103,
-785                24: 0.008569387847540,
-786                28: 8.566803713382559E-03,
-787                32: 0.008565541650006,
-788                40: 8.564480684962046E-03,
-789                48: 8.564098025073460E-03,
-790                64: 8.563853943383087E-03}
-791
-792    return t * t * (5 / 3 * plaq - 1 / 12 * C2x1) / normdict[L]
+            
730def read_gf_coupling(path, prefix, c, dtr_cnfg=1, Zeuthen_flow=True, **kwargs):
+731    """Read the gradient flow coupling based on sfqcd gradient flow measurements. See 1607.06423 for details.
+732
+733    Note: The current implementation only works for c=0.3 and T=L. The definition of the coupling in 1607.06423 requires projection to topological charge zero which is not done within this function but has to be performed in a separate step.
+734
+735    Parameters
+736    ----------
+737    path : str
+738        path of the measurement files
+739    prefix : str
+740        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat.
+741        Ignored if file names are passed explicitly via keyword files.
+742    c : double
+743        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L.
+744    dtr_cnfg : int
+745        (optional) parameter that specifies the number of measurements
+746        between two configs.
+747        If it is not set, the distance between two measurements
+748        in the file is assumed to be the distance between two configurations.
+749    steps : int
+750        (optional) Distance between two configurations in units of trajectories /
+751         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
+752    r_start : list
+753        list which contains the first config to be read for each replicum.
+754    r_stop : list
+755        list which contains the last config to be read for each replicum.
+756    files : list
+757        specify the exact files that need to be read
+758        from path, practical if e.g. only one replicum is needed
+759    names : list
+760        Alternative labeling for replicas/ensembles.
+761        Has to have the appropriate length.
+762    postfix : str
+763        postfix of the file to read, e.g. '.gfms.dat' for openQCD-files
+764    Zeuthen_flow : bool
+765        (optional) If True, the Zeuthen flow is used for the coupling. If False, the Wilson flow is used.
+766    """
+767
+768    if c != 0.3:
+769        raise Exception("The required lattice norm is only implemented for c=0.3 at the moment.")
+770
+771    plaq = _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version="sfqcd", obspos=6, sum_t=False, Zeuthen_flow=Zeuthen_flow, integer_charge=False, **kwargs)
+772    C2x1 = _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version="sfqcd", obspos=7, sum_t=False, Zeuthen_flow=Zeuthen_flow, integer_charge=False, **kwargs)
+773    L = plaq.tag["L"]
+774    T = plaq.tag["T"]
+775
+776    if T != L:
+777        raise Exception("The required lattice norm is only implemented for T=L at the moment.")
+778
+779    if Zeuthen_flow is not True:
+780        raise Exception("The required lattice norm is only implemented for the Zeuthen flow at the moment.")
+781
+782    t = (c * L) ** 2 / 8
+783
+784    normdict = {4: 0.012341170468270,
+785                6: 0.010162691462430,
+786                8: 0.009031614807931,
+787                10: 0.008744966371393,
+788                12: 0.008650917856809,
+789                14: 8.611154391267955E-03,
+790                16: 0.008591758449508,
+791                20: 0.008575359627103,
+792                24: 0.008569387847540,
+793                28: 8.566803713382559E-03,
+794                32: 0.008565541650006,
+795                40: 8.564480684962046E-03,
+796                48: 8.564098025073460E-03,
+797                64: 8.563853943383087E-03}
+798
+799    return t * t * (5 / 3 * plaq - 1 / 12 * C2x1) / normdict[L]
 
@@ -2266,30 +2276,30 @@ postfix of the file to read, e.g. '.gfms.dat' for openQCD-files
-
1067def qtop_projection(qtop, target=0):
-1068    """Returns the projection to the topological charge sector defined by target.
-1069
-1070    Parameters
-1071    ----------
-1072    path : Obs
-1073        Topological charge.
-1074    target : int
-1075        Specifies the topological sector to be reweighted to (default 0)
-1076
-1077    Returns
-1078    -------
-1079    reto : Obs
-1080        projection to the topological charge sector defined by target
-1081    """
-1082    if qtop.reweighted:
-1083        raise Exception('You can not use a reweighted observable for reweighting!')
+            
1075def qtop_projection(qtop, target=0):
+1076    """Returns the projection to the topological charge sector defined by target.
+1077
+1078    Parameters
+1079    ----------
+1080    path : Obs
+1081        Topological charge.
+1082    target : int
+1083        Specifies the topological sector to be reweighted to (default 0)
 1084
-1085    proj_qtop = []
-1086    for n in qtop.deltas:
-1087        proj_qtop.append(np.array([1 if round(qtop.r_values[n] + q) == target else 0 for q in qtop.deltas[n]]))
-1088
-1089    reto = Obs(proj_qtop, qtop.names, idl=[qtop.idl[name] for name in qtop.names])
-1090    return reto
+1085    Returns
+1086    -------
+1087    reto : Obs
+1088        projection to the topological charge sector defined by target
+1089    """
+1090    if qtop.reweighted:
+1091        raise Exception('You can not use a reweighted observable for reweighting!')
+1092
+1093    proj_qtop = []
+1094    for n in qtop.deltas:
+1095        proj_qtop.append(np.array([1 if round(qtop.r_values[n] + q) == target else 0 for q in qtop.deltas[n]]))
+1096
+1097    reto = Obs(proj_qtop, qtop.names, idl=[qtop.idl[name] for name in qtop.names])
+1098    return reto
 
@@ -2325,62 +2335,62 @@ projection to the topological charge sector defined by target
-
1093def read_qtop_sector(path, prefix, c, target=0, **kwargs):
-1094    """Constructs reweighting factors to a specified topological sector.
-1095
-1096    Parameters
-1097    ----------
-1098    path : str
-1099        path of the measurement files
-1100    prefix : str
-1101        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat
-1102    c : double
-1103        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L
-1104    target : int
-1105        Specifies the topological sector to be reweighted to (default 0)
-1106    dtr_cnfg : int
-1107        (optional) parameter that specifies the number of trajectories
-1108        between two configs.
-1109        if it is not set, the distance between two measurements
-1110        in the file is assumed to be the distance between two configurations.
-1111    steps : int
-1112        (optional) Distance between two configurations in units of trajectories /
-1113         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
-1114    version : str
-1115        version string of the openQCD (sfqcd) version used to create
-1116        the ensemble. Default is 2.0. May also be set to sfqcd.
-1117    L : int
-1118        spatial length of the lattice in L/a.
-1119        HAS to be set if version != sfqcd, since openQCD does not provide
-1120        this in the header
-1121    r_start : list
-1122        offset of the first ensemble, making it easier to match
-1123        later on with other Obs
-1124    r_stop : list
-1125        last configurations that need to be read (per replicum)
-1126    files : list
-1127        specify the exact files that need to be read
-1128        from path, practical if e.g. only one replicum is needed
-1129    names : list
-1130        Alternative labeling for replicas/ensembles.
-1131        Has to have the appropriate length
-1132    Zeuthen_flow : bool
-1133        (optional) If True, the Zeuthen flow is used for Qtop. Only possible
-1134        for version=='sfqcd' If False, the Wilson flow is used.
-1135
-1136    Returns
-1137    -------
-1138    reto : Obs
-1139        projection to the topological charge sector defined by target
-1140    """
-1141
-1142    if not isinstance(target, int):
-1143        raise Exception("'target' has to be an integer.")
-1144
-1145    kwargs['integer_charge'] = True
-1146    qtop = read_qtop(path, prefix, c, **kwargs)
-1147
-1148    return qtop_projection(qtop, target=target)
+            
1101def read_qtop_sector(path, prefix, c, target=0, **kwargs):
+1102    """Constructs reweighting factors to a specified topological sector.
+1103
+1104    Parameters
+1105    ----------
+1106    path : str
+1107        path of the measurement files
+1108    prefix : str
+1109        prefix of the measurement files, e.g. <prefix>_id0_r0.ms.dat
+1110    c : double
+1111        Smearing radius in units of the lattice extent, c = sqrt(8 t0) / L
+1112    target : int
+1113        Specifies the topological sector to be reweighted to (default 0)
+1114    dtr_cnfg : int
+1115        (optional) parameter that specifies the number of trajectories
+1116        between two configs.
+1117        if it is not set, the distance between two measurements
+1118        in the file is assumed to be the distance between two configurations.
+1119    steps : int
+1120        (optional) Distance between two configurations in units of trajectories /
+1121         cycles. Assumed to be the distance between two measurements * dtr_cnfg if not given
+1122    version : str
+1123        version string of the openQCD (sfqcd) version used to create
+1124        the ensemble. Default is 2.0. May also be set to sfqcd.
+1125    L : int
+1126        spatial length of the lattice in L/a.
+1127        HAS to be set if version != sfqcd, since openQCD does not provide
+1128        this in the header
+1129    r_start : list
+1130        offset of the first ensemble, making it easier to match
+1131        later on with other Obs
+1132    r_stop : list
+1133        last configurations that need to be read (per replicum)
+1134    files : list
+1135        specify the exact files that need to be read
+1136        from path, practical if e.g. only one replicum is needed
+1137    names : list
+1138        Alternative labeling for replicas/ensembles.
+1139        Has to have the appropriate length
+1140    Zeuthen_flow : bool
+1141        (optional) If True, the Zeuthen flow is used for Qtop. Only possible
+1142        for version=='sfqcd' If False, the Wilson flow is used.
+1143
+1144    Returns
+1145    -------
+1146    reto : Obs
+1147        projection to the topological charge sector defined by target
+1148    """
+1149
+1150    if not isinstance(target, int):
+1151        raise Exception("'target' has to be an integer.")
+1152
+1153    kwargs['integer_charge'] = True
+1154    qtop = read_qtop(path, prefix, c, **kwargs)
+1155
+1156    return qtop_projection(qtop, target=target)
 
@@ -2449,172 +2459,172 @@ projection to the topological charge sector defined by target
-
1151def read_ms5_xsf(path, prefix, qc, corr, sep="r", **kwargs):
-1152    """
-1153    Read data from files in the specified directory with the specified prefix and quark combination extension, and return a `Corr` object containing the data.
-1154
-1155    Parameters
-1156    ----------
-1157    path : str
-1158        The directory to search for the files in.
-1159    prefix : str
-1160        The prefix to match the files against.
-1161    qc : str
-1162        The quark combination extension to match the files against.
-1163    corr : str
-1164        The correlator to extract data for.
-1165    sep : str, optional
-1166        The separator to use when parsing the replika names.
-1167    **kwargs
-1168        Additional keyword arguments. The following keyword arguments are recognized:
-1169
-1170        - names (List[str]): A list of names to use for the replicas.
-1171        - files (List[str]): A list of files to read data from.
-1172        - idl (List[List[int]]): A list of idls per replicum, resticting data to the idls given.
-1173
-1174    Returns
-1175    -------
-1176    Corr
-1177        A complex valued `Corr` object containing the data read from the files. In case of boudary to bulk correlators.
-1178    or
-1179    CObs
-1180        A complex valued `CObs` object containing the data read from the files. In case of boudary to boundary correlators.
+            
1159def read_ms5_xsf(path, prefix, qc, corr, sep="r", **kwargs):
+1160    """
+1161    Read data from files in the specified directory with the specified prefix and quark combination extension, and return a `Corr` object containing the data.
+1162
+1163    Parameters
+1164    ----------
+1165    path : str
+1166        The directory to search for the files in.
+1167    prefix : str
+1168        The prefix to match the files against.
+1169    qc : str
+1170        The quark combination extension to match the files against.
+1171    corr : str
+1172        The correlator to extract data for.
+1173    sep : str, optional
+1174        The separator to use when parsing the replika names.
+1175    **kwargs
+1176        Additional keyword arguments. The following keyword arguments are recognized:
+1177
+1178        - names (List[str]): A list of names to use for the replicas.
+1179        - files (List[str]): A list of files to read data from.
+1180        - idl (List[List[int]]): A list of idls per replicum, resticting data to the idls given.
 1181
-1182
-1183    Raises
-1184    ------
-1185    FileNotFoundError
-1186        If no files matching the specified prefix and quark combination extension are found in the specified directory.
-1187    IOError
-1188        If there is an error reading a file.
-1189    struct.error
-1190        If there is an error unpacking binary data.
-1191    """
-1192
-1193    # found = []
-1194    files = []
-1195    names = []
-1196
-1197    # test if the input is correct
-1198    if qc not in ['dd', 'ud', 'du', 'uu']:
-1199        raise Exception("Unknown quark conbination!")
+1182    Returns
+1183    -------
+1184    Corr
+1185        A complex valued `Corr` object containing the data read from the files. In case of boudary to bulk correlators.
+1186    or
+1187    CObs
+1188        A complex valued `CObs` object containing the data read from the files. In case of boudary to boundary correlators.
+1189
+1190
+1191    Raises
+1192    ------
+1193    FileNotFoundError
+1194        If no files matching the specified prefix and quark combination extension are found in the specified directory.
+1195    IOError
+1196        If there is an error reading a file.
+1197    struct.error
+1198        If there is an error unpacking binary data.
+1199    """
 1200
-1201    if corr not in ["gS", "gP", "gA", "gV", "gVt", "lA", "lV", "lVt", "lT", "lTt", "g1", "l1"]:
-1202        raise Exception("Unknown correlator!")
-1203
-1204    if "files" in kwargs:
-1205        known_files = kwargs.get("files")
-1206    else:
-1207        known_files = []
-1208    files = _find_files(path, prefix, "ms5_xsf_" + qc, "dat", known_files=known_files)
-1209
-1210    if "names" in kwargs:
-1211        names = kwargs.get("names")
-1212    else:
-1213        for f in files:
-1214            if not sep == "":
-1215                se = f.split(".")[0]
-1216                for s in f.split(".")[1:-2]:
-1217                    se += "." + s
-1218                names.append(se.split(sep)[0] + "|r" + se.split(sep)[1])
-1219            else:
-1220                names.append(prefix)
-1221    if 'idl' in kwargs:
-1222        expected_idl = kwargs.get('idl')
-1223    names = sorted(names)
-1224    files = sorted(files)
-1225
-1226    cnfgs = []
-1227    realsamples = []
-1228    imagsamples = []
-1229    repnum = 0
-1230    for file in files:
-1231        with open(path + "/" + file, "rb") as fp:
-1232
-1233            t = fp.read(8)
-1234            kappa = struct.unpack('d', t)[0]
-1235            t = fp.read(8)
-1236            csw = struct.unpack('d', t)[0]
-1237            t = fp.read(8)
-1238            dF = struct.unpack('d', t)[0]
-1239            t = fp.read(8)
-1240            zF = struct.unpack('d', t)[0]
-1241
-1242            t = fp.read(4)
-1243            tmax = struct.unpack('i', t)[0]
-1244            t = fp.read(4)
-1245            bnd = struct.unpack('i', t)[0]
-1246
-1247            placesBI = ["gS", "gP",
-1248                        "gA", "gV",
-1249                        "gVt", "lA",
-1250                        "lV", "lVt",
-1251                        "lT", "lTt"]
-1252            placesBB = ["g1", "l1"]
-1253
-1254            # the chunks have the following structure:
-1255            # confignumber, 10x timedependent complex correlators as doubles, 2x timeindependent complex correlators as doubles
-1256
-1257            chunksize = 4 + (8 * 2 * tmax * 10) + (8 * 2 * 2)
-1258            packstr = '=i' + ('d' * 2 * tmax * 10) + ('d' * 2 * 2)
-1259            cnfgs.append([])
-1260            realsamples.append([])
-1261            imagsamples.append([])
-1262            for t in range(tmax):
-1263                realsamples[repnum].append([])
-1264                imagsamples[repnum].append([])
-1265            if 'idl' in kwargs:
-1266                left_idl = set(expected_idl[repnum])
-1267            while True:
-1268                cnfgt = fp.read(chunksize)
-1269                if not cnfgt:
-1270                    break
-1271                asascii = struct.unpack(packstr, cnfgt)
-1272                cnfg = asascii[0]
-1273                idl_wanted = True
-1274                if 'idl' in kwargs:
-1275                    idl_wanted = (cnfg in expected_idl[repnum])
-1276                    left_idl = left_idl - set([cnfg])
-1277                if idl_wanted:
-1278                    cnfgs[repnum].append(cnfg)
-1279
-1280                    if corr not in placesBB:
-1281                        tmpcorr = asascii[1 + 2 * tmax * placesBI.index(corr):1 + 2 * tmax * placesBI.index(corr) + 2 * tmax]
-1282                    else:
-1283                        tmpcorr = asascii[1 + 2 * tmax * len(placesBI) + 2 * placesBB.index(corr):1 + 2 * tmax * len(placesBI) + 2 * placesBB.index(corr) + 2]
-1284
-1285                    corrres = [[], []]
-1286                    for i in range(len(tmpcorr)):
-1287                        corrres[i % 2].append(tmpcorr[i])
-1288                    for t in range(int(len(tmpcorr) / 2)):
-1289                        realsamples[repnum][t].append(corrres[0][t])
-1290                    for t in range(int(len(tmpcorr) / 2)):
-1291                        imagsamples[repnum][t].append(corrres[1][t])
-1292            if 'idl' in kwargs:
-1293                left_idl = list(left_idl)
-1294                if expected_idl[repnum] == left_idl:
-1295                    raise ValueError("None of the idls searched for were found in replikum of file " + file)
-1296                elif len(left_idl) > 0:
-1297                    warnings.warn('Could not find idls ' + str(left_idl) + ' in replikum of file ' + file, UserWarning)
-1298        repnum += 1
-1299    s = "Read correlator " + corr + " from " + str(repnum) + " replika with idls" + str(realsamples[0][t])
-1300    for rep in range(1, repnum):
-1301        s += ", " + str(realsamples[rep][t])
-1302    print(s)
-1303    print("Asserted run parameters:\n T:", tmax, "kappa:", kappa, "csw:", csw, "dF:", dF, "zF:", zF, "bnd:", bnd)
-1304
-1305    # we have the data now... but we need to re format the whole thing and put it into Corr objects.
-1306
-1307    compObs = []
-1308
-1309    for t in range(int(len(tmpcorr) / 2)):
-1310        compObs.append(CObs(Obs([realsamples[rep][t] for rep in range(repnum)], names=names, idl=cnfgs),
-1311                            Obs([imagsamples[rep][t] for rep in range(repnum)], names=names, idl=cnfgs)))
+1201    # found = []
+1202    files = []
+1203    names = []
+1204
+1205    # test if the input is correct
+1206    if qc not in ['dd', 'ud', 'du', 'uu']:
+1207        raise Exception("Unknown quark conbination!")
+1208
+1209    if corr not in ["gS", "gP", "gA", "gV", "gVt", "lA", "lV", "lVt", "lT", "lTt", "g1", "l1"]:
+1210        raise Exception("Unknown correlator!")
+1211
+1212    if "files" in kwargs:
+1213        known_files = kwargs.get("files")
+1214    else:
+1215        known_files = []
+1216    files = _find_files(path, prefix, "ms5_xsf_" + qc, "dat", known_files=known_files)
+1217
+1218    if "names" in kwargs:
+1219        names = kwargs.get("names")
+1220    else:
+1221        for f in files:
+1222            if not sep == "":
+1223                se = f.split(".")[0]
+1224                for s in f.split(".")[1:-2]:
+1225                    se += "." + s
+1226                names.append(se.split(sep)[0] + "|r" + se.split(sep)[1])
+1227            else:
+1228                names.append(prefix)
+1229    if 'idl' in kwargs:
+1230        expected_idl = kwargs.get('idl')
+1231    names = sorted(names)
+1232    files = sorted(files)
+1233
+1234    cnfgs = []
+1235    realsamples = []
+1236    imagsamples = []
+1237    repnum = 0
+1238    for file in files:
+1239        with open(path + "/" + file, "rb") as fp:
+1240
+1241            t = fp.read(8)
+1242            kappa = struct.unpack('d', t)[0]
+1243            t = fp.read(8)
+1244            csw = struct.unpack('d', t)[0]
+1245            t = fp.read(8)
+1246            dF = struct.unpack('d', t)[0]
+1247            t = fp.read(8)
+1248            zF = struct.unpack('d', t)[0]
+1249
+1250            t = fp.read(4)
+1251            tmax = struct.unpack('i', t)[0]
+1252            t = fp.read(4)
+1253            bnd = struct.unpack('i', t)[0]
+1254
+1255            placesBI = ["gS", "gP",
+1256                        "gA", "gV",
+1257                        "gVt", "lA",
+1258                        "lV", "lVt",
+1259                        "lT", "lTt"]
+1260            placesBB = ["g1", "l1"]
+1261
+1262            # the chunks have the following structure:
+1263            # confignumber, 10x timedependent complex correlators as doubles, 2x timeindependent complex correlators as doubles
+1264
+1265            chunksize = 4 + (8 * 2 * tmax * 10) + (8 * 2 * 2)
+1266            packstr = '=i' + ('d' * 2 * tmax * 10) + ('d' * 2 * 2)
+1267            cnfgs.append([])
+1268            realsamples.append([])
+1269            imagsamples.append([])
+1270            for _ in range(tmax):
+1271                realsamples[repnum].append([])
+1272                imagsamples[repnum].append([])
+1273            if 'idl' in kwargs:
+1274                left_idl = set(expected_idl[repnum])
+1275            while True:
+1276                cnfgt = fp.read(chunksize)
+1277                if not cnfgt:
+1278                    break
+1279                asascii = struct.unpack(packstr, cnfgt)
+1280                cnfg = asascii[0]
+1281                idl_wanted = True
+1282                if 'idl' in kwargs:
+1283                    idl_wanted = (cnfg in expected_idl[repnum])
+1284                    left_idl = left_idl - set([cnfg])
+1285                if idl_wanted:
+1286                    cnfgs[repnum].append(cnfg)
+1287
+1288                    if corr not in placesBB:
+1289                        tmpcorr = asascii[1 + 2 * tmax * placesBI.index(corr):1 + 2 * tmax * placesBI.index(corr) + 2 * tmax]
+1290                    else:
+1291                        tmpcorr = asascii[1 + 2 * tmax * len(placesBI) + 2 * placesBB.index(corr):1 + 2 * tmax * len(placesBI) + 2 * placesBB.index(corr) + 2]
+1292
+1293                    corrres = [[], []]
+1294                    for i in range(len(tmpcorr)):
+1295                        corrres[i % 2].append(tmpcorr[i])
+1296                    for t in range(int(len(tmpcorr) / 2)):
+1297                        realsamples[repnum][t].append(corrres[0][t])
+1298                    for t in range(int(len(tmpcorr) / 2)):
+1299                        imagsamples[repnum][t].append(corrres[1][t])
+1300            if 'idl' in kwargs:
+1301                left_idl = list(left_idl)
+1302                if expected_idl[repnum] == left_idl:
+1303                    raise ValueError("None of the idls searched for were found in replikum of file " + file)
+1304                elif len(left_idl) > 0:
+1305                    warnings.warn('Could not find idls ' + str(left_idl) + ' in replikum of file ' + file, UserWarning, stacklevel=2)
+1306        repnum += 1
+1307    s = "Read correlator " + corr + " from " + str(repnum) + " replika with idls" + str(realsamples[0][t])
+1308    for rep in range(1, repnum):
+1309        s += ", " + str(realsamples[rep][t])
+1310    print(s)
+1311    print("Asserted run parameters:\n T:", tmax, "kappa:", kappa, "csw:", csw, "dF:", dF, "zF:", zF, "bnd:", bnd)
 1312
-1313    if len(compObs) == 1:
-1314        return compObs[0]
-1315    else:
-1316        return Corr(compObs)
+1313    # we have the data now... but we need to re format the whole thing and put it into Corr objects.
+1314
+1315    compObs = []
+1316
+1317    for t in range(int(len(tmpcorr) / 2)):
+1318        compObs.append(CObs(Obs([realsamples[rep][t] for rep in range(repnum)], names=names, idl=cnfgs),
+1319                            Obs([imagsamples[rep][t] for rep in range(repnum)], names=names, idl=cnfgs)))
+1320
+1321    if len(compObs) == 1:
+1322        return compObs[0]
+1323    else:
+1324        return Corr(compObs)
 
diff --git a/docs/pyerrors/input/pandas.html b/docs/pyerrors/input/pandas.html index a36419f1..a0386193 100644 --- a/docs/pyerrors/input/pandas.html +++ b/docs/pyerrors/input/pandas.html @@ -85,220 +85,222 @@ -
  1import warnings
-  2import gzip
-  3import sqlite3
+                        
  1import gzip
+  2import sqlite3
+  3import warnings
   4from contextlib import closing
-  5import pandas as pd
-  6from ..obs import Obs
-  7from ..correlators import Corr
-  8from .json import create_json_string, import_json_string
-  9import numpy as np
- 10
- 11
- 12def to_sql(df, table_name, db, if_exists='fail', gz=True, **kwargs):
- 13    """Write DataFrame including Obs or Corr valued columns to sqlite database.
- 14
- 15    Parameters
- 16    ----------
- 17    df : pandas.DataFrame
- 18        Dataframe to be written to the database.
- 19    table_name : str
- 20        Name of the table in the database.
- 21    db : str
- 22        Path to the sqlite database.
- 23    if exists : str
- 24        How to behave if table already exists. Options 'fail', 'replace', 'append'.
- 25    gz : bool
- 26        If True the json strings are gzipped.
- 27
- 28    Returns
- 29    -------
- 30    None
- 31    """
- 32    se_df = _serialize_df(df, gz=gz)
- 33    with closing(sqlite3.connect(db)) as con:
- 34        se_df.to_sql(table_name, con=con, if_exists=if_exists, index=False, **kwargs)
- 35
- 36
- 37def read_sql(sql, db, auto_gamma=False, **kwargs):
- 38    """Execute SQL query on sqlite database and obtain DataFrame including Obs or Corr valued columns.
- 39
- 40    Parameters
- 41    ----------
- 42    sql : str
- 43        SQL query to be executed.
- 44    db : str
- 45        Path to the sqlite database.
- 46    auto_gamma : bool
- 47        If True applies the gamma_method to all imported Obs objects with the default parameters for
- 48        the error analysis. Default False.
- 49
- 50    Returns
- 51    -------
- 52    data : pandas.DataFrame
- 53        Dataframe with the content of the sqlite database.
- 54    """
- 55    with closing(sqlite3.connect(db)) as con:
- 56        extract_df = pd.read_sql(sql, con=con, **kwargs)
- 57    return _deserialize_df(extract_df, auto_gamma=auto_gamma)
- 58
- 59
- 60def dump_df(df, fname, gz=True):
- 61    """Exports a pandas DataFrame containing Obs valued columns to a (gzipped) csv file.
- 62
- 63    Before making use of pandas to_csv functionality Obs objects are serialized via the standardized
- 64    json format of pyerrors.
- 65
- 66    Parameters
- 67    ----------
- 68    df : pandas.DataFrame
- 69        Dataframe to be dumped to a file.
- 70    fname : str
- 71        Filename of the output file.
- 72    gz : bool
- 73        If True, the output is a gzipped csv file. If False, the output is a csv file.
- 74
- 75    Returns
- 76    -------
- 77    None
- 78    """
- 79    for column in df:
- 80        serialize = _need_to_serialize(df[column])
- 81        if not serialize:
- 82            if all(isinstance(entry, (int, np.integer, float, np.floating)) for entry in df[column]):
- 83                if any([np.isnan(entry) for entry in df[column]]):
- 84                    warnings.warn("nan value in column " + column + " will be replaced by None", UserWarning)
- 85
- 86    out = _serialize_df(df, gz=False)
+  5
+  6import numpy as np
+  7import pandas as pd
+  8
+  9from ..correlators import Corr
+ 10from ..obs import Obs
+ 11from .json import create_json_string, import_json_string
+ 12
+ 13
+ 14def to_sql(df, table_name, db, if_exists='fail', gz=True, **kwargs):
+ 15    """Write DataFrame including Obs or Corr valued columns to sqlite database.
+ 16
+ 17    Parameters
+ 18    ----------
+ 19    df : pandas.DataFrame
+ 20        Dataframe to be written to the database.
+ 21    table_name : str
+ 22        Name of the table in the database.
+ 23    db : str
+ 24        Path to the sqlite database.
+ 25    if exists : str
+ 26        How to behave if table already exists. Options 'fail', 'replace', 'append'.
+ 27    gz : bool
+ 28        If True the json strings are gzipped.
+ 29
+ 30    Returns
+ 31    -------
+ 32    None
+ 33    """
+ 34    se_df = _serialize_df(df, gz=gz)
+ 35    with closing(sqlite3.connect(db)) as con:
+ 36        se_df.to_sql(table_name, con=con, if_exists=if_exists, index=False, **kwargs)
+ 37
+ 38
+ 39def read_sql(sql, db, auto_gamma=False, **kwargs):
+ 40    """Execute SQL query on sqlite database and obtain DataFrame including Obs or Corr valued columns.
+ 41
+ 42    Parameters
+ 43    ----------
+ 44    sql : str
+ 45        SQL query to be executed.
+ 46    db : str
+ 47        Path to the sqlite database.
+ 48    auto_gamma : bool
+ 49        If True applies the gamma_method to all imported Obs objects with the default parameters for
+ 50        the error analysis. Default False.
+ 51
+ 52    Returns
+ 53    -------
+ 54    data : pandas.DataFrame
+ 55        Dataframe with the content of the sqlite database.
+ 56    """
+ 57    with closing(sqlite3.connect(db)) as con:
+ 58        extract_df = pd.read_sql(sql, con=con, **kwargs)
+ 59    return _deserialize_df(extract_df, auto_gamma=auto_gamma)
+ 60
+ 61
+ 62def dump_df(df, fname, gz=True):
+ 63    """Exports a pandas DataFrame containing Obs valued columns to a (gzipped) csv file.
+ 64
+ 65    Before making use of pandas to_csv functionality Obs objects are serialized via the standardized
+ 66    json format of pyerrors.
+ 67
+ 68    Parameters
+ 69    ----------
+ 70    df : pandas.DataFrame
+ 71        Dataframe to be dumped to a file.
+ 72    fname : str
+ 73        Filename of the output file.
+ 74    gz : bool
+ 75        If True, the output is a gzipped csv file. If False, the output is a csv file.
+ 76
+ 77    Returns
+ 78    -------
+ 79    None
+ 80    """
+ 81    for column in df:
+ 82        serialize = _need_to_serialize(df[column])
+ 83        if not serialize:
+ 84            if all(isinstance(entry, (int, np.integer, float, np.floating)) for entry in df[column]):
+ 85                if any([np.isnan(entry) for entry in df[column]]):
+ 86                    warnings.warn("nan value in column " + column + " will be replaced by None", UserWarning, stacklevel=2)
  87
- 88    if not fname.endswith('.csv'):
- 89        fname += '.csv'
- 90
- 91    if gz is True:
- 92        if not fname.endswith('.gz'):
- 93            fname += '.gz'
- 94        out.to_csv(fname, index=False, compression='gzip')
- 95    else:
- 96        out.to_csv(fname, index=False)
- 97
- 98
- 99def load_df(fname, auto_gamma=False, gz=True):
-100    """Imports a pandas DataFrame from a csv.(gz) file in which Obs objects are serialized as json strings.
-101
-102    Parameters
-103    ----------
-104    fname : str
-105        Filename of the input file.
-106    auto_gamma : bool
-107        If True applies the gamma_method to all imported Obs objects with the default parameters for
-108        the error analysis. Default False.
-109    gz : bool
-110        If True, assumes that data is gzipped. If False, assumes JSON file.
-111
-112    Returns
-113    -------
-114    data : pandas.DataFrame
-115        Dataframe with the content of the sqlite database.
-116    """
-117    if not fname.endswith('.csv') and not fname.endswith('.gz'):
-118        fname += '.csv'
-119
-120    if gz is True:
-121        if not fname.endswith('.gz'):
-122            fname += '.gz'
-123        with gzip.open(fname) as f:
-124            re_import = pd.read_csv(f, keep_default_na=False)
-125    else:
-126        if fname.endswith('.gz'):
-127            warnings.warn("Trying to read from %s without unzipping!" % fname, UserWarning)
-128        re_import = pd.read_csv(fname, keep_default_na=False)
-129
-130    return _deserialize_df(re_import, auto_gamma=auto_gamma)
+ 88    out = _serialize_df(df, gz=False)
+ 89
+ 90    if not fname.endswith('.csv'):
+ 91        fname += '.csv'
+ 92
+ 93    if gz is True:
+ 94        if not fname.endswith('.gz'):
+ 95            fname += '.gz'
+ 96        out.to_csv(fname, index=False, compression='gzip')
+ 97    else:
+ 98        out.to_csv(fname, index=False)
+ 99
+100
+101def load_df(fname, auto_gamma=False, gz=True):
+102    """Imports a pandas DataFrame from a csv.(gz) file in which Obs objects are serialized as json strings.
+103
+104    Parameters
+105    ----------
+106    fname : str
+107        Filename of the input file.
+108    auto_gamma : bool
+109        If True applies the gamma_method to all imported Obs objects with the default parameters for
+110        the error analysis. Default False.
+111    gz : bool
+112        If True, assumes that data is gzipped. If False, assumes JSON file.
+113
+114    Returns
+115    -------
+116    data : pandas.DataFrame
+117        Dataframe with the content of the sqlite database.
+118    """
+119    if not fname.endswith('.csv') and not fname.endswith('.gz'):
+120        fname += '.csv'
+121
+122    if gz is True:
+123        if not fname.endswith('.gz'):
+124            fname += '.gz'
+125        with gzip.open(fname) as f:
+126            re_import = pd.read_csv(f, keep_default_na=False)
+127    else:
+128        if fname.endswith('.gz'):
+129            warnings.warn(f"Trying to read from {fname} without unzipping!", UserWarning, stacklevel=2)
+130        re_import = pd.read_csv(fname, keep_default_na=False)
 131
-132
-133def _serialize_df(df, gz=False):
-134    """Serializes all Obs or Corr valued columns into json strings according to the pyerrors json specification.
-135
-136    Parameters
-137    ----------
-138    df : pandas.DataFrame
-139        DataFrame to be serilized.
-140    gz: bool
-141        gzip the json string representation. Default False.
-142    """
-143    out = df.copy()
-144    for column in out:
-145        serialize = _need_to_serialize(out[column])
-146
-147        if serialize is True:
-148            out[column] = out[column].transform(lambda x: create_json_string(x, indent=0) if not _is_null(x) else None)
-149            if gz is True:
-150                out[column] = out[column].transform(lambda x: gzip.compress(x.encode('utf-8')) if not _is_null(x) else gzip.compress(b''))
-151    return out
-152
-153
-154def _deserialize_df(df, auto_gamma=False):
-155    """Deserializes all pyerrors json strings into Obs or Corr objects according to the pyerrors json specification.
-156
-157    Parameters
-158    ----------
-159    df : pandas.DataFrame
-160        DataFrame to be deserilized.
-161    auto_gamma : bool
-162        If True applies the gamma_method to all imported Obs objects with the default parameters for
-163        the error analysis. Default False.
-164
-165    Notes:
-166    ------
-167    In case any column of the DataFrame is gzipped it is gunzipped in the process.
-168    """
-169    # In pandas 3+, string columns use 'str' dtype instead of 'object'
-170    string_like_dtypes = ["object", "str"] if int(pd.__version__.split(".")[0]) >= 3 else ["object"]
-171    for column in df.select_dtypes(include=string_like_dtypes):
-172        if len(df[column]) == 0:
-173            continue
-174        if isinstance(df[column].iloc[0], bytes):
-175            if df[column].iloc[0].startswith(b"\x1f\x8b\x08\x00"):
-176                df[column] = df[column].transform(lambda x: gzip.decompress(x).decode('utf-8') if not pd.isna(x) else '')
-177
-178        if df[column].notna().any():
-179            df[column] = df[column].replace({r'^$': None}, regex=True)
-180            i = 0
-181            while i < len(df[column]) and pd.isna(df[column].iloc[i]):
-182                i += 1
-183            if i < len(df[column]) and isinstance(df[column].iloc[i], str):
-184                if '"program":' in df[column].iloc[i][:20]:
-185                    df[column] = df[column].transform(lambda x: import_json_string(x, verbose=False) if not pd.isna(x) else None)
-186                    if auto_gamma is True:
-187                        if isinstance(df[column].iloc[i], list):
-188                            df[column].apply(lambda x: [o.gm() if o is not None else x for o in x] if x is not None else x)
-189                        else:
-190                            df[column].apply(lambda x: x.gm() if x is not None else x)
-191        # Convert NA values back to Python None for compatibility with `x is None` checks
-192        if df[column].isna().any():
-193            df[column] = df[column].astype(object).where(df[column].notna(), None)
-194    return df
-195
-196
-197def _need_to_serialize(col):
-198    serialize = False
-199    i = 0
-200    while i < len(col) and _is_null(col.iloc[i]):
-201        i += 1
-202    if i == len(col):
-203        return serialize
-204    if isinstance(col.iloc[i], (Obs, Corr)):
-205        serialize = True
-206    elif isinstance(col.iloc[i], list):
-207        if all(isinstance(o, Obs) for o in col.iloc[i]):
-208            serialize = True
-209    return serialize
-210
-211
-212def _is_null(val):
-213    """Check if a value is null (None or NA), handling list/array values."""
-214    return False if isinstance(val, (list, np.ndarray)) else pd.isna(val)
+132    return _deserialize_df(re_import, auto_gamma=auto_gamma)
+133
+134
+135def _serialize_df(df, gz=False):
+136    """Serializes all Obs or Corr valued columns into json strings according to the pyerrors json specification.
+137
+138    Parameters
+139    ----------
+140    df : pandas.DataFrame
+141        DataFrame to be serilized.
+142    gz: bool
+143        gzip the json string representation. Default False.
+144    """
+145    out = df.copy()
+146    for column in out:
+147        serialize = _need_to_serialize(out[column])
+148
+149        if serialize is True:
+150            out[column] = out[column].transform(lambda x: create_json_string(x, indent=0) if not _is_null(x) else None)
+151            if gz is True:
+152                out[column] = out[column].transform(lambda x: gzip.compress(x.encode('utf-8')) if not _is_null(x) else gzip.compress(b''))
+153    return out
+154
+155
+156def _deserialize_df(df, auto_gamma=False):
+157    """Deserializes all pyerrors json strings into Obs or Corr objects according to the pyerrors json specification.
+158
+159    Parameters
+160    ----------
+161    df : pandas.DataFrame
+162        DataFrame to be deserilized.
+163    auto_gamma : bool
+164        If True applies the gamma_method to all imported Obs objects with the default parameters for
+165        the error analysis. Default False.
+166
+167    Notes:
+168    ------
+169    In case any column of the DataFrame is gzipped it is gunzipped in the process.
+170    """
+171    # In pandas 3+, string columns use 'str' dtype instead of 'object'
+172    string_like_dtypes = ["object", "str"] if int(pd.__version__.split(".")[0]) >= 3 else ["object"]
+173    for column in df.select_dtypes(include=string_like_dtypes):
+174        if len(df[column]) == 0:
+175            continue
+176        if isinstance(df[column].iloc[0], bytes):
+177            if df[column].iloc[0].startswith(b"\x1f\x8b\x08\x00"):
+178                df[column] = df[column].transform(lambda x: gzip.decompress(x).decode('utf-8') if not pd.isna(x) else '')
+179
+180        if df[column].notna().any():
+181            df[column] = df[column].replace({r'^$': None}, regex=True)
+182            i = 0
+183            while i < len(df[column]) and pd.isna(df[column].iloc[i]):
+184                i += 1
+185            if i < len(df[column]) and isinstance(df[column].iloc[i], str):
+186                if '"program":' in df[column].iloc[i][:20]:
+187                    df[column] = df[column].transform(lambda x: import_json_string(x, verbose=False) if not pd.isna(x) else None)
+188                    if auto_gamma is True:
+189                        if isinstance(df[column].iloc[i], list):
+190                            df[column].apply(lambda x: [o.gm() if o is not None else x for o in x] if x is not None else x)
+191                        else:
+192                            df[column].apply(lambda x: x.gm() if x is not None else x)
+193        # Convert NA values back to Python None for compatibility with `x is None` checks
+194        if df[column].isna().any():
+195            df[column] = df[column].astype(object).where(df[column].notna(), None)
+196    return df
+197
+198
+199def _need_to_serialize(col):
+200    serialize = False
+201    i = 0
+202    while i < len(col) and _is_null(col.iloc[i]):
+203        i += 1
+204    if i == len(col):
+205        return serialize
+206    if isinstance(col.iloc[i], (Obs, Corr)):
+207        serialize = True
+208    elif isinstance(col.iloc[i], list):
+209        if all(isinstance(o, Obs) for o in col.iloc[i]):
+210            serialize = True
+211    return serialize
+212
+213
+214def _is_null(val):
+215    """Check if a value is null (None or NA), handling list/array values."""
+216    return False if isinstance(val, (list, np.ndarray)) else pd.isna(val)
 
@@ -314,29 +316,29 @@
-
13def to_sql(df, table_name, db, if_exists='fail', gz=True, **kwargs):
-14    """Write DataFrame including Obs or Corr valued columns to sqlite database.
-15
-16    Parameters
-17    ----------
-18    df : pandas.DataFrame
-19        Dataframe to be written to the database.
-20    table_name : str
-21        Name of the table in the database.
-22    db : str
-23        Path to the sqlite database.
-24    if exists : str
-25        How to behave if table already exists. Options 'fail', 'replace', 'append'.
-26    gz : bool
-27        If True the json strings are gzipped.
-28
-29    Returns
-30    -------
-31    None
-32    """
-33    se_df = _serialize_df(df, gz=gz)
-34    with closing(sqlite3.connect(db)) as con:
-35        se_df.to_sql(table_name, con=con, if_exists=if_exists, index=False, **kwargs)
+            
15def to_sql(df, table_name, db, if_exists='fail', gz=True, **kwargs):
+16    """Write DataFrame including Obs or Corr valued columns to sqlite database.
+17
+18    Parameters
+19    ----------
+20    df : pandas.DataFrame
+21        Dataframe to be written to the database.
+22    table_name : str
+23        Name of the table in the database.
+24    db : str
+25        Path to the sqlite database.
+26    if exists : str
+27        How to behave if table already exists. Options 'fail', 'replace', 'append'.
+28    gz : bool
+29        If True the json strings are gzipped.
+30
+31    Returns
+32    -------
+33    None
+34    """
+35    se_df = _serialize_df(df, gz=gz)
+36    with closing(sqlite3.connect(db)) as con:
+37        se_df.to_sql(table_name, con=con, if_exists=if_exists, index=False, **kwargs)
 
@@ -377,27 +379,27 @@ If True the json strings are gzipped.
-
38def read_sql(sql, db, auto_gamma=False, **kwargs):
-39    """Execute SQL query on sqlite database and obtain DataFrame including Obs or Corr valued columns.
-40
-41    Parameters
-42    ----------
-43    sql : str
-44        SQL query to be executed.
-45    db : str
-46        Path to the sqlite database.
-47    auto_gamma : bool
-48        If True applies the gamma_method to all imported Obs objects with the default parameters for
-49        the error analysis. Default False.
-50
-51    Returns
-52    -------
-53    data : pandas.DataFrame
-54        Dataframe with the content of the sqlite database.
-55    """
-56    with closing(sqlite3.connect(db)) as con:
-57        extract_df = pd.read_sql(sql, con=con, **kwargs)
-58    return _deserialize_df(extract_df, auto_gamma=auto_gamma)
+            
40def read_sql(sql, db, auto_gamma=False, **kwargs):
+41    """Execute SQL query on sqlite database and obtain DataFrame including Obs or Corr valued columns.
+42
+43    Parameters
+44    ----------
+45    sql : str
+46        SQL query to be executed.
+47    db : str
+48        Path to the sqlite database.
+49    auto_gamma : bool
+50        If True applies the gamma_method to all imported Obs objects with the default parameters for
+51        the error analysis. Default False.
+52
+53    Returns
+54    -------
+55    data : pandas.DataFrame
+56        Dataframe with the content of the sqlite database.
+57    """
+58    with closing(sqlite3.connect(db)) as con:
+59        extract_df = pd.read_sql(sql, con=con, **kwargs)
+60    return _deserialize_df(extract_df, auto_gamma=auto_gamma)
 
@@ -436,43 +438,43 @@ Dataframe with the content of the sqlite database.
-
61def dump_df(df, fname, gz=True):
-62    """Exports a pandas DataFrame containing Obs valued columns to a (gzipped) csv file.
-63
-64    Before making use of pandas to_csv functionality Obs objects are serialized via the standardized
-65    json format of pyerrors.
-66
-67    Parameters
-68    ----------
-69    df : pandas.DataFrame
-70        Dataframe to be dumped to a file.
-71    fname : str
-72        Filename of the output file.
-73    gz : bool
-74        If True, the output is a gzipped csv file. If False, the output is a csv file.
-75
-76    Returns
-77    -------
-78    None
-79    """
-80    for column in df:
-81        serialize = _need_to_serialize(df[column])
-82        if not serialize:
-83            if all(isinstance(entry, (int, np.integer, float, np.floating)) for entry in df[column]):
-84                if any([np.isnan(entry) for entry in df[column]]):
-85                    warnings.warn("nan value in column " + column + " will be replaced by None", UserWarning)
-86
-87    out = _serialize_df(df, gz=False)
+            
63def dump_df(df, fname, gz=True):
+64    """Exports a pandas DataFrame containing Obs valued columns to a (gzipped) csv file.
+65
+66    Before making use of pandas to_csv functionality Obs objects are serialized via the standardized
+67    json format of pyerrors.
+68
+69    Parameters
+70    ----------
+71    df : pandas.DataFrame
+72        Dataframe to be dumped to a file.
+73    fname : str
+74        Filename of the output file.
+75    gz : bool
+76        If True, the output is a gzipped csv file. If False, the output is a csv file.
+77
+78    Returns
+79    -------
+80    None
+81    """
+82    for column in df:
+83        serialize = _need_to_serialize(df[column])
+84        if not serialize:
+85            if all(isinstance(entry, (int, np.integer, float, np.floating)) for entry in df[column]):
+86                if any([np.isnan(entry) for entry in df[column]]):
+87                    warnings.warn("nan value in column " + column + " will be replaced by None", UserWarning, stacklevel=2)
 88
-89    if not fname.endswith('.csv'):
-90        fname += '.csv'
-91
-92    if gz is True:
-93        if not fname.endswith('.gz'):
-94            fname += '.gz'
-95        out.to_csv(fname, index=False, compression='gzip')
-96    else:
-97        out.to_csv(fname, index=False)
+89    out = _serialize_df(df, gz=False)
+90
+91    if not fname.endswith('.csv'):
+92        fname += '.csv'
+93
+94    if gz is True:
+95        if not fname.endswith('.gz'):
+96            fname += '.gz'
+97        out.to_csv(fname, index=False, compression='gzip')
+98    else:
+99        out.to_csv(fname, index=False)
 
@@ -512,38 +514,38 @@ If True, the output is a gzipped csv file. If False, the output is a csv file. -
100def load_df(fname, auto_gamma=False, gz=True):
-101    """Imports a pandas DataFrame from a csv.(gz) file in which Obs objects are serialized as json strings.
-102
-103    Parameters
-104    ----------
-105    fname : str
-106        Filename of the input file.
-107    auto_gamma : bool
-108        If True applies the gamma_method to all imported Obs objects with the default parameters for
-109        the error analysis. Default False.
-110    gz : bool
-111        If True, assumes that data is gzipped. If False, assumes JSON file.
-112
-113    Returns
-114    -------
-115    data : pandas.DataFrame
-116        Dataframe with the content of the sqlite database.
-117    """
-118    if not fname.endswith('.csv') and not fname.endswith('.gz'):
-119        fname += '.csv'
-120
-121    if gz is True:
-122        if not fname.endswith('.gz'):
-123            fname += '.gz'
-124        with gzip.open(fname) as f:
-125            re_import = pd.read_csv(f, keep_default_na=False)
-126    else:
-127        if fname.endswith('.gz'):
-128            warnings.warn("Trying to read from %s without unzipping!" % fname, UserWarning)
-129        re_import = pd.read_csv(fname, keep_default_na=False)
-130
-131    return _deserialize_df(re_import, auto_gamma=auto_gamma)
+            
102def load_df(fname, auto_gamma=False, gz=True):
+103    """Imports a pandas DataFrame from a csv.(gz) file in which Obs objects are serialized as json strings.
+104
+105    Parameters
+106    ----------
+107    fname : str
+108        Filename of the input file.
+109    auto_gamma : bool
+110        If True applies the gamma_method to all imported Obs objects with the default parameters for
+111        the error analysis. Default False.
+112    gz : bool
+113        If True, assumes that data is gzipped. If False, assumes JSON file.
+114
+115    Returns
+116    -------
+117    data : pandas.DataFrame
+118        Dataframe with the content of the sqlite database.
+119    """
+120    if not fname.endswith('.csv') and not fname.endswith('.gz'):
+121        fname += '.csv'
+122
+123    if gz is True:
+124        if not fname.endswith('.gz'):
+125            fname += '.gz'
+126        with gzip.open(fname) as f:
+127            re_import = pd.read_csv(f, keep_default_na=False)
+128    else:
+129        if fname.endswith('.gz'):
+130            warnings.warn(f"Trying to read from {fname} without unzipping!", UserWarning, stacklevel=2)
+131        re_import = pd.read_csv(fname, keep_default_na=False)
+132
+133    return _deserialize_df(re_import, auto_gamma=auto_gamma)
 
diff --git a/docs/pyerrors/input/sfcf.html b/docs/pyerrors/input/sfcf.html index 5329b3a4..5a8675d2 100644 --- a/docs/pyerrors/input/sfcf.html +++ b/docs/pyerrors/input/sfcf.html @@ -82,738 +82,750 @@ -
  1import os
-  2import fnmatch
-  3import re
-  4import numpy as np  # Thinly-wrapped numpy
-  5from ..obs import Obs
-  6from .utils import sort_names, check_idl
-  7import itertools
-  8import warnings
-  9
- 10
- 11sep = "/"
- 12
+                        
  1import fnmatch
+  2import itertools
+  3import os
+  4import re
+  5import warnings
+  6
+  7import numpy as np  # Thinly-wrapped numpy
+  8
+  9from ..obs import Obs
+ 10from .utils import check_idl, sort_names
+ 11
+ 12sep = "/"
  13
- 14def read_sfcf(path, prefix, name, quarks='.*', corr_type="bi", noffset=0, wf=0, wf2=0, version="1.0c", cfg_separator="n", cfg_func=None, silent=False, **kwargs):
- 15    """Read sfcf files from given folder structure.
- 16
- 17    Parameters
- 18    ----------
- 19    path : str
- 20        Path to the sfcf files.
- 21    prefix : str
- 22        Prefix of the sfcf files.
- 23    name : str
- 24        Name of the correlation function to read.
- 25    quarks : str
- 26        Label of the quarks used in the sfcf input file. e.g. "quark quark"
- 27        for version 0.0 this does NOT need to be given with the typical " - "
- 28        that is present in the output file,
- 29        this is done automatically for this version
- 30    corr_type : str
- 31        Type of correlation function to read. Can be
- 32        - 'bi' for boundary-inner
- 33        - 'bb' for boundary-boundary
- 34        - 'bib' for boundary-inner-boundary
- 35    noffset : int
- 36        Offset of the source (only relevant when wavefunctions are used)
- 37    wf : int
- 38        ID of wave function
- 39    wf2 : int
- 40        ID of the second wavefunction
- 41        (only relevant for boundary-to-boundary correlation functions)
- 42    im : bool
- 43        if True, read imaginary instead of real part
- 44        of the correlation function.
- 45    names : list
- 46        Alternative labeling for replicas/ensembles.
- 47        Has to have the appropriate length
- 48    ens_name : str
- 49        replaces the name of the ensemble
- 50    version: str
- 51        version of SFCF, with which the measurement was done.
- 52        if the compact output option (-c) was specified,
- 53        append a "c" to the version (e.g. "1.0c")
- 54        if the append output option (-a) was specified,
- 55        append an "a" to the version
- 56    cfg_separator : str
- 57        String that separates the ensemble identifier from the configuration number (default 'n').
- 58    replica: list
- 59        list of replica to be read, default is all
- 60    files: list
- 61        list of files to be read per replica, default is all.
- 62        for non-compact output format, hand the folders to be read here.
- 63    check_configs: list[list[int]]
- 64        list of list of supposed configs, eg. [range(1,1000)]
- 65        for one replicum with 1000 configs
- 66
- 67    Returns
- 68    -------
- 69    result: list[Obs]
- 70        list of Observables with length T, observable per timeslice.
- 71        bb-type correlators have length 1.
- 72    """
- 73    ret = read_sfcf_multi(path, prefix, [name], quarks_list=[quarks], corr_type_list=[corr_type],
- 74                          noffset_list=[noffset], wf_list=[wf], wf2_list=[wf2], version=version,
- 75                          cfg_separator=cfg_separator, cfg_func=cfg_func, silent=silent, **kwargs)
- 76    return ret[name][quarks][str(noffset)][str(wf)][str(wf2)]
- 77
+ 14
+ 15def read_sfcf(path, prefix, name, quarks='.*', corr_type="bi", noffset=0, wf=0, wf2=0, version="1.0c", cfg_separator="n", cfg_func=None, silent=False, **kwargs):
+ 16    """Read sfcf files from given folder structure.
+ 17
+ 18    Parameters
+ 19    ----------
+ 20    path : str
+ 21        Path to the sfcf files.
+ 22    prefix : str
+ 23        Prefix of the sfcf files.
+ 24    name : str
+ 25        Name of the correlation function to read.
+ 26    quarks : str
+ 27        Label of the quarks used in the sfcf input file. e.g. "quark quark"
+ 28        for version 0.0 this does NOT need to be given with the typical " - "
+ 29        that is present in the output file,
+ 30        this is done automatically for this version
+ 31    corr_type : str
+ 32        Type of correlation function to read. Can be
+ 33        - 'bi' for boundary-inner
+ 34        - 'bb' for boundary-boundary
+ 35        - 'bib' for boundary-inner-boundary
+ 36    noffset : int
+ 37        Offset of the source (only relevant when wavefunctions are used)
+ 38    wf : int
+ 39        ID of wave function
+ 40    wf2 : int
+ 41        ID of the second wavefunction
+ 42        (only relevant for boundary-to-boundary correlation functions)
+ 43    im : bool
+ 44        if True, read imaginary instead of real part
+ 45        of the correlation function.
+ 46    names : list
+ 47        Alternative labeling for replicas/ensembles.
+ 48        Has to have the appropriate length
+ 49    ens_name : str
+ 50        replaces the name of the ensemble
+ 51    version: str
+ 52        version of SFCF, with which the measurement was done.
+ 53        if the compact output option (-c) was specified,
+ 54        append a "c" to the version (e.g. "1.0c")
+ 55        if the append output option (-a) was specified,
+ 56        append an "a" to the version
+ 57    cfg_separator : str
+ 58        String that separates the ensemble identifier from the configuration number (default 'n').
+ 59    replica: list
+ 60        list of replica to be read, default is all
+ 61    files: list
+ 62        list of files to be read per replica, default is all.
+ 63        for non-compact output format, hand the folders to be read here.
+ 64    check_configs: list[list[int]]
+ 65        list of list of supposed configs, eg. [range(1,1000)]
+ 66        for one replicum with 1000 configs
+ 67
+ 68    Returns
+ 69    -------
+ 70    result: list[Obs]
+ 71        list of Observables with length T, observable per timeslice.
+ 72        bb-type correlators have length 1.
+ 73    """
+ 74    ret = read_sfcf_multi(path, prefix, [name], quarks_list=[quarks], corr_type_list=[corr_type],
+ 75                          noffset_list=[noffset], wf_list=[wf], wf2_list=[wf2], version=version,
+ 76                          cfg_separator=cfg_separator, cfg_func=cfg_func, silent=silent, **kwargs)
+ 77    return ret[name][quarks][str(noffset)][str(wf)][str(wf2)]
  78
- 79def read_sfcf_multi(path, prefix, name_list, quarks_list=['.*'], corr_type_list=['bi'], noffset_list=[0], wf_list=[0], wf2_list=[0], version="1.0c", cfg_separator="n", cfg_func=None, silent=False, keyed_out=False, **kwargs):
- 80    """Read sfcf files from given folder structure.
- 81
- 82    Parameters
- 83    ----------
- 84    path : str
- 85        Path to the sfcf files.
- 86    prefix : str
- 87        Prefix of the sfcf files.
- 88    name : str
- 89        Name of the correlation function to read.
- 90    quarks_list : list[str]
- 91        Label of the quarks used in the sfcf input file. e.g. "quark quark"
- 92        for version 0.0 this does NOT need to be given with the typical " - "
- 93        that is present in the output file,
- 94        this is done automatically for this version
- 95    corr_type_list : list[str]
- 96        Type of correlation function to read. Can be
- 97        - 'bi' for boundary-inner
- 98        - 'bb' for boundary-boundary
- 99        - 'bib' for boundary-inner-boundary
-100    noffset_list : list[int]
-101        Offset of the source (only relevant when wavefunctions are used)
-102    wf_list : int
-103        ID of wave function
-104    wf2_list : list[int]
-105        ID of the second wavefunction
-106        (only relevant for boundary-to-boundary correlation functions)
-107    im : bool
-108        if True, read imaginary instead of real part
-109        of the correlation function.
-110    names : list
-111        Alternative labeling for replicas/ensembles.
-112        Has to have the appropriate length
-113    ens_name : str
-114        replaces the name of the ensemble
-115    version: str
-116        version of SFCF, with which the measurement was done.
-117        if the compact output option (-c) was specified,
-118        append a "c" to the version (e.g. "1.0c")
-119        if the append output option (-a) was specified,
-120        append an "a" to the version
-121    cfg_separator : str
-122        String that separates the ensemble identifier from the configuration number (default 'n').
-123    replica: list
-124        list of replica to be read, default is all
-125    files: list[list[int]]
-126        list of files to be read per replica, default is all.
-127        for non-compact output format, hand the folders to be read here.
-128    check_configs: list[list[int]]
-129        list of list of supposed configs, eg. [range(1,1000)]
-130        for one replicum with 1000 configs
-131    rep_string: str
-132        Separator of ensemble name and replicum. Example: In "ensAr0", "r" would be the separator string.
-133    Returns
-134    -------
-135    result: dict[list[Obs]]
-136        dict with one of the following properties:
-137        if keyed_out:
-138            dict[key] = list[Obs]
-139            where key has the form name/quarks/offset/wf/wf2
-140        if not keyed_out:
-141            dict[name][quarks][offset][wf][wf2] = list[Obs]
-142    """
-143
-144    if kwargs.get('im'):
-145        im = 1
-146        part = 'imaginary'
-147    else:
-148        im = 0
-149        part = 'real'
-150
-151    known_versions = ["0.0", "1.0", "2.0", "1.0c", "2.0c", "1.0a", "2.0a"]
-152
-153    if version not in known_versions:
-154        raise Exception("This version is not known!")
-155    if (version[-1] == "c"):
-156        appended = False
-157        compact = True
-158        version = version[:-1]
-159    elif (version[-1] == "a"):
-160        appended = True
-161        compact = False
-162        version = version[:-1]
-163    else:
-164        compact = False
-165        appended = False
-166    ls = []
-167    if "replica" in kwargs:
-168        ls = kwargs.get("replica")
-169    else:
-170        for (dirpath, dirnames, filenames) in os.walk(path):
-171            if not appended:
-172                ls.extend(dirnames)
-173            else:
-174                ls.extend(filenames)
-175            break
-176        if not ls:
-177            raise Exception('Error, directory not found')
-178        # Exclude folders with different names
-179        for exc in ls:
-180            if not fnmatch.fnmatch(exc, prefix + '*'):
-181                ls = list(set(ls) - set([exc]))
-182
-183    if not appended:
-184        ls = sort_names(ls)
-185        replica = len(ls)
-186
-187    else:
-188        replica = len([file.split(".")[-1] for file in ls]) // len(set([file.split(".")[-1] for file in ls]))
-189    if replica == 0:
-190        raise Exception('No replica found in directory')
-191    if not silent:
-192        print('Read', part, 'part of', name_list, 'from', prefix[:-1], ',', replica, 'replica')
-193
-194    if 'names' in kwargs:
-195        new_names = kwargs.get('names')
-196        if len(new_names) != len(set(new_names)):
-197            raise Exception("names are not unique!")
-198        if len(new_names) != replica:
-199            raise Exception('names should have the length', replica)
-200
-201    else:
-202        ens_name = kwargs.get("ens_name")
-203        if not appended:
-204            new_names = _get_rep_names(ls, ens_name, rep_sep=(kwargs.get('rep_string', 'r')))
-205        else:
-206            new_names = _get_appended_rep_names(ls, prefix, name_list[0], ens_name, rep_sep=(kwargs.get('rep_string', 'r')))
-207        new_names = sort_names(new_names)
-208
-209    idl = []
-210
-211    noffset_list = [str(x) for x in noffset_list]
-212    wf_list = [str(x) for x in wf_list]
-213    wf2_list = [str(x) for x in wf2_list]
-214
-215    # setup dict structures
-216    intern = {}
-217    for name, corr_type in zip(name_list, corr_type_list):
-218        intern[name] = {}
-219        b2b, single = _extract_corr_type(corr_type)
-220        intern[name]["b2b"] = b2b
-221        intern[name]["single"] = single
-222        intern[name]["spec"] = {}
-223        for quarks in quarks_list:
-224            intern[name]["spec"][quarks] = {}
-225            for off in noffset_list:
-226                intern[name]["spec"][quarks][off] = {}
-227                for w in wf_list:
-228                    intern[name]["spec"][quarks][off][w] = {}
-229                    if b2b:
-230                        for w2 in wf2_list:
-231                            intern[name]["spec"][quarks][off][w][w2] = {}
-232                            intern[name]["spec"][quarks][off][w][w2]["pattern"] = _make_pattern(version, name, off, w, w2, intern[name]['b2b'], quarks)
-233                    else:
-234                        intern[name]["spec"][quarks][off][w]["0"] = {}
-235                        intern[name]["spec"][quarks][off][w]["0"]["pattern"] = _make_pattern(version, name, off, w, 0, intern[name]['b2b'], quarks)
-236
-237    internal_ret_dict = {}
-238    needed_keys = []
-239    for name, corr_type in zip(name_list, corr_type_list):
-240        b2b, single = _extract_corr_type(corr_type)
-241        if b2b:
-242            needed_keys.extend(_lists2key([name], quarks_list, noffset_list, wf_list, wf2_list))
-243        else:
-244            needed_keys.extend(_lists2key([name], quarks_list, noffset_list, wf_list, ["0"]))
-245
-246    for key in needed_keys:
-247        internal_ret_dict[key] = []
+ 79
+ 80def read_sfcf_multi(path, prefix, name_list, quarks_list=None, corr_type_list=None, noffset_list=None, wf_list=None, wf2_list=None, version="1.0c", cfg_separator="n", cfg_func=None, silent=False, keyed_out=False, **kwargs):
+ 81    """Read sfcf files from given folder structure.
+ 82
+ 83    Parameters
+ 84    ----------
+ 85    path : str
+ 86        Path to the sfcf files.
+ 87    prefix : str
+ 88        Prefix of the sfcf files.
+ 89    name : str
+ 90        Name of the correlation function to read.
+ 91    quarks_list : list[str]
+ 92        Label of the quarks used in the sfcf input file. e.g. "quark quark"
+ 93        for version 0.0 this does NOT need to be given with the typical " - "
+ 94        that is present in the output file,
+ 95        this is done automatically for this version
+ 96    corr_type_list : list[str]
+ 97        Type of correlation function to read. Can be
+ 98        - 'bi' for boundary-inner
+ 99        - 'bb' for boundary-boundary
+100        - 'bib' for boundary-inner-boundary
+101    noffset_list : list[int]
+102        Offset of the source (only relevant when wavefunctions are used)
+103    wf_list : int
+104        ID of wave function
+105    wf2_list : list[int]
+106        ID of the second wavefunction
+107        (only relevant for boundary-to-boundary correlation functions)
+108    im : bool
+109        if True, read imaginary instead of real part
+110        of the correlation function.
+111    names : list
+112        Alternative labeling for replicas/ensembles.
+113        Has to have the appropriate length
+114    ens_name : str
+115        replaces the name of the ensemble
+116    version: str
+117        version of SFCF, with which the measurement was done.
+118        if the compact output option (-c) was specified,
+119        append a "c" to the version (e.g. "1.0c")
+120        if the append output option (-a) was specified,
+121        append an "a" to the version
+122    cfg_separator : str
+123        String that separates the ensemble identifier from the configuration number (default 'n').
+124    replica: list
+125        list of replica to be read, default is all
+126    files: list[list[int]]
+127        list of files to be read per replica, default is all.
+128        for non-compact output format, hand the folders to be read here.
+129    check_configs: list[list[int]]
+130        list of list of supposed configs, eg. [range(1,1000)]
+131        for one replicum with 1000 configs
+132    rep_string: str
+133        Separator of ensemble name and replicum. Example: In "ensAr0", "r" would be the separator string.
+134    Returns
+135    -------
+136    result: dict[list[Obs]]
+137        dict with one of the following properties:
+138        if keyed_out:
+139            dict[key] = list[Obs]
+140            where key has the form name/quarks/offset/wf/wf2
+141        if not keyed_out:
+142            dict[name][quarks][offset][wf][wf2] = list[Obs]
+143    """
+144
+145    if quarks_list is None:
+146        quarks_list = ['.*']
+147    if corr_type_list is None:
+148        corr_type_list = ['bi']
+149    if noffset_list is None:
+150        noffset_list = [0]
+151    if wf_list is None:
+152        wf_list = [0]
+153    if wf2_list is None:
+154        wf2_list = [0]
+155
+156    if kwargs.get('im'):
+157        im = 1
+158        part = 'imaginary'
+159    else:
+160        im = 0
+161        part = 'real'
+162
+163    known_versions = ["0.0", "1.0", "2.0", "1.0c", "2.0c", "1.0a", "2.0a"]
+164
+165    if version not in known_versions:
+166        raise Exception("This version is not known!")
+167    if (version[-1] == "c"):
+168        appended = False
+169        compact = True
+170        version = version[:-1]
+171    elif (version[-1] == "a"):
+172        appended = True
+173        compact = False
+174        version = version[:-1]
+175    else:
+176        compact = False
+177        appended = False
+178    ls = []
+179    if "replica" in kwargs:
+180        ls = kwargs.get("replica")
+181    else:
+182        for (_dirpath, dirnames, filenames) in os.walk(path):
+183            if not appended:
+184                ls.extend(dirnames)
+185            else:
+186                ls.extend(filenames)
+187            break
+188        if not ls:
+189            raise Exception('Error, directory not found')
+190        # Exclude folders with different names
+191        for exc in ls:
+192            if not fnmatch.fnmatch(exc, prefix + '*'):
+193                ls = list(set(ls) - set([exc]))
+194
+195    if not appended:
+196        ls = sort_names(ls)
+197        replica = len(ls)
+198
+199    else:
+200        replica = len([file.split(".")[-1] for file in ls]) // len(set([file.split(".")[-1] for file in ls]))
+201    if replica == 0:
+202        raise Exception('No replica found in directory')
+203    if not silent:
+204        print('Read', part, 'part of', name_list, 'from', prefix[:-1], ',', replica, 'replica')
+205
+206    if 'names' in kwargs:
+207        new_names = kwargs.get('names')
+208        if len(new_names) != len(set(new_names)):
+209            raise Exception("names are not unique!")
+210        if len(new_names) != replica:
+211            raise Exception('names should have the length', replica)
+212
+213    else:
+214        ens_name = kwargs.get("ens_name")
+215        if not appended:
+216            new_names = _get_rep_names(ls, ens_name, rep_sep=(kwargs.get('rep_string', 'r')))
+217        else:
+218            new_names = _get_appended_rep_names(ls, prefix, name_list[0], ens_name, rep_sep=(kwargs.get('rep_string', 'r')))
+219        new_names = sort_names(new_names)
+220
+221    idl = []
+222
+223    noffset_list = [str(x) for x in noffset_list]
+224    wf_list = [str(x) for x in wf_list]
+225    wf2_list = [str(x) for x in wf2_list]
+226
+227    # setup dict structures
+228    intern = {}
+229    for name, corr_type in zip(name_list, corr_type_list, strict=True):
+230        intern[name] = {}
+231        b2b, single = _extract_corr_type(corr_type)
+232        intern[name]["b2b"] = b2b
+233        intern[name]["single"] = single
+234        intern[name]["spec"] = {}
+235        for quarks in quarks_list:
+236            intern[name]["spec"][quarks] = {}
+237            for off in noffset_list:
+238                intern[name]["spec"][quarks][off] = {}
+239                for w in wf_list:
+240                    intern[name]["spec"][quarks][off][w] = {}
+241                    if b2b:
+242                        for w2 in wf2_list:
+243                            intern[name]["spec"][quarks][off][w][w2] = {}
+244                            intern[name]["spec"][quarks][off][w][w2]["pattern"] = _make_pattern(version, name, off, w, w2, intern[name]['b2b'], quarks)
+245                    else:
+246                        intern[name]["spec"][quarks][off][w]["0"] = {}
+247                        intern[name]["spec"][quarks][off][w]["0"]["pattern"] = _make_pattern(version, name, off, w, 0, intern[name]['b2b'], quarks)
 248
-249    def _default_idl_func(cfg_string, cfg_sep):
-250        return int(cfg_string.split(cfg_sep)[-1])
-251
-252    if cfg_func is None:
-253        print("Default idl function in use.")
-254        cfg_func = _default_idl_func
-255        cfg_func_args = [cfg_separator]
-256    else:
-257        cfg_func_args = kwargs.get("cfg_func_args", [])
-258
-259    if not appended:
-260        for i, item in enumerate(ls):
-261            rep_path = path + '/' + item
-262            if "files" in kwargs:
-263                files = kwargs.get("files")
-264                if isinstance(files, list):
-265                    if all(isinstance(f, list) for f in files):
-266                        files = files[i]
-267                    elif all(isinstance(f, str) for f in files):
-268                        files = files
-269                    else:
-270                        raise TypeError("files has to be of type list[list[str]] or list[str]!")
-271                else:
-272                    raise TypeError("files has to be of type list[list[str]] or list[str]!")
-273
-274            else:
-275                files = []
-276            sub_ls = _find_files(rep_path, prefix, compact, files)
-277            rep_idl = []
-278            no_cfg = len(sub_ls)
-279            for cfg in sub_ls:
-280                try:
-281                    if compact:
-282                        rep_idl.append(cfg_func(cfg, *cfg_func_args))
-283                    else:
-284                        rep_idl.append(int(cfg[3:]))
-285                except Exception:
-286                    raise Exception("Couldn't parse idl from directory, problem with file " + cfg)
-287            rep_idl.sort()
-288            # maybe there is a better way to print the idls
-289            if not silent:
-290                print(item, ':', no_cfg, ' configurations')
-291            idl.append(rep_idl)
-292            # here we have found all the files we need to look into.
-293            if i == 0:
-294                if version != "0.0" and compact:
-295                    file = path + '/' + item + '/' + sub_ls[0]
-296                for name_index, name in enumerate(name_list):
-297                    if version == "0.0" or not compact:
-298                        file = path + '/' + item + '/' + sub_ls[0] + '/' + name
-299                    if corr_type_list[name_index] == 'bi':
-300                        name_keys = _lists2key(quarks_list, noffset_list, wf_list, ["0"])
-301                    else:
-302                        name_keys = _lists2key(quarks_list, noffset_list, wf_list, wf2_list)
-303                    for key in name_keys:
-304                        specs = _key2specs(key)
-305                        quarks = specs[0]
-306                        off = specs[1]
-307                        w = specs[2]
-308                        w2 = specs[3]
-309                        # here, we want to find the place within the file,
-310                        # where the correlator we need is stored.
-311                        # to do so, the pattern needed is put together
-312                        # from the input values
-313                        start_read, T = _find_correlator(file, version, intern[name]["spec"][quarks][str(off)][str(w)][str(w2)]["pattern"], intern[name]['b2b'], silent=silent)
-314                        intern[name]["spec"][quarks][str(off)][str(w)][str(w2)]["start"] = start_read
-315                        intern[name]["T"] = T
-316                        # preparing the datastructure
-317                        # the correlators get parsed into...
-318                        deltas = []
-319                        for j in range(intern[name]["T"]):
-320                            deltas.append([])
-321                        internal_ret_dict[sep.join([name, key])] = deltas
-322
-323            if compact:
-324                rep_deltas = _read_compact_rep(path, item, sub_ls, intern, needed_keys, im)
-325                for key in needed_keys:
-326                    name = _key2specs(key)[0]
-327                    for t in range(intern[name]["T"]):
-328                        internal_ret_dict[key][t].append(rep_deltas[key][t])
-329            else:
-330                for key in needed_keys:
-331                    rep_data = []
-332                    name = _key2specs(key)[0]
-333                    for subitem in sub_ls:
-334                        cfg_path = path + '/' + item + '/' + subitem
-335                        file_data = _read_o_file(cfg_path, name, needed_keys, intern, version, im)
-336                        rep_data.append(file_data)
+249    internal_ret_dict = {}
+250    needed_keys = []
+251    for name, corr_type in zip(name_list, corr_type_list, strict=True):
+252        b2b, single = _extract_corr_type(corr_type)
+253        if b2b:
+254            needed_keys.extend(_lists2key([name], quarks_list, noffset_list, wf_list, wf2_list))
+255        else:
+256            needed_keys.extend(_lists2key([name], quarks_list, noffset_list, wf_list, ["0"]))
+257
+258    for key in needed_keys:
+259        internal_ret_dict[key] = []
+260
+261    def _default_idl_func(cfg_string, cfg_sep):
+262        return int(cfg_string.split(cfg_sep)[-1])
+263
+264    if cfg_func is None:
+265        print("Default idl function in use.")
+266        cfg_func = _default_idl_func
+267        cfg_func_args = [cfg_separator]
+268    else:
+269        cfg_func_args = kwargs.get("cfg_func_args", [])
+270
+271    if not appended:
+272        for i, item in enumerate(ls):
+273            rep_path = path + '/' + item
+274            if "files" in kwargs:
+275                files = kwargs.get("files")
+276                if isinstance(files, list):
+277                    if all(isinstance(f, list) for f in files):
+278                        files = files[i]
+279                    elif not all(isinstance(f, str) for f in files):
+280                        raise TypeError("files has to be of type list[list[str]] or list[str]!")
+281                else:
+282                    raise TypeError("files has to be of type list[list[str]] or list[str]!")
+283
+284            else:
+285                files = []
+286            sub_ls = _find_files(rep_path, prefix, compact, files)
+287            rep_idl = []
+288            no_cfg = len(sub_ls)
+289            for cfg in sub_ls:
+290                try:
+291                    if compact:
+292                        rep_idl.append(cfg_func(cfg, *cfg_func_args))
+293                    else:
+294                        rep_idl.append(int(cfg[3:]))
+295                except Exception as err:
+296                    raise Exception("Couldn't parse idl from directory, problem with file " + cfg) from err
+297            rep_idl.sort()
+298            # maybe there is a better way to print the idls
+299            if not silent:
+300                print(item, ':', no_cfg, ' configurations')
+301            idl.append(rep_idl)
+302            # here we have found all the files we need to look into.
+303            if i == 0:
+304                if version != "0.0" and compact:
+305                    file = path + '/' + item + '/' + sub_ls[0]
+306                for name_index, name in enumerate(name_list):
+307                    if version == "0.0" or not compact:
+308                        file = path + '/' + item + '/' + sub_ls[0] + '/' + name
+309                    if corr_type_list[name_index] == 'bi':
+310                        name_keys = _lists2key(quarks_list, noffset_list, wf_list, ["0"])
+311                    else:
+312                        name_keys = _lists2key(quarks_list, noffset_list, wf_list, wf2_list)
+313                    for key in name_keys:
+314                        specs = _key2specs(key)
+315                        quarks = specs[0]
+316                        off = specs[1]
+317                        w = specs[2]
+318                        w2 = specs[3]
+319                        # here, we want to find the place within the file,
+320                        # where the correlator we need is stored.
+321                        # to do so, the pattern needed is put together
+322                        # from the input values
+323                        start_read, T = _find_correlator(file, version, intern[name]["spec"][quarks][str(off)][str(w)][str(w2)]["pattern"], intern[name]['b2b'], silent=silent)
+324                        intern[name]["spec"][quarks][str(off)][str(w)][str(w2)]["start"] = start_read
+325                        intern[name]["T"] = T
+326                        # preparing the datastructure
+327                        # the correlators get parsed into...
+328                        deltas = []
+329                        for _j in range(intern[name]["T"]):
+330                            deltas.append([])
+331                        internal_ret_dict[sep.join([name, key])] = deltas
+332
+333            if compact:
+334                rep_deltas = _read_compact_rep(path, item, sub_ls, intern, needed_keys, im)
+335                for key in needed_keys:
+336                    name = _key2specs(key)[0]
 337                    for t in range(intern[name]["T"]):
-338                        internal_ret_dict[key][t].append([])
-339                        for cfg in range(no_cfg):
-340                            internal_ret_dict[key][t][i].append(rep_data[cfg][key][t])
-341    else:
-342        for key in needed_keys:
-343            specs = _key2specs(key)
-344            name = specs[0]
-345            quarks = specs[1]
-346            off = specs[2]
-347            w = specs[3]
-348            w2 = specs[4]
-349            if "files" in kwargs:
-350                if isinstance(kwargs.get("files"), list) and all(isinstance(f, str) for f in kwargs.get("files")):
-351                    name_ls = kwargs.get("files")
-352                else:
-353                    raise TypeError("In append mode, files has to be of type list[str]!")
-354            else:
-355                name_ls = ls
-356                for exc in name_ls:
-357                    if not fnmatch.fnmatch(exc, prefix + '*.' + name):
-358                        name_ls = list(set(name_ls) - set([exc]))
-359            name_ls = sort_names(name_ls)
-360            pattern = intern[name]['spec'][quarks][off][w][w2]['pattern']
-361            deltas = []
-362            for rep, file in enumerate(name_ls):
-363                rep_idl = []
-364                filename = path + '/' + file
-365                T, rep_idl, rep_data = _read_append_rep(filename, pattern, intern[name]['b2b'], im, intern[name]['single'], cfg_func, cfg_func_args)
-366                if rep == 0:
-367                    intern[name]['T'] = T
-368                    for t in range(intern[name]['T']):
-369                        deltas.append([])
-370                for t in range(intern[name]['T']):
-371                    deltas[t].append(rep_data[t])
-372                internal_ret_dict[key] = deltas
-373                if name == name_list[0]:
-374                    idl.append(rep_idl)
-375
-376    if kwargs.get("check_configs") is True:
-377        if not silent:
-378            print("Checking for missing configs...")
-379        che = kwargs.get("check_configs")
-380        if not (len(che) == len(idl)):
-381            raise Exception("check_configs has to be the same length as replica!")
-382        for r in range(len(idl)):
-383            if not silent:
-384                print("checking " + new_names[r])
-385            check_idl(idl[r], che[r])
-386        if not silent:
-387            print("Done")
-388
-389    result_dict = {}
-390    if keyed_out:
-391        for key in needed_keys:
-392            name = _key2specs(key)[0]
-393            result = []
-394            for t in range(intern[name]["T"]):
-395                result.append(Obs(internal_ret_dict[key][t], new_names, idl=idl))
-396            result_dict[key] = result
-397    else:
-398        for name, corr_type in zip(name_list, corr_type_list):
-399            result_dict[name] = {}
-400            for quarks in quarks_list:
-401                result_dict[name][quarks] = {}
-402                for off in noffset_list:
-403                    result_dict[name][quarks][off] = {}
-404                    for w in wf_list:
-405                        result_dict[name][quarks][off][w] = {}
-406                        if corr_type != 'bi':
-407                            for w2 in wf2_list:
-408                                key = _specs2key(name, quarks, off, w, w2)
-409                                result = []
-410                                for t in range(intern[name]["T"]):
-411                                    result.append(Obs(internal_ret_dict[key][t], new_names, idl=idl))
-412                                result_dict[name][quarks][str(off)][str(w)][str(w2)] = result
-413                        else:
-414                            key = _specs2key(name, quarks, off, w, "0")
-415                            result = []
-416                            for t in range(intern[name]["T"]):
-417                                result.append(Obs(internal_ret_dict[key][t], new_names, idl=idl))
-418                            result_dict[name][quarks][str(off)][str(w)][str(0)] = result
-419    return result_dict
-420
-421
-422def _lists2key(*lists):
-423    keys = []
-424    for tup in itertools.product(*lists):
-425        keys.append(sep.join(tup))
-426    return keys
-427
-428
-429def _key2specs(key):
-430    return key.split(sep)
+338                        internal_ret_dict[key][t].append(rep_deltas[key][t])
+339            else:
+340                for key in needed_keys:
+341                    rep_data = []
+342                    name = _key2specs(key)[0]
+343                    for subitem in sub_ls:
+344                        cfg_path = path + '/' + item + '/' + subitem
+345                        file_data = _read_o_file(cfg_path, name, needed_keys, intern, version, im)
+346                        rep_data.append(file_data)
+347                    for t in range(intern[name]["T"]):
+348                        internal_ret_dict[key][t].append([])
+349                        for cfg in range(no_cfg):
+350                            internal_ret_dict[key][t][i].append(rep_data[cfg][key][t])
+351    else:
+352        for key in needed_keys:
+353            specs = _key2specs(key)
+354            name = specs[0]
+355            quarks = specs[1]
+356            off = specs[2]
+357            w = specs[3]
+358            w2 = specs[4]
+359            if "files" in kwargs:
+360                if isinstance(kwargs.get("files"), list) and all(isinstance(f, str) for f in kwargs.get("files")):
+361                    name_ls = kwargs.get("files")
+362                else:
+363                    raise TypeError("In append mode, files has to be of type list[str]!")
+364            else:
+365                name_ls = ls
+366                for exc in name_ls:
+367                    if not fnmatch.fnmatch(exc, prefix + '*.' + name):
+368                        name_ls = list(set(name_ls) - set([exc]))
+369            name_ls = sort_names(name_ls)
+370            pattern = intern[name]['spec'][quarks][off][w][w2]['pattern']
+371            deltas = []
+372            for rep, file in enumerate(name_ls):
+373                rep_idl = []
+374                filename = path + '/' + file
+375                T, rep_idl, rep_data = _read_append_rep(filename, pattern, intern[name]['b2b'], im, intern[name]['single'], cfg_func, cfg_func_args)
+376                if rep == 0:
+377                    intern[name]['T'] = T
+378                    for _ in range(intern[name]['T']):
+379                        deltas.append([])
+380                for t in range(intern[name]['T']):
+381                    deltas[t].append(rep_data[t])
+382                internal_ret_dict[key] = deltas
+383                if name == name_list[0]:
+384                    idl.append(rep_idl)
+385
+386    if kwargs.get("check_configs") is True:
+387        if not silent:
+388            print("Checking for missing configs...")
+389        che = kwargs.get("check_configs")
+390        if not (len(che) == len(idl)):
+391            raise Exception("check_configs has to be the same length as replica!")
+392        for r in range(len(idl)):
+393            if not silent:
+394                print("checking " + new_names[r])
+395            check_idl(idl[r], che[r])
+396        if not silent:
+397            print("Done")
+398
+399    result_dict = {}
+400    if keyed_out:
+401        for key in needed_keys:
+402            name = _key2specs(key)[0]
+403            result = []
+404            for t in range(intern[name]["T"]):
+405                result.append(Obs(internal_ret_dict[key][t], new_names, idl=idl))
+406            result_dict[key] = result
+407    else:
+408        for name, corr_type in zip(name_list, corr_type_list, strict=True):
+409            result_dict[name] = {}
+410            for quarks in quarks_list:
+411                result_dict[name][quarks] = {}
+412                for off in noffset_list:
+413                    result_dict[name][quarks][off] = {}
+414                    for w in wf_list:
+415                        result_dict[name][quarks][off][w] = {}
+416                        if corr_type != 'bi':
+417                            for w2 in wf2_list:
+418                                key = _specs2key(name, quarks, off, w, w2)
+419                                result = []
+420                                for t in range(intern[name]["T"]):
+421                                    result.append(Obs(internal_ret_dict[key][t], new_names, idl=idl))
+422                                result_dict[name][quarks][str(off)][str(w)][str(w2)] = result
+423                        else:
+424                            key = _specs2key(name, quarks, off, w, "0")
+425                            result = []
+426                            for t in range(intern[name]["T"]):
+427                                result.append(Obs(internal_ret_dict[key][t], new_names, idl=idl))
+428                            result_dict[name][quarks][str(off)][str(w)][str(0)] = result
+429    return result_dict
+430
 431
-432
-433def _specs2key(*specs):
-434    return sep.join(specs)
-435
-436
-437def _read_o_file(cfg_path, name, needed_keys, intern, version, im):
-438    return_vals = {}
-439    for key in needed_keys:
-440        file = cfg_path + '/' + name
-441        specs = _key2specs(key)
-442        if specs[0] == name:
-443            with open(file) as fp:
-444                lines = fp.readlines()
-445                quarks = specs[1]
-446                off = specs[2]
-447                w = specs[3]
-448                w2 = specs[4]
-449                T = intern[name]["T"]
-450                start_read = intern[name]["spec"][quarks][off][w][w2]["start"]
-451                deltas = []
-452                for line in lines[start_read:start_read + T]:
-453                    floats = list(map(float, line.split()))
-454                    if version == "0.0":
-455                        deltas.append(floats[im - intern[name]["single"]])
-456                    else:
-457                        deltas.append(floats[1 + im - intern[name]["single"]])
-458                return_vals[key] = deltas
-459    return return_vals
-460
-461
-462def _extract_corr_type(corr_type):
-463    if corr_type == 'bb':
-464        b2b = True
-465        single = True
-466    elif corr_type == 'bib':
-467        b2b = True
-468        single = False
-469    else:
-470        b2b = False
-471        single = False
-472    return b2b, single
-473
-474
-475def _find_files(rep_path, prefix, compact, files=[]):
-476    sub_ls = []
-477    if not files == []:
-478        files.sort(key=lambda x: int(re.findall(r'\d+', x)[-1]))
+432def _lists2key(*lists):
+433    keys = []
+434    for tup in itertools.product(*lists):
+435        keys.append(sep.join(tup))
+436    return keys
+437
+438
+439def _key2specs(key):
+440    return key.split(sep)
+441
+442
+443def _specs2key(*specs):
+444    return sep.join(specs)
+445
+446
+447def _read_o_file(cfg_path, name, needed_keys, intern, version, im):
+448    return_vals = {}
+449    for key in needed_keys:
+450        file = cfg_path + '/' + name
+451        specs = _key2specs(key)
+452        if specs[0] == name:
+453            with open(file) as fp:
+454                lines = fp.readlines()
+455                quarks = specs[1]
+456                off = specs[2]
+457                w = specs[3]
+458                w2 = specs[4]
+459                T = intern[name]["T"]
+460                start_read = intern[name]["spec"][quarks][off][w][w2]["start"]
+461                deltas = []
+462                for line in lines[start_read:start_read + T]:
+463                    floats = list(map(float, line.split()))
+464                    if version == "0.0":
+465                        deltas.append(floats[im - intern[name]["single"]])
+466                    else:
+467                        deltas.append(floats[1 + im - intern[name]["single"]])
+468                return_vals[key] = deltas
+469    return return_vals
+470
+471
+472def _extract_corr_type(corr_type):
+473    if corr_type == 'bb':
+474        b2b = True
+475        single = True
+476    elif corr_type == 'bib':
+477        b2b = True
+478        single = False
 479    else:
-480        for (dirpath, dirnames, filenames) in os.walk(rep_path):
-481            if compact:
-482                sub_ls.extend(filenames)
-483            else:
-484                sub_ls.extend(dirnames)
-485            break
-486        if compact:
-487            for exc in sub_ls:
-488                if not fnmatch.fnmatch(exc, prefix + '*'):
-489                    sub_ls = list(set(sub_ls) - set([exc]))
-490            sub_ls.sort(key=lambda x: int(re.findall(r'\d+', x)[-1]))
-491        else:
-492            for exc in sub_ls:
-493                if not fnmatch.fnmatch(exc, 'cfg*'):
-494                    sub_ls = list(set(sub_ls) - set([exc]))
-495            sub_ls.sort(key=lambda x: int(x[3:]))
-496        files = sub_ls
-497    if len(files) == 0:
-498        raise FileNotFoundError("Did not find files in", rep_path, "with prefix", prefix, "and the given structure.")
-499    return files
-500
-501
-502def _make_pattern(version, name, noffset, wf, wf2, b2b, quarks):
-503    if version == "0.0":
-504        pattern = "# " + name + " : offset " + str(noffset) + ", wf " + str(wf)
-505        if b2b:
-506            pattern += ", wf_2 " + str(wf2)
-507        qs = quarks.split(" ")
-508        pattern += " : " + qs[0] + " - " + qs[1]
-509    else:
-510        pattern = 'name      ' + name + '\nquarks    ' + quarks + '\noffset    ' + str(noffset) + '\nwf        ' + str(wf)
-511        if b2b:
-512            pattern += '\nwf_2      ' + str(wf2)
-513    return pattern
-514
-515
-516def _find_correlator(file_name, version, pattern, b2b, silent=False):
-517    T = 0
-518
-519    with open(file_name, "r") as my_file:
-520
-521        content = my_file.read()
-522        match = re.search(pattern, content)
-523        if match:
-524            if version == "0.0":
-525                start_read = content.count('\n', 0, match.start()) + 1
-526                T = content.count('\n', start_read)
-527            else:
-528                start_read = content.count('\n', 0, match.start()) + 5 + b2b
-529                end_match = re.search(r'\n\s*\n', content[match.start():])
-530                T = content[match.start():].count('\n', 0, end_match.start()) - 4 - b2b
-531            if not T > 0:
-532                raise ValueError("Correlator with pattern\n" + pattern + "\nis empty!")
-533            if not silent:
-534                print(T, 'entries, starting to read in line', start_read)
-535
-536        else:
-537            raise ValueError('Correlator with pattern\n' + pattern + '\nnot found.')
-538
-539    return start_read, T
-540
-541
-542def _read_compact_file(rep_path, cfg_file, intern, needed_keys, im):
-543    return_vals = {}
-544    with open(rep_path + cfg_file) as fp:
-545        lines = fp.readlines()
-546        for key in needed_keys:
-547            keys = _key2specs(key)
-548            name = keys[0]
-549            quarks = keys[1]
-550            off = keys[2]
-551            w = keys[3]
-552            w2 = keys[4]
+480        b2b = False
+481        single = False
+482    return b2b, single
+483
+484
+485def _find_files(rep_path, prefix, compact, files=None):
+486    if files is None:
+487        files = []
+488    sub_ls = []
+489    if not files == []:
+490        files.sort(key=lambda x: int(re.findall(r'\d+', x)[-1]))
+491    else:
+492        for (_dirpath, dirnames, filenames) in os.walk(rep_path):
+493            if compact:
+494                sub_ls.extend(filenames)
+495            else:
+496                sub_ls.extend(dirnames)
+497            break
+498        if compact:
+499            for exc in sub_ls:
+500                if not fnmatch.fnmatch(exc, prefix + '*'):
+501                    sub_ls = list(set(sub_ls) - set([exc]))
+502            sub_ls.sort(key=lambda x: int(re.findall(r'\d+', x)[-1]))
+503        else:
+504            for exc in sub_ls:
+505                if not fnmatch.fnmatch(exc, 'cfg*'):
+506                    sub_ls = list(set(sub_ls) - set([exc]))
+507            sub_ls.sort(key=lambda x: int(x[3:]))
+508        files = sub_ls
+509    if len(files) == 0:
+510        raise FileNotFoundError("Did not find files in", rep_path, "with prefix", prefix, "and the given structure.")
+511    return files
+512
+513
+514def _make_pattern(version, name, noffset, wf, wf2, b2b, quarks):
+515    if version == "0.0":
+516        pattern = "# " + name + " : offset " + str(noffset) + ", wf " + str(wf)
+517        if b2b:
+518            pattern += ", wf_2 " + str(wf2)
+519        qs = quarks.split(" ")
+520        pattern += " : " + qs[0] + " - " + qs[1]
+521    else:
+522        pattern = 'name      ' + name + '\nquarks    ' + quarks + '\noffset    ' + str(noffset) + '\nwf        ' + str(wf)
+523        if b2b:
+524            pattern += '\nwf_2      ' + str(wf2)
+525    return pattern
+526
+527
+528def _find_correlator(file_name, version, pattern, b2b, silent=False):
+529    T = 0
+530
+531    with open(file_name) as my_file:
+532
+533        content = my_file.read()
+534        match = re.search(pattern, content)
+535        if match:
+536            if version == "0.0":
+537                start_read = content.count('\n', 0, match.start()) + 1
+538                T = content.count('\n', start_read)
+539            else:
+540                start_read = content.count('\n', 0, match.start()) + 5 + b2b
+541                end_match = re.search(r'\n\s*\n', content[match.start():])
+542                T = content[match.start():].count('\n', 0, end_match.start()) - 4 - b2b
+543            if not T > 0:
+544                raise ValueError("Correlator with pattern\n" + pattern + "\nis empty!")
+545            if not silent:
+546                print(T, 'entries, starting to read in line', start_read)
+547
+548        else:
+549            raise ValueError('Correlator with pattern\n' + pattern + '\nnot found.')
+550
+551    return start_read, T
+552
 553
-554            T = intern[name]["T"]
-555            start_read = intern[name]["spec"][quarks][off][w][w2]["start"]
-556            # check, if the correlator is in fact
-557            # printed completely
-558            if (start_read + T + 1 > len(lines)):
-559                raise Exception("EOF before end of correlator data! Maybe " + rep_path + cfg_file + " is corrupted?")
-560            corr_lines = lines[start_read - 6: start_read + T]
-561            t_vals = []
-562
-563            if corr_lines[1 - intern[name]["b2b"]].strip() != 'name      ' + name:
-564                raise Exception('Wrong format in file', cfg_file)
+554def _read_compact_file(rep_path, cfg_file, intern, needed_keys, im):
+555    return_vals = {}
+556    with open(rep_path + cfg_file) as fp:
+557        lines = fp.readlines()
+558        for key in needed_keys:
+559            keys = _key2specs(key)
+560            name = keys[0]
+561            quarks = keys[1]
+562            off = keys[2]
+563            w = keys[3]
+564            w2 = keys[4]
 565
-566            for k in range(6, T + 6):
-567                floats = list(map(float, corr_lines[k].split()))
-568                t_vals.append(floats[-2:][im])
-569            return_vals[key] = t_vals
-570    return return_vals
-571
-572
-573def _read_compact_rep(path, rep, sub_ls, intern, needed_keys, im):
-574    rep_path = path + '/' + rep + '/'
-575    no_cfg = len(sub_ls)
-576
-577    return_vals = {}
-578    for key in needed_keys:
-579        name = _key2specs(key)[0]
-580        deltas = []
-581        for t in range(intern[name]["T"]):
-582            deltas.append(np.zeros(no_cfg))
-583        return_vals[key] = deltas
+566            T = intern[name]["T"]
+567            start_read = intern[name]["spec"][quarks][off][w][w2]["start"]
+568            # check, if the correlator is in fact
+569            # printed completely
+570            if (start_read + T + 1 > len(lines)):
+571                raise Exception("EOF before end of correlator data! Maybe " + rep_path + cfg_file + " is corrupted?")
+572            corr_lines = lines[start_read - 6: start_read + T]
+573            t_vals = []
+574
+575            if corr_lines[1 - intern[name]["b2b"]].strip() != 'name      ' + name:
+576                raise Exception('Wrong format in file', cfg_file)
+577
+578            for k in range(6, T + 6):
+579                floats = list(map(float, corr_lines[k].split()))
+580                t_vals.append(floats[-2:][im])
+581            return_vals[key] = t_vals
+582    return return_vals
+583
 584
-585    for cfg in range(no_cfg):
-586        cfg_file = sub_ls[cfg]
-587        cfg_data = _read_compact_file(rep_path, cfg_file, intern, needed_keys, im)
-588        for key in needed_keys:
-589            name = _key2specs(key)[0]
-590            for t in range(intern[name]["T"]):
-591                return_vals[key][t][cfg] = cfg_data[key][t]
-592    return return_vals
-593
-594
-595def _read_chunk_data(chunk, start_read, T, corr_line, b2b, pattern, im, single):
-596    found_pat = ""
-597    data = []
-598    for li in chunk[corr_line + 1:corr_line + 6 + b2b]:
-599        found_pat += li
-600    if re.search(pattern, found_pat):
-601        for t, line in enumerate(chunk[start_read:start_read + T]):
-602            floats = list(map(float, line.split()))
-603            data.append(floats[im + 1 - single])
-604    return data
+585def _read_compact_rep(path, rep, sub_ls, intern, needed_keys, im):
+586    rep_path = path + '/' + rep + '/'
+587    no_cfg = len(sub_ls)
+588
+589    return_vals = {}
+590    for key in needed_keys:
+591        name = _key2specs(key)[0]
+592        deltas = []
+593        for _ in range(intern[name]["T"]):
+594            deltas.append(np.zeros(no_cfg))
+595        return_vals[key] = deltas
+596
+597    for cfg in range(no_cfg):
+598        cfg_file = sub_ls[cfg]
+599        cfg_data = _read_compact_file(rep_path, cfg_file, intern, needed_keys, im)
+600        for key in needed_keys:
+601            name = _key2specs(key)[0]
+602            for t in range(intern[name]["T"]):
+603                return_vals[key][t][cfg] = cfg_data[key][t]
+604    return return_vals
 605
 606
-607def _check_append_rep(content, start_list):
-608    data_len_list = []
-609    header_len_list = []
-610    has_regular_len_heads = True
-611    for chunk_num in range(len(start_list)):
-612        start = start_list[chunk_num]
-613        if chunk_num == len(start_list) - 1:
-614            stop = len(content)
-615        else:
-616            stop = start_list[chunk_num + 1]
-617        chunk = content[start:stop]
-618        for linenumber, line in enumerate(chunk):
-619            if line.startswith("[correlator]"):
-620                header_len = linenumber
-621                break
-622        header_len_list.append(header_len)
-623        data_len_list.append(len(chunk) - header_len)
-624
-625    if len(set(header_len_list)) > 1:
-626        warnings.warn("Not all headers have the same length. Data parts do.")
-627        has_regular_len_heads = False
-628
-629    if len(set(data_len_list)) > 1:
-630        raise Exception("Irregularities in file structure found, not all run data are of the same output length")
-631    return has_regular_len_heads
-632
-633
-634def _read_chunk_structure(chunk, pattern, b2b):
-635    start_read = 0
-636    for linenumber, line in enumerate(chunk):
-637        if line.startswith("gauge_name"):
-638            gauge_line = linenumber
-639        elif line.startswith("[correlator]"):
-640            corr_line = linenumber
-641            found_pat = ""
-642            for li in chunk[corr_line + 1: corr_line + 6 + b2b]:
-643                found_pat += li
-644            if re.search(pattern, found_pat):
-645                start_read = corr_line + 7 + b2b
-646                break
-647    if start_read == 0:
-648        raise ValueError("Did not find pattern\n", pattern)
-649    endline = corr_line + 6 + b2b
-650    while not chunk[endline] == "\n":
-651        endline += 1
-652    T = endline - start_read
-653    return gauge_line, corr_line, start_read, T
-654
-655
-656def _read_append_rep(filename, pattern, b2b, im, single, idl_func, cfg_func_args):
-657    with open(filename, 'r') as fp:
-658        content = fp.readlines()
-659        chunk_start_lines = []
-660        for linenumber, line in enumerate(content):
-661            if "[run]" in line:
-662                chunk_start_lines.append(linenumber)
-663        has_regular_len_heads = _check_append_rep(content, chunk_start_lines)
-664        if has_regular_len_heads:
-665            chunk = content[:chunk_start_lines[1]]
-666            try:
-667                gauge_line, corr_line, start_read, T = _read_chunk_structure(chunk, pattern, b2b)
-668            except ValueError:
-669                raise ValueError("Did not find pattern\n", pattern, "\nin\n", filename, "lines", 1, "to", chunk_start_lines[1] + 1)
-670        # if has_regular_len_heads is true, all other chunks should follow the same structure
-671        rep_idl = []
-672        rep_data = []
-673
-674        for chunk_num in range(len(chunk_start_lines)):
-675            start = chunk_start_lines[chunk_num]
-676            if chunk_num == len(chunk_start_lines) - 1:
-677                stop = len(content)
-678            else:
-679                stop = chunk_start_lines[chunk_num + 1]
-680            chunk = content[start:stop]
-681            if not has_regular_len_heads:
-682                gauge_line, corr_line, start_read, T = _read_chunk_structure(chunk, pattern, b2b)
-683            try:
-684                idl = idl_func(chunk[gauge_line], *cfg_func_args)
-685            except Exception:
-686                raise Exception("Couldn't parse idl from file", filename, ", problem with chunk of lines", start + 1, "to", stop + 1)
-687            data = _read_chunk_data(chunk, start_read, T, corr_line, b2b, pattern, im, single)
-688            rep_idl.append(idl)
-689            rep_data.append(data)
-690
-691        data = []
-692
-693        for t in range(T):
-694            data.append([])
-695            for c in range(len(rep_data)):
-696                data[t].append(rep_data[c][t])
-697        return T, rep_idl, data
-698
-699
-700def _get_rep_names(ls, ens_name=None, rep_sep='r'):
-701    new_names = []
-702    for entry in ls:
-703        try:
-704            idx = entry.index(rep_sep)
-705        except Exception:
-706            raise Exception("Automatic recognition of replicum failed, please enter the key word 'names'.")
-707
-708        if ens_name:
-709            new_names.append(ens_name + '|' + entry[idx:])
-710        else:
-711            new_names.append(entry[:idx] + '|' + entry[idx:])
-712    return new_names
-713
-714
-715def _get_appended_rep_names(ls, prefix, name, ens_name=None, rep_sep='r'):
-716    new_names = []
-717    for exc in ls:
-718        if not fnmatch.fnmatch(exc, prefix + '*.' + name):
-719            ls = list(set(ls) - set([exc]))
-720    ls.sort(key=lambda x: int(re.findall(r'\d+', x)[-1]))
-721    for entry in ls:
-722        myentry = entry[:-len(name) - 1]
-723        try:
-724            idx = myentry.index(rep_sep)
-725        except Exception:
-726            raise Exception("Automatic recognition of replicum failed, please enter the key word 'names'.")
-727
-728        if ens_name:
-729            new_names.append(ens_name + '|' + entry[idx:])
-730        else:
-731            new_names.append(myentry[:idx] + '|' + myentry[idx:])
-732    return new_names
+607def _read_chunk_data(chunk, start_read, T, corr_line, b2b, pattern, im, single):
+608    found_pat = ""
+609    data = []
+610    for li in chunk[corr_line + 1:corr_line + 6 + b2b]:
+611        found_pat += li
+612    if re.search(pattern, found_pat):
+613        for _t, line in enumerate(chunk[start_read:start_read + T]):
+614            floats = list(map(float, line.split()))
+615            data.append(floats[im + 1 - single])
+616    return data
+617
+618
+619def _check_append_rep(content, start_list):
+620    data_len_list = []
+621    header_len_list = []
+622    has_regular_len_heads = True
+623    for chunk_num in range(len(start_list)):
+624        start = start_list[chunk_num]
+625        if chunk_num == len(start_list) - 1:
+626            stop = len(content)
+627        else:
+628            stop = start_list[chunk_num + 1]
+629        chunk = content[start:stop]
+630        for linenumber, line in enumerate(chunk):
+631            if line.startswith("[correlator]"):
+632                header_len = linenumber
+633                break
+634        header_len_list.append(header_len)
+635        data_len_list.append(len(chunk) - header_len)
+636
+637    if len(set(header_len_list)) > 1:
+638        warnings.warn("Not all headers have the same length. Data parts do.", stacklevel=2)
+639        has_regular_len_heads = False
+640
+641    if len(set(data_len_list)) > 1:
+642        raise Exception("Irregularities in file structure found, not all run data are of the same output length")
+643    return has_regular_len_heads
+644
+645
+646def _read_chunk_structure(chunk, pattern, b2b):
+647    start_read = 0
+648    for linenumber, line in enumerate(chunk):
+649        if line.startswith("gauge_name"):
+650            gauge_line = linenumber
+651        elif line.startswith("[correlator]"):
+652            corr_line = linenumber
+653            found_pat = ""
+654            for li in chunk[corr_line + 1: corr_line + 6 + b2b]:
+655                found_pat += li
+656            if re.search(pattern, found_pat):
+657                start_read = corr_line + 7 + b2b
+658                break
+659    if start_read == 0:
+660        raise ValueError("Did not find pattern\n", pattern)
+661    endline = corr_line + 6 + b2b
+662    while not chunk[endline] == "\n":
+663        endline += 1
+664    T = endline - start_read
+665    return gauge_line, corr_line, start_read, T
+666
+667
+668def _read_append_rep(filename, pattern, b2b, im, single, idl_func, cfg_func_args):
+669    with open(filename) as fp:
+670        content = fp.readlines()
+671        chunk_start_lines = []
+672        for linenumber, line in enumerate(content):
+673            if "[run]" in line:
+674                chunk_start_lines.append(linenumber)
+675        has_regular_len_heads = _check_append_rep(content, chunk_start_lines)
+676        if has_regular_len_heads:
+677            chunk = content[:chunk_start_lines[1]]
+678            try:
+679                gauge_line, corr_line, start_read, T = _read_chunk_structure(chunk, pattern, b2b)
+680            except ValueError as err:
+681                raise ValueError("Did not find pattern\n", pattern, "\nin\n", filename, "lines", 1, "to", chunk_start_lines[1] + 1) from err
+682        # if has_regular_len_heads is true, all other chunks should follow the same structure
+683        rep_idl = []
+684        rep_data = []
+685
+686        for chunk_num in range(len(chunk_start_lines)):
+687            start = chunk_start_lines[chunk_num]
+688            if chunk_num == len(chunk_start_lines) - 1:
+689                stop = len(content)
+690            else:
+691                stop = chunk_start_lines[chunk_num + 1]
+692            chunk = content[start:stop]
+693            if not has_regular_len_heads:
+694                gauge_line, corr_line, start_read, T = _read_chunk_structure(chunk, pattern, b2b)
+695            try:
+696                idl = idl_func(chunk[gauge_line], *cfg_func_args)
+697            except Exception as err:
+698                raise Exception("Couldn't parse idl from file", filename, ", problem with chunk of lines", start + 1, "to", stop + 1) from err
+699            data = _read_chunk_data(chunk, start_read, T, corr_line, b2b, pattern, im, single)
+700            rep_idl.append(idl)
+701            rep_data.append(data)
+702
+703        data = []
+704
+705        for t in range(T):
+706            data.append([])
+707            for c in range(len(rep_data)):
+708                data[t].append(rep_data[c][t])
+709        return T, rep_idl, data
+710
+711
+712def _get_rep_names(ls, ens_name=None, rep_sep='r'):
+713    new_names = []
+714    for entry in ls:
+715        try:
+716            idx = entry.index(rep_sep)
+717        except Exception as err:
+718            raise Exception("Automatic recognition of replicum failed, please enter the key word 'names'.") from err
+719
+720        if ens_name:
+721            new_names.append(ens_name + '|' + entry[idx:])
+722        else:
+723            new_names.append(entry[:idx] + '|' + entry[idx:])
+724    return new_names
+725
+726
+727def _get_appended_rep_names(ls, prefix, name, ens_name=None, rep_sep='r'):
+728    new_names = []
+729    for exc in ls:
+730        if not fnmatch.fnmatch(exc, prefix + '*.' + name):
+731            ls = list(set(ls) - set([exc]))
+732    ls.sort(key=lambda x: int(re.findall(r'\d+', x)[-1]))
+733    for entry in ls:
+734        myentry = entry[:-len(name) - 1]
+735        try:
+736            idx = myentry.index(rep_sep)
+737        except Exception as err:
+738            raise Exception("Automatic recognition of replicum failed, please enter the key word 'names'.") from err
+739
+740        if ens_name:
+741            new_names.append(ens_name + '|' + entry[idx:])
+742        else:
+743            new_names.append(myentry[:idx] + '|' + myentry[idx:])
+744    return new_names
 
@@ -841,69 +853,69 @@
-
15def read_sfcf(path, prefix, name, quarks='.*', corr_type="bi", noffset=0, wf=0, wf2=0, version="1.0c", cfg_separator="n", cfg_func=None, silent=False, **kwargs):
-16    """Read sfcf files from given folder structure.
-17
-18    Parameters
-19    ----------
-20    path : str
-21        Path to the sfcf files.
-22    prefix : str
-23        Prefix of the sfcf files.
-24    name : str
-25        Name of the correlation function to read.
-26    quarks : str
-27        Label of the quarks used in the sfcf input file. e.g. "quark quark"
-28        for version 0.0 this does NOT need to be given with the typical " - "
-29        that is present in the output file,
-30        this is done automatically for this version
-31    corr_type : str
-32        Type of correlation function to read. Can be
-33        - 'bi' for boundary-inner
-34        - 'bb' for boundary-boundary
-35        - 'bib' for boundary-inner-boundary
-36    noffset : int
-37        Offset of the source (only relevant when wavefunctions are used)
-38    wf : int
-39        ID of wave function
-40    wf2 : int
-41        ID of the second wavefunction
-42        (only relevant for boundary-to-boundary correlation functions)
-43    im : bool
-44        if True, read imaginary instead of real part
-45        of the correlation function.
-46    names : list
-47        Alternative labeling for replicas/ensembles.
-48        Has to have the appropriate length
-49    ens_name : str
-50        replaces the name of the ensemble
-51    version: str
-52        version of SFCF, with which the measurement was done.
-53        if the compact output option (-c) was specified,
-54        append a "c" to the version (e.g. "1.0c")
-55        if the append output option (-a) was specified,
-56        append an "a" to the version
-57    cfg_separator : str
-58        String that separates the ensemble identifier from the configuration number (default 'n').
-59    replica: list
-60        list of replica to be read, default is all
-61    files: list
-62        list of files to be read per replica, default is all.
-63        for non-compact output format, hand the folders to be read here.
-64    check_configs: list[list[int]]
-65        list of list of supposed configs, eg. [range(1,1000)]
-66        for one replicum with 1000 configs
-67
-68    Returns
-69    -------
-70    result: list[Obs]
-71        list of Observables with length T, observable per timeslice.
-72        bb-type correlators have length 1.
-73    """
-74    ret = read_sfcf_multi(path, prefix, [name], quarks_list=[quarks], corr_type_list=[corr_type],
-75                          noffset_list=[noffset], wf_list=[wf], wf2_list=[wf2], version=version,
-76                          cfg_separator=cfg_separator, cfg_func=cfg_func, silent=silent, **kwargs)
-77    return ret[name][quarks][str(noffset)][str(wf)][str(wf2)]
+            
16def read_sfcf(path, prefix, name, quarks='.*', corr_type="bi", noffset=0, wf=0, wf2=0, version="1.0c", cfg_separator="n", cfg_func=None, silent=False, **kwargs):
+17    """Read sfcf files from given folder structure.
+18
+19    Parameters
+20    ----------
+21    path : str
+22        Path to the sfcf files.
+23    prefix : str
+24        Prefix of the sfcf files.
+25    name : str
+26        Name of the correlation function to read.
+27    quarks : str
+28        Label of the quarks used in the sfcf input file. e.g. "quark quark"
+29        for version 0.0 this does NOT need to be given with the typical " - "
+30        that is present in the output file,
+31        this is done automatically for this version
+32    corr_type : str
+33        Type of correlation function to read. Can be
+34        - 'bi' for boundary-inner
+35        - 'bb' for boundary-boundary
+36        - 'bib' for boundary-inner-boundary
+37    noffset : int
+38        Offset of the source (only relevant when wavefunctions are used)
+39    wf : int
+40        ID of wave function
+41    wf2 : int
+42        ID of the second wavefunction
+43        (only relevant for boundary-to-boundary correlation functions)
+44    im : bool
+45        if True, read imaginary instead of real part
+46        of the correlation function.
+47    names : list
+48        Alternative labeling for replicas/ensembles.
+49        Has to have the appropriate length
+50    ens_name : str
+51        replaces the name of the ensemble
+52    version: str
+53        version of SFCF, with which the measurement was done.
+54        if the compact output option (-c) was specified,
+55        append a "c" to the version (e.g. "1.0c")
+56        if the append output option (-a) was specified,
+57        append an "a" to the version
+58    cfg_separator : str
+59        String that separates the ensemble identifier from the configuration number (default 'n').
+60    replica: list
+61        list of replica to be read, default is all
+62    files: list
+63        list of files to be read per replica, default is all.
+64        for non-compact output format, hand the folders to be read here.
+65    check_configs: list[list[int]]
+66        list of list of supposed configs, eg. [range(1,1000)]
+67        for one replicum with 1000 configs
+68
+69    Returns
+70    -------
+71    result: list[Obs]
+72        list of Observables with length T, observable per timeslice.
+73        bb-type correlators have length 1.
+74    """
+75    ret = read_sfcf_multi(path, prefix, [name], quarks_list=[quarks], corr_type_list=[corr_type],
+76                          noffset_list=[noffset], wf_list=[wf], wf2_list=[wf2], version=version,
+77                          cfg_separator=cfg_separator, cfg_func=cfg_func, silent=silent, **kwargs)
+78    return ret[name][quarks][str(noffset)][str(wf)][str(wf2)]
 
@@ -979,353 +991,362 @@ bb-type correlators have length 1.
def - read_sfcf_multi( path, prefix, name_list, quarks_list=['.*'], corr_type_list=['bi'], noffset_list=[0], wf_list=[0], wf2_list=[0], version='1.0c', cfg_separator='n', cfg_func=None, silent=False, keyed_out=False, **kwargs): + read_sfcf_multi( path, prefix, name_list, quarks_list=None, corr_type_list=None, noffset_list=None, wf_list=None, wf2_list=None, version='1.0c', cfg_separator='n', cfg_func=None, silent=False, keyed_out=False, **kwargs):
-
 80def read_sfcf_multi(path, prefix, name_list, quarks_list=['.*'], corr_type_list=['bi'], noffset_list=[0], wf_list=[0], wf2_list=[0], version="1.0c", cfg_separator="n", cfg_func=None, silent=False, keyed_out=False, **kwargs):
- 81    """Read sfcf files from given folder structure.
- 82
- 83    Parameters
- 84    ----------
- 85    path : str
- 86        Path to the sfcf files.
- 87    prefix : str
- 88        Prefix of the sfcf files.
- 89    name : str
- 90        Name of the correlation function to read.
- 91    quarks_list : list[str]
- 92        Label of the quarks used in the sfcf input file. e.g. "quark quark"
- 93        for version 0.0 this does NOT need to be given with the typical " - "
- 94        that is present in the output file,
- 95        this is done automatically for this version
- 96    corr_type_list : list[str]
- 97        Type of correlation function to read. Can be
- 98        - 'bi' for boundary-inner
- 99        - 'bb' for boundary-boundary
-100        - 'bib' for boundary-inner-boundary
-101    noffset_list : list[int]
-102        Offset of the source (only relevant when wavefunctions are used)
-103    wf_list : int
-104        ID of wave function
-105    wf2_list : list[int]
-106        ID of the second wavefunction
-107        (only relevant for boundary-to-boundary correlation functions)
-108    im : bool
-109        if True, read imaginary instead of real part
-110        of the correlation function.
-111    names : list
-112        Alternative labeling for replicas/ensembles.
-113        Has to have the appropriate length
-114    ens_name : str
-115        replaces the name of the ensemble
-116    version: str
-117        version of SFCF, with which the measurement was done.
-118        if the compact output option (-c) was specified,
-119        append a "c" to the version (e.g. "1.0c")
-120        if the append output option (-a) was specified,
-121        append an "a" to the version
-122    cfg_separator : str
-123        String that separates the ensemble identifier from the configuration number (default 'n').
-124    replica: list
-125        list of replica to be read, default is all
-126    files: list[list[int]]
-127        list of files to be read per replica, default is all.
-128        for non-compact output format, hand the folders to be read here.
-129    check_configs: list[list[int]]
-130        list of list of supposed configs, eg. [range(1,1000)]
-131        for one replicum with 1000 configs
-132    rep_string: str
-133        Separator of ensemble name and replicum. Example: In "ensAr0", "r" would be the separator string.
-134    Returns
-135    -------
-136    result: dict[list[Obs]]
-137        dict with one of the following properties:
-138        if keyed_out:
-139            dict[key] = list[Obs]
-140            where key has the form name/quarks/offset/wf/wf2
-141        if not keyed_out:
-142            dict[name][quarks][offset][wf][wf2] = list[Obs]
-143    """
-144
-145    if kwargs.get('im'):
-146        im = 1
-147        part = 'imaginary'
-148    else:
-149        im = 0
-150        part = 'real'
-151
-152    known_versions = ["0.0", "1.0", "2.0", "1.0c", "2.0c", "1.0a", "2.0a"]
-153
-154    if version not in known_versions:
-155        raise Exception("This version is not known!")
-156    if (version[-1] == "c"):
-157        appended = False
-158        compact = True
-159        version = version[:-1]
-160    elif (version[-1] == "a"):
-161        appended = True
-162        compact = False
-163        version = version[:-1]
-164    else:
-165        compact = False
-166        appended = False
-167    ls = []
-168    if "replica" in kwargs:
-169        ls = kwargs.get("replica")
-170    else:
-171        for (dirpath, dirnames, filenames) in os.walk(path):
-172            if not appended:
-173                ls.extend(dirnames)
-174            else:
-175                ls.extend(filenames)
-176            break
-177        if not ls:
-178            raise Exception('Error, directory not found')
-179        # Exclude folders with different names
-180        for exc in ls:
-181            if not fnmatch.fnmatch(exc, prefix + '*'):
-182                ls = list(set(ls) - set([exc]))
-183
-184    if not appended:
-185        ls = sort_names(ls)
-186        replica = len(ls)
-187
-188    else:
-189        replica = len([file.split(".")[-1] for file in ls]) // len(set([file.split(".")[-1] for file in ls]))
-190    if replica == 0:
-191        raise Exception('No replica found in directory')
-192    if not silent:
-193        print('Read', part, 'part of', name_list, 'from', prefix[:-1], ',', replica, 'replica')
-194
-195    if 'names' in kwargs:
-196        new_names = kwargs.get('names')
-197        if len(new_names) != len(set(new_names)):
-198            raise Exception("names are not unique!")
-199        if len(new_names) != replica:
-200            raise Exception('names should have the length', replica)
-201
-202    else:
-203        ens_name = kwargs.get("ens_name")
-204        if not appended:
-205            new_names = _get_rep_names(ls, ens_name, rep_sep=(kwargs.get('rep_string', 'r')))
-206        else:
-207            new_names = _get_appended_rep_names(ls, prefix, name_list[0], ens_name, rep_sep=(kwargs.get('rep_string', 'r')))
-208        new_names = sort_names(new_names)
-209
-210    idl = []
-211
-212    noffset_list = [str(x) for x in noffset_list]
-213    wf_list = [str(x) for x in wf_list]
-214    wf2_list = [str(x) for x in wf2_list]
-215
-216    # setup dict structures
-217    intern = {}
-218    for name, corr_type in zip(name_list, corr_type_list):
-219        intern[name] = {}
-220        b2b, single = _extract_corr_type(corr_type)
-221        intern[name]["b2b"] = b2b
-222        intern[name]["single"] = single
-223        intern[name]["spec"] = {}
-224        for quarks in quarks_list:
-225            intern[name]["spec"][quarks] = {}
-226            for off in noffset_list:
-227                intern[name]["spec"][quarks][off] = {}
-228                for w in wf_list:
-229                    intern[name]["spec"][quarks][off][w] = {}
-230                    if b2b:
-231                        for w2 in wf2_list:
-232                            intern[name]["spec"][quarks][off][w][w2] = {}
-233                            intern[name]["spec"][quarks][off][w][w2]["pattern"] = _make_pattern(version, name, off, w, w2, intern[name]['b2b'], quarks)
-234                    else:
-235                        intern[name]["spec"][quarks][off][w]["0"] = {}
-236                        intern[name]["spec"][quarks][off][w]["0"]["pattern"] = _make_pattern(version, name, off, w, 0, intern[name]['b2b'], quarks)
-237
-238    internal_ret_dict = {}
-239    needed_keys = []
-240    for name, corr_type in zip(name_list, corr_type_list):
-241        b2b, single = _extract_corr_type(corr_type)
-242        if b2b:
-243            needed_keys.extend(_lists2key([name], quarks_list, noffset_list, wf_list, wf2_list))
-244        else:
-245            needed_keys.extend(_lists2key([name], quarks_list, noffset_list, wf_list, ["0"]))
-246
-247    for key in needed_keys:
-248        internal_ret_dict[key] = []
+            
 81def read_sfcf_multi(path, prefix, name_list, quarks_list=None, corr_type_list=None, noffset_list=None, wf_list=None, wf2_list=None, version="1.0c", cfg_separator="n", cfg_func=None, silent=False, keyed_out=False, **kwargs):
+ 82    """Read sfcf files from given folder structure.
+ 83
+ 84    Parameters
+ 85    ----------
+ 86    path : str
+ 87        Path to the sfcf files.
+ 88    prefix : str
+ 89        Prefix of the sfcf files.
+ 90    name : str
+ 91        Name of the correlation function to read.
+ 92    quarks_list : list[str]
+ 93        Label of the quarks used in the sfcf input file. e.g. "quark quark"
+ 94        for version 0.0 this does NOT need to be given with the typical " - "
+ 95        that is present in the output file,
+ 96        this is done automatically for this version
+ 97    corr_type_list : list[str]
+ 98        Type of correlation function to read. Can be
+ 99        - 'bi' for boundary-inner
+100        - 'bb' for boundary-boundary
+101        - 'bib' for boundary-inner-boundary
+102    noffset_list : list[int]
+103        Offset of the source (only relevant when wavefunctions are used)
+104    wf_list : int
+105        ID of wave function
+106    wf2_list : list[int]
+107        ID of the second wavefunction
+108        (only relevant for boundary-to-boundary correlation functions)
+109    im : bool
+110        if True, read imaginary instead of real part
+111        of the correlation function.
+112    names : list
+113        Alternative labeling for replicas/ensembles.
+114        Has to have the appropriate length
+115    ens_name : str
+116        replaces the name of the ensemble
+117    version: str
+118        version of SFCF, with which the measurement was done.
+119        if the compact output option (-c) was specified,
+120        append a "c" to the version (e.g. "1.0c")
+121        if the append output option (-a) was specified,
+122        append an "a" to the version
+123    cfg_separator : str
+124        String that separates the ensemble identifier from the configuration number (default 'n').
+125    replica: list
+126        list of replica to be read, default is all
+127    files: list[list[int]]
+128        list of files to be read per replica, default is all.
+129        for non-compact output format, hand the folders to be read here.
+130    check_configs: list[list[int]]
+131        list of list of supposed configs, eg. [range(1,1000)]
+132        for one replicum with 1000 configs
+133    rep_string: str
+134        Separator of ensemble name and replicum. Example: In "ensAr0", "r" would be the separator string.
+135    Returns
+136    -------
+137    result: dict[list[Obs]]
+138        dict with one of the following properties:
+139        if keyed_out:
+140            dict[key] = list[Obs]
+141            where key has the form name/quarks/offset/wf/wf2
+142        if not keyed_out:
+143            dict[name][quarks][offset][wf][wf2] = list[Obs]
+144    """
+145
+146    if quarks_list is None:
+147        quarks_list = ['.*']
+148    if corr_type_list is None:
+149        corr_type_list = ['bi']
+150    if noffset_list is None:
+151        noffset_list = [0]
+152    if wf_list is None:
+153        wf_list = [0]
+154    if wf2_list is None:
+155        wf2_list = [0]
+156
+157    if kwargs.get('im'):
+158        im = 1
+159        part = 'imaginary'
+160    else:
+161        im = 0
+162        part = 'real'
+163
+164    known_versions = ["0.0", "1.0", "2.0", "1.0c", "2.0c", "1.0a", "2.0a"]
+165
+166    if version not in known_versions:
+167        raise Exception("This version is not known!")
+168    if (version[-1] == "c"):
+169        appended = False
+170        compact = True
+171        version = version[:-1]
+172    elif (version[-1] == "a"):
+173        appended = True
+174        compact = False
+175        version = version[:-1]
+176    else:
+177        compact = False
+178        appended = False
+179    ls = []
+180    if "replica" in kwargs:
+181        ls = kwargs.get("replica")
+182    else:
+183        for (_dirpath, dirnames, filenames) in os.walk(path):
+184            if not appended:
+185                ls.extend(dirnames)
+186            else:
+187                ls.extend(filenames)
+188            break
+189        if not ls:
+190            raise Exception('Error, directory not found')
+191        # Exclude folders with different names
+192        for exc in ls:
+193            if not fnmatch.fnmatch(exc, prefix + '*'):
+194                ls = list(set(ls) - set([exc]))
+195
+196    if not appended:
+197        ls = sort_names(ls)
+198        replica = len(ls)
+199
+200    else:
+201        replica = len([file.split(".")[-1] for file in ls]) // len(set([file.split(".")[-1] for file in ls]))
+202    if replica == 0:
+203        raise Exception('No replica found in directory')
+204    if not silent:
+205        print('Read', part, 'part of', name_list, 'from', prefix[:-1], ',', replica, 'replica')
+206
+207    if 'names' in kwargs:
+208        new_names = kwargs.get('names')
+209        if len(new_names) != len(set(new_names)):
+210            raise Exception("names are not unique!")
+211        if len(new_names) != replica:
+212            raise Exception('names should have the length', replica)
+213
+214    else:
+215        ens_name = kwargs.get("ens_name")
+216        if not appended:
+217            new_names = _get_rep_names(ls, ens_name, rep_sep=(kwargs.get('rep_string', 'r')))
+218        else:
+219            new_names = _get_appended_rep_names(ls, prefix, name_list[0], ens_name, rep_sep=(kwargs.get('rep_string', 'r')))
+220        new_names = sort_names(new_names)
+221
+222    idl = []
+223
+224    noffset_list = [str(x) for x in noffset_list]
+225    wf_list = [str(x) for x in wf_list]
+226    wf2_list = [str(x) for x in wf2_list]
+227
+228    # setup dict structures
+229    intern = {}
+230    for name, corr_type in zip(name_list, corr_type_list, strict=True):
+231        intern[name] = {}
+232        b2b, single = _extract_corr_type(corr_type)
+233        intern[name]["b2b"] = b2b
+234        intern[name]["single"] = single
+235        intern[name]["spec"] = {}
+236        for quarks in quarks_list:
+237            intern[name]["spec"][quarks] = {}
+238            for off in noffset_list:
+239                intern[name]["spec"][quarks][off] = {}
+240                for w in wf_list:
+241                    intern[name]["spec"][quarks][off][w] = {}
+242                    if b2b:
+243                        for w2 in wf2_list:
+244                            intern[name]["spec"][quarks][off][w][w2] = {}
+245                            intern[name]["spec"][quarks][off][w][w2]["pattern"] = _make_pattern(version, name, off, w, w2, intern[name]['b2b'], quarks)
+246                    else:
+247                        intern[name]["spec"][quarks][off][w]["0"] = {}
+248                        intern[name]["spec"][quarks][off][w]["0"]["pattern"] = _make_pattern(version, name, off, w, 0, intern[name]['b2b'], quarks)
 249
-250    def _default_idl_func(cfg_string, cfg_sep):
-251        return int(cfg_string.split(cfg_sep)[-1])
-252
-253    if cfg_func is None:
-254        print("Default idl function in use.")
-255        cfg_func = _default_idl_func
-256        cfg_func_args = [cfg_separator]
-257    else:
-258        cfg_func_args = kwargs.get("cfg_func_args", [])
-259
-260    if not appended:
-261        for i, item in enumerate(ls):
-262            rep_path = path + '/' + item
-263            if "files" in kwargs:
-264                files = kwargs.get("files")
-265                if isinstance(files, list):
-266                    if all(isinstance(f, list) for f in files):
-267                        files = files[i]
-268                    elif all(isinstance(f, str) for f in files):
-269                        files = files
-270                    else:
-271                        raise TypeError("files has to be of type list[list[str]] or list[str]!")
-272                else:
-273                    raise TypeError("files has to be of type list[list[str]] or list[str]!")
-274
-275            else:
-276                files = []
-277            sub_ls = _find_files(rep_path, prefix, compact, files)
-278            rep_idl = []
-279            no_cfg = len(sub_ls)
-280            for cfg in sub_ls:
-281                try:
-282                    if compact:
-283                        rep_idl.append(cfg_func(cfg, *cfg_func_args))
-284                    else:
-285                        rep_idl.append(int(cfg[3:]))
-286                except Exception:
-287                    raise Exception("Couldn't parse idl from directory, problem with file " + cfg)
-288            rep_idl.sort()
-289            # maybe there is a better way to print the idls
-290            if not silent:
-291                print(item, ':', no_cfg, ' configurations')
-292            idl.append(rep_idl)
-293            # here we have found all the files we need to look into.
-294            if i == 0:
-295                if version != "0.0" and compact:
-296                    file = path + '/' + item + '/' + sub_ls[0]
-297                for name_index, name in enumerate(name_list):
-298                    if version == "0.0" or not compact:
-299                        file = path + '/' + item + '/' + sub_ls[0] + '/' + name
-300                    if corr_type_list[name_index] == 'bi':
-301                        name_keys = _lists2key(quarks_list, noffset_list, wf_list, ["0"])
-302                    else:
-303                        name_keys = _lists2key(quarks_list, noffset_list, wf_list, wf2_list)
-304                    for key in name_keys:
-305                        specs = _key2specs(key)
-306                        quarks = specs[0]
-307                        off = specs[1]
-308                        w = specs[2]
-309                        w2 = specs[3]
-310                        # here, we want to find the place within the file,
-311                        # where the correlator we need is stored.
-312                        # to do so, the pattern needed is put together
-313                        # from the input values
-314                        start_read, T = _find_correlator(file, version, intern[name]["spec"][quarks][str(off)][str(w)][str(w2)]["pattern"], intern[name]['b2b'], silent=silent)
-315                        intern[name]["spec"][quarks][str(off)][str(w)][str(w2)]["start"] = start_read
-316                        intern[name]["T"] = T
-317                        # preparing the datastructure
-318                        # the correlators get parsed into...
-319                        deltas = []
-320                        for j in range(intern[name]["T"]):
-321                            deltas.append([])
-322                        internal_ret_dict[sep.join([name, key])] = deltas
-323
-324            if compact:
-325                rep_deltas = _read_compact_rep(path, item, sub_ls, intern, needed_keys, im)
-326                for key in needed_keys:
-327                    name = _key2specs(key)[0]
-328                    for t in range(intern[name]["T"]):
-329                        internal_ret_dict[key][t].append(rep_deltas[key][t])
-330            else:
-331                for key in needed_keys:
-332                    rep_data = []
-333                    name = _key2specs(key)[0]
-334                    for subitem in sub_ls:
-335                        cfg_path = path + '/' + item + '/' + subitem
-336                        file_data = _read_o_file(cfg_path, name, needed_keys, intern, version, im)
-337                        rep_data.append(file_data)
+250    internal_ret_dict = {}
+251    needed_keys = []
+252    for name, corr_type in zip(name_list, corr_type_list, strict=True):
+253        b2b, single = _extract_corr_type(corr_type)
+254        if b2b:
+255            needed_keys.extend(_lists2key([name], quarks_list, noffset_list, wf_list, wf2_list))
+256        else:
+257            needed_keys.extend(_lists2key([name], quarks_list, noffset_list, wf_list, ["0"]))
+258
+259    for key in needed_keys:
+260        internal_ret_dict[key] = []
+261
+262    def _default_idl_func(cfg_string, cfg_sep):
+263        return int(cfg_string.split(cfg_sep)[-1])
+264
+265    if cfg_func is None:
+266        print("Default idl function in use.")
+267        cfg_func = _default_idl_func
+268        cfg_func_args = [cfg_separator]
+269    else:
+270        cfg_func_args = kwargs.get("cfg_func_args", [])
+271
+272    if not appended:
+273        for i, item in enumerate(ls):
+274            rep_path = path + '/' + item
+275            if "files" in kwargs:
+276                files = kwargs.get("files")
+277                if isinstance(files, list):
+278                    if all(isinstance(f, list) for f in files):
+279                        files = files[i]
+280                    elif not all(isinstance(f, str) for f in files):
+281                        raise TypeError("files has to be of type list[list[str]] or list[str]!")
+282                else:
+283                    raise TypeError("files has to be of type list[list[str]] or list[str]!")
+284
+285            else:
+286                files = []
+287            sub_ls = _find_files(rep_path, prefix, compact, files)
+288            rep_idl = []
+289            no_cfg = len(sub_ls)
+290            for cfg in sub_ls:
+291                try:
+292                    if compact:
+293                        rep_idl.append(cfg_func(cfg, *cfg_func_args))
+294                    else:
+295                        rep_idl.append(int(cfg[3:]))
+296                except Exception as err:
+297                    raise Exception("Couldn't parse idl from directory, problem with file " + cfg) from err
+298            rep_idl.sort()
+299            # maybe there is a better way to print the idls
+300            if not silent:
+301                print(item, ':', no_cfg, ' configurations')
+302            idl.append(rep_idl)
+303            # here we have found all the files we need to look into.
+304            if i == 0:
+305                if version != "0.0" and compact:
+306                    file = path + '/' + item + '/' + sub_ls[0]
+307                for name_index, name in enumerate(name_list):
+308                    if version == "0.0" or not compact:
+309                        file = path + '/' + item + '/' + sub_ls[0] + '/' + name
+310                    if corr_type_list[name_index] == 'bi':
+311                        name_keys = _lists2key(quarks_list, noffset_list, wf_list, ["0"])
+312                    else:
+313                        name_keys = _lists2key(quarks_list, noffset_list, wf_list, wf2_list)
+314                    for key in name_keys:
+315                        specs = _key2specs(key)
+316                        quarks = specs[0]
+317                        off = specs[1]
+318                        w = specs[2]
+319                        w2 = specs[3]
+320                        # here, we want to find the place within the file,
+321                        # where the correlator we need is stored.
+322                        # to do so, the pattern needed is put together
+323                        # from the input values
+324                        start_read, T = _find_correlator(file, version, intern[name]["spec"][quarks][str(off)][str(w)][str(w2)]["pattern"], intern[name]['b2b'], silent=silent)
+325                        intern[name]["spec"][quarks][str(off)][str(w)][str(w2)]["start"] = start_read
+326                        intern[name]["T"] = T
+327                        # preparing the datastructure
+328                        # the correlators get parsed into...
+329                        deltas = []
+330                        for _j in range(intern[name]["T"]):
+331                            deltas.append([])
+332                        internal_ret_dict[sep.join([name, key])] = deltas
+333
+334            if compact:
+335                rep_deltas = _read_compact_rep(path, item, sub_ls, intern, needed_keys, im)
+336                for key in needed_keys:
+337                    name = _key2specs(key)[0]
 338                    for t in range(intern[name]["T"]):
-339                        internal_ret_dict[key][t].append([])
-340                        for cfg in range(no_cfg):
-341                            internal_ret_dict[key][t][i].append(rep_data[cfg][key][t])
-342    else:
-343        for key in needed_keys:
-344            specs = _key2specs(key)
-345            name = specs[0]
-346            quarks = specs[1]
-347            off = specs[2]
-348            w = specs[3]
-349            w2 = specs[4]
-350            if "files" in kwargs:
-351                if isinstance(kwargs.get("files"), list) and all(isinstance(f, str) for f in kwargs.get("files")):
-352                    name_ls = kwargs.get("files")
-353                else:
-354                    raise TypeError("In append mode, files has to be of type list[str]!")
-355            else:
-356                name_ls = ls
-357                for exc in name_ls:
-358                    if not fnmatch.fnmatch(exc, prefix + '*.' + name):
-359                        name_ls = list(set(name_ls) - set([exc]))
-360            name_ls = sort_names(name_ls)
-361            pattern = intern[name]['spec'][quarks][off][w][w2]['pattern']
-362            deltas = []
-363            for rep, file in enumerate(name_ls):
-364                rep_idl = []
-365                filename = path + '/' + file
-366                T, rep_idl, rep_data = _read_append_rep(filename, pattern, intern[name]['b2b'], im, intern[name]['single'], cfg_func, cfg_func_args)
-367                if rep == 0:
-368                    intern[name]['T'] = T
-369                    for t in range(intern[name]['T']):
-370                        deltas.append([])
-371                for t in range(intern[name]['T']):
-372                    deltas[t].append(rep_data[t])
-373                internal_ret_dict[key] = deltas
-374                if name == name_list[0]:
-375                    idl.append(rep_idl)
-376
-377    if kwargs.get("check_configs") is True:
-378        if not silent:
-379            print("Checking for missing configs...")
-380        che = kwargs.get("check_configs")
-381        if not (len(che) == len(idl)):
-382            raise Exception("check_configs has to be the same length as replica!")
-383        for r in range(len(idl)):
-384            if not silent:
-385                print("checking " + new_names[r])
-386            check_idl(idl[r], che[r])
-387        if not silent:
-388            print("Done")
-389
-390    result_dict = {}
-391    if keyed_out:
-392        for key in needed_keys:
-393            name = _key2specs(key)[0]
-394            result = []
-395            for t in range(intern[name]["T"]):
-396                result.append(Obs(internal_ret_dict[key][t], new_names, idl=idl))
-397            result_dict[key] = result
-398    else:
-399        for name, corr_type in zip(name_list, corr_type_list):
-400            result_dict[name] = {}
-401            for quarks in quarks_list:
-402                result_dict[name][quarks] = {}
-403                for off in noffset_list:
-404                    result_dict[name][quarks][off] = {}
-405                    for w in wf_list:
-406                        result_dict[name][quarks][off][w] = {}
-407                        if corr_type != 'bi':
-408                            for w2 in wf2_list:
-409                                key = _specs2key(name, quarks, off, w, w2)
-410                                result = []
-411                                for t in range(intern[name]["T"]):
-412                                    result.append(Obs(internal_ret_dict[key][t], new_names, idl=idl))
-413                                result_dict[name][quarks][str(off)][str(w)][str(w2)] = result
-414                        else:
-415                            key = _specs2key(name, quarks, off, w, "0")
-416                            result = []
-417                            for t in range(intern[name]["T"]):
-418                                result.append(Obs(internal_ret_dict[key][t], new_names, idl=idl))
-419                            result_dict[name][quarks][str(off)][str(w)][str(0)] = result
-420    return result_dict
+339                        internal_ret_dict[key][t].append(rep_deltas[key][t])
+340            else:
+341                for key in needed_keys:
+342                    rep_data = []
+343                    name = _key2specs(key)[0]
+344                    for subitem in sub_ls:
+345                        cfg_path = path + '/' + item + '/' + subitem
+346                        file_data = _read_o_file(cfg_path, name, needed_keys, intern, version, im)
+347                        rep_data.append(file_data)
+348                    for t in range(intern[name]["T"]):
+349                        internal_ret_dict[key][t].append([])
+350                        for cfg in range(no_cfg):
+351                            internal_ret_dict[key][t][i].append(rep_data[cfg][key][t])
+352    else:
+353        for key in needed_keys:
+354            specs = _key2specs(key)
+355            name = specs[0]
+356            quarks = specs[1]
+357            off = specs[2]
+358            w = specs[3]
+359            w2 = specs[4]
+360            if "files" in kwargs:
+361                if isinstance(kwargs.get("files"), list) and all(isinstance(f, str) for f in kwargs.get("files")):
+362                    name_ls = kwargs.get("files")
+363                else:
+364                    raise TypeError("In append mode, files has to be of type list[str]!")
+365            else:
+366                name_ls = ls
+367                for exc in name_ls:
+368                    if not fnmatch.fnmatch(exc, prefix + '*.' + name):
+369                        name_ls = list(set(name_ls) - set([exc]))
+370            name_ls = sort_names(name_ls)
+371            pattern = intern[name]['spec'][quarks][off][w][w2]['pattern']
+372            deltas = []
+373            for rep, file in enumerate(name_ls):
+374                rep_idl = []
+375                filename = path + '/' + file
+376                T, rep_idl, rep_data = _read_append_rep(filename, pattern, intern[name]['b2b'], im, intern[name]['single'], cfg_func, cfg_func_args)
+377                if rep == 0:
+378                    intern[name]['T'] = T
+379                    for _ in range(intern[name]['T']):
+380                        deltas.append([])
+381                for t in range(intern[name]['T']):
+382                    deltas[t].append(rep_data[t])
+383                internal_ret_dict[key] = deltas
+384                if name == name_list[0]:
+385                    idl.append(rep_idl)
+386
+387    if kwargs.get("check_configs") is True:
+388        if not silent:
+389            print("Checking for missing configs...")
+390        che = kwargs.get("check_configs")
+391        if not (len(che) == len(idl)):
+392            raise Exception("check_configs has to be the same length as replica!")
+393        for r in range(len(idl)):
+394            if not silent:
+395                print("checking " + new_names[r])
+396            check_idl(idl[r], che[r])
+397        if not silent:
+398            print("Done")
+399
+400    result_dict = {}
+401    if keyed_out:
+402        for key in needed_keys:
+403            name = _key2specs(key)[0]
+404            result = []
+405            for t in range(intern[name]["T"]):
+406                result.append(Obs(internal_ret_dict[key][t], new_names, idl=idl))
+407            result_dict[key] = result
+408    else:
+409        for name, corr_type in zip(name_list, corr_type_list, strict=True):
+410            result_dict[name] = {}
+411            for quarks in quarks_list:
+412                result_dict[name][quarks] = {}
+413                for off in noffset_list:
+414                    result_dict[name][quarks][off] = {}
+415                    for w in wf_list:
+416                        result_dict[name][quarks][off][w] = {}
+417                        if corr_type != 'bi':
+418                            for w2 in wf2_list:
+419                                key = _specs2key(name, quarks, off, w, w2)
+420                                result = []
+421                                for t in range(intern[name]["T"]):
+422                                    result.append(Obs(internal_ret_dict[key][t], new_names, idl=idl))
+423                                result_dict[name][quarks][str(off)][str(w)][str(w2)] = result
+424                        else:
+425                            key = _specs2key(name, quarks, off, w, "0")
+426                            result = []
+427                            for t in range(intern[name]["T"]):
+428                                result.append(Obs(internal_ret_dict[key][t], new_names, idl=idl))
+429                            result_dict[name][quarks][str(off)][str(w)][str(0)] = result
+430    return result_dict
 
diff --git a/docs/pyerrors/input/utils.html b/docs/pyerrors/input/utils.html index 90f51115..ff38e720 100644 --- a/docs/pyerrors/input/utils.html +++ b/docs/pyerrors/input/utils.html @@ -86,9 +86,9 @@
  1"""Utilities for the input"""
   2
-  3import re
-  4import fnmatch
-  5import os
+  3import fnmatch
+  4import os
+  5import re
   6
   7
   8def sort_names(ll):
@@ -190,7 +190,7 @@
 104    """
 105
 106    ls = []
-107    for (dirpath, dirnames, filenames) in os.walk(path):
+107    for (_dirpath, dirnames, _filenames) in os.walk(path):
 108        ls.extend(dirnames)
 109        break
 110    if not ls:
@@ -206,7 +206,7 @@
 120        rep_path = path + '/' + rep
 121        # files of replicum
 122        sub_ls = []
-123        for (dirpath, dirnames, filenames) in os.walk(rep_path):
+123        for (_dirpath, _dirnames, filenames) in os.walk(rep_path):
 124            sub_ls.extend(filenames)
 125
 126        # filter
@@ -218,9 +218,9 @@
 132        rep_nums = ''
 133        for file in param_files:
 134            with open(rep_path + '/' + file) as fp:
-135                for line in fp:
-136                    pass
-137                last_line = line
+135                last_line = ''
+136                for line in fp:
+137                    last_line = line
 138                if last_line.split()[2] != param_hash:
 139                    rep_nums += file.split("_")[1] + ','
 140        nums[rep_path] = rep_nums
@@ -408,7 +408,7 @@ string with integers of which idls are missing
 105    """
 106
 107    ls = []
-108    for (dirpath, dirnames, filenames) in os.walk(path):
+108    for (_dirpath, dirnames, _filenames) in os.walk(path):
 109        ls.extend(dirnames)
 110        break
 111    if not ls:
@@ -424,7 +424,7 @@ string with integers of which idls are missing
 121        rep_path = path + '/' + rep
 122        # files of replicum
 123        sub_ls = []
-124        for (dirpath, dirnames, filenames) in os.walk(rep_path):
+124        for (_dirpath, _dirnames, filenames) in os.walk(rep_path):
 125            sub_ls.extend(filenames)
 126
 127        # filter
@@ -436,9 +436,9 @@ string with integers of which idls are missing
 133        rep_nums = ''
 134        for file in param_files:
 135            with open(rep_path + '/' + file) as fp:
-136                for line in fp:
-137                    pass
-138                last_line = line
+136                last_line = ''
+137                for line in fp:
+138                    last_line = line
 139                if last_line.split()[2] != param_hash:
 140                    rep_nums += file.split("_")[1] + ','
 141        nums[rep_path] = rep_nums
diff --git a/docs/pyerrors/integrate.html b/docs/pyerrors/integrate.html
index 278884d8..020fb66d 100644
--- a/docs/pyerrors/integrate.html
+++ b/docs/pyerrors/integrate.html
@@ -77,92 +77,93 @@
                         
 
                         
 1import numpy as np
- 2from .obs import derived_observable, Obs
- 3from autograd import jacobian
- 4from scipy.integrate import quad as squad
- 5
+ 2from autograd import jacobian
+ 3from scipy.integrate import quad as squad
+ 4
+ 5from .obs import Obs, derived_observable
  6
- 7def quad(func, p, a, b, **kwargs):
- 8    '''Performs a (one-dimensional) numeric integration of f(p, x) from a to b.
- 9
-10    The integration is performed using scipy.integrate.quad().
-11    All parameters that can be passed to scipy.integrate.quad may also be passed to this function.
-12    The output is the same as for scipy.integrate.quad, the first element being an Obs.
-13
-14    Parameters
-15    ----------
-16    func : object
-17        function to integrate, has to be of the form
-18
-19        ```python
-20        import autograd.numpy as anp
-21
-22        def func(p, x):
-23            return p[0] + p[1] * x + p[2] * anp.sinh(x)
-24        ```
-25        where x is the integration variable.
-26    p : list of floats or Obs
-27        parameters of the function func.
-28    a: float or Obs
-29        Lower limit of integration (use -numpy.inf for -infinity).
-30    b: float or Obs
-31        Upper limit of integration (use -numpy.inf for -infinity).
-32    All parameters of scipy.integrate.quad
-33
-34    Returns
-35    -------
-36    y : Obs
-37        The integral of func from `a` to `b`.
-38    abserr : float
-39        An estimate of the absolute error in the result.
-40    infodict : dict
-41        A dictionary containing additional information.
-42        Run scipy.integrate.quad_explain() for more information.
-43    message
-44        A convergence message.
-45    explain
-46        Appended only with 'cos' or 'sin' weighting and infinite
-47        integration limits, it contains an explanation of the codes in
-48        infodict['ierlst']
-49    '''
-50
-51    Np = len(p)
-52    isobs = [True if isinstance(pi, Obs) else False for pi in p]
-53    pval = np.array([p[i].value if isobs[i] else p[i] for i in range(Np)],)
-54    pobs = [p[i] for i in range(Np) if isobs[i]]
-55
-56    bounds = [a, b]
-57    isobs_b = [True if isinstance(bi, Obs) else False for bi in bounds]
-58    bval = np.array([bounds[i].value if isobs_b[i] else bounds[i] for i in range(2)])
-59    bobs = [bounds[i] for i in range(2) if isobs_b[i]]
-60    bsign = [-1, 1]
-61
-62    ifunc = np.vectorize(lambda x: func(pval, x))
-63
-64    intpars = squad.__code__.co_varnames[3:3 + len(squad.__defaults__)]
-65    ikwargs = {k: kwargs[k] for k in intpars if k in kwargs}
-66
-67    integration_result = squad(ifunc, bval[0], bval[1], **ikwargs)
-68    val = integration_result[0]
-69
-70    jac = jacobian(func)
-71
-72    derivint = []
-73    for i in range(Np):
-74        if isobs[i]:
-75            ifunc = np.vectorize(lambda x: jac(pval, x)[i])
-76            derivint.append(squad(ifunc, bounds[0], bounds[1], **ikwargs)[0])
-77
-78    for i in range(2):
-79        if isobs_b[i]:
-80            derivint.append(bsign[i] * func(pval, bval[i]))
-81
-82    if len(derivint) == 0:
-83        return integration_result
-84
-85    res = derived_observable(lambda x, **kwargs: 0 * (x[0] + np.finfo(np.float64).eps) * (pval[0] + np.finfo(np.float64).eps) + val, pobs + bobs, man_grad=derivint)
-86
-87    return (res, *integration_result[1:])
+ 7
+ 8def quad(func, p, a, b, **kwargs):
+ 9    '''Performs a (one-dimensional) numeric integration of f(p, x) from a to b.
+10
+11    The integration is performed using scipy.integrate.quad().
+12    All parameters that can be passed to scipy.integrate.quad may also be passed to this function.
+13    The output is the same as for scipy.integrate.quad, the first element being an Obs.
+14
+15    Parameters
+16    ----------
+17    func : object
+18        function to integrate, has to be of the form
+19
+20        ```python
+21        import autograd.numpy as anp
+22
+23        def func(p, x):
+24            return p[0] + p[1] * x + p[2] * anp.sinh(x)
+25        ```
+26        where x is the integration variable.
+27    p : list of floats or Obs
+28        parameters of the function func.
+29    a: float or Obs
+30        Lower limit of integration (use -numpy.inf for -infinity).
+31    b: float or Obs
+32        Upper limit of integration (use -numpy.inf for -infinity).
+33    All parameters of scipy.integrate.quad
+34
+35    Returns
+36    -------
+37    y : Obs
+38        The integral of func from `a` to `b`.
+39    abserr : float
+40        An estimate of the absolute error in the result.
+41    infodict : dict
+42        A dictionary containing additional information.
+43        Run scipy.integrate.quad_explain() for more information.
+44    message
+45        A convergence message.
+46    explain
+47        Appended only with 'cos' or 'sin' weighting and infinite
+48        integration limits, it contains an explanation of the codes in
+49        infodict['ierlst']
+50    '''
+51
+52    Np = len(p)
+53    isobs = [True if isinstance(pi, Obs) else False for pi in p]
+54    pval = np.array([p[i].value if isobs[i] else p[i] for i in range(Np)],)
+55    pobs = [p[i] for i in range(Np) if isobs[i]]
+56
+57    bounds = [a, b]
+58    isobs_b = [True if isinstance(bi, Obs) else False for bi in bounds]
+59    bval = np.array([bounds[i].value if isobs_b[i] else bounds[i] for i in range(2)])
+60    bobs = [bounds[i] for i in range(2) if isobs_b[i]]
+61    bsign = [-1, 1]
+62
+63    ifunc = np.vectorize(lambda x: func(pval, x))
+64
+65    intpars = squad.__code__.co_varnames[3:3 + len(squad.__defaults__)]
+66    ikwargs = {k: kwargs[k] for k in intpars if k in kwargs}
+67
+68    integration_result = squad(ifunc, bval[0], bval[1], **ikwargs)
+69    val = integration_result[0]
+70
+71    jac = jacobian(func)
+72
+73    derivint = []
+74    for i in range(Np):
+75        if isobs[i]:
+76            ifunc = np.vectorize(lambda x, i=i: jac(pval, x)[i])
+77            derivint.append(squad(ifunc, bounds[0], bounds[1], **ikwargs)[0])
+78
+79    for i in range(2):
+80        if isobs_b[i]:
+81            derivint.append(bsign[i] * func(pval, bval[i]))
+82
+83    if len(derivint) == 0:
+84        return integration_result
+85
+86    res = derived_observable(lambda x, **kwargs: 0 * (x[0] + np.finfo(np.float64).eps) * (pval[0] + np.finfo(np.float64).eps) + val, pobs + bobs, man_grad=derivint)
+87
+88    return (res, *integration_result[1:])
 
@@ -178,87 +179,87 @@
-
 8def quad(func, p, a, b, **kwargs):
- 9    '''Performs a (one-dimensional) numeric integration of f(p, x) from a to b.
-10
-11    The integration is performed using scipy.integrate.quad().
-12    All parameters that can be passed to scipy.integrate.quad may also be passed to this function.
-13    The output is the same as for scipy.integrate.quad, the first element being an Obs.
-14
-15    Parameters
-16    ----------
-17    func : object
-18        function to integrate, has to be of the form
-19
-20        ```python
-21        import autograd.numpy as anp
-22
-23        def func(p, x):
-24            return p[0] + p[1] * x + p[2] * anp.sinh(x)
-25        ```
-26        where x is the integration variable.
-27    p : list of floats or Obs
-28        parameters of the function func.
-29    a: float or Obs
-30        Lower limit of integration (use -numpy.inf for -infinity).
-31    b: float or Obs
-32        Upper limit of integration (use -numpy.inf for -infinity).
-33    All parameters of scipy.integrate.quad
-34
-35    Returns
-36    -------
-37    y : Obs
-38        The integral of func from `a` to `b`.
-39    abserr : float
-40        An estimate of the absolute error in the result.
-41    infodict : dict
-42        A dictionary containing additional information.
-43        Run scipy.integrate.quad_explain() for more information.
-44    message
-45        A convergence message.
-46    explain
-47        Appended only with 'cos' or 'sin' weighting and infinite
-48        integration limits, it contains an explanation of the codes in
-49        infodict['ierlst']
-50    '''
-51
-52    Np = len(p)
-53    isobs = [True if isinstance(pi, Obs) else False for pi in p]
-54    pval = np.array([p[i].value if isobs[i] else p[i] for i in range(Np)],)
-55    pobs = [p[i] for i in range(Np) if isobs[i]]
-56
-57    bounds = [a, b]
-58    isobs_b = [True if isinstance(bi, Obs) else False for bi in bounds]
-59    bval = np.array([bounds[i].value if isobs_b[i] else bounds[i] for i in range(2)])
-60    bobs = [bounds[i] for i in range(2) if isobs_b[i]]
-61    bsign = [-1, 1]
-62
-63    ifunc = np.vectorize(lambda x: func(pval, x))
-64
-65    intpars = squad.__code__.co_varnames[3:3 + len(squad.__defaults__)]
-66    ikwargs = {k: kwargs[k] for k in intpars if k in kwargs}
-67
-68    integration_result = squad(ifunc, bval[0], bval[1], **ikwargs)
-69    val = integration_result[0]
-70
-71    jac = jacobian(func)
-72
-73    derivint = []
-74    for i in range(Np):
-75        if isobs[i]:
-76            ifunc = np.vectorize(lambda x: jac(pval, x)[i])
-77            derivint.append(squad(ifunc, bounds[0], bounds[1], **ikwargs)[0])
-78
-79    for i in range(2):
-80        if isobs_b[i]:
-81            derivint.append(bsign[i] * func(pval, bval[i]))
-82
-83    if len(derivint) == 0:
-84        return integration_result
-85
-86    res = derived_observable(lambda x, **kwargs: 0 * (x[0] + np.finfo(np.float64).eps) * (pval[0] + np.finfo(np.float64).eps) + val, pobs + bobs, man_grad=derivint)
-87
-88    return (res, *integration_result[1:])
+            
 9def quad(func, p, a, b, **kwargs):
+10    '''Performs a (one-dimensional) numeric integration of f(p, x) from a to b.
+11
+12    The integration is performed using scipy.integrate.quad().
+13    All parameters that can be passed to scipy.integrate.quad may also be passed to this function.
+14    The output is the same as for scipy.integrate.quad, the first element being an Obs.
+15
+16    Parameters
+17    ----------
+18    func : object
+19        function to integrate, has to be of the form
+20
+21        ```python
+22        import autograd.numpy as anp
+23
+24        def func(p, x):
+25            return p[0] + p[1] * x + p[2] * anp.sinh(x)
+26        ```
+27        where x is the integration variable.
+28    p : list of floats or Obs
+29        parameters of the function func.
+30    a: float or Obs
+31        Lower limit of integration (use -numpy.inf for -infinity).
+32    b: float or Obs
+33        Upper limit of integration (use -numpy.inf for -infinity).
+34    All parameters of scipy.integrate.quad
+35
+36    Returns
+37    -------
+38    y : Obs
+39        The integral of func from `a` to `b`.
+40    abserr : float
+41        An estimate of the absolute error in the result.
+42    infodict : dict
+43        A dictionary containing additional information.
+44        Run scipy.integrate.quad_explain() for more information.
+45    message
+46        A convergence message.
+47    explain
+48        Appended only with 'cos' or 'sin' weighting and infinite
+49        integration limits, it contains an explanation of the codes in
+50        infodict['ierlst']
+51    '''
+52
+53    Np = len(p)
+54    isobs = [True if isinstance(pi, Obs) else False for pi in p]
+55    pval = np.array([p[i].value if isobs[i] else p[i] for i in range(Np)],)
+56    pobs = [p[i] for i in range(Np) if isobs[i]]
+57
+58    bounds = [a, b]
+59    isobs_b = [True if isinstance(bi, Obs) else False for bi in bounds]
+60    bval = np.array([bounds[i].value if isobs_b[i] else bounds[i] for i in range(2)])
+61    bobs = [bounds[i] for i in range(2) if isobs_b[i]]
+62    bsign = [-1, 1]
+63
+64    ifunc = np.vectorize(lambda x: func(pval, x))
+65
+66    intpars = squad.__code__.co_varnames[3:3 + len(squad.__defaults__)]
+67    ikwargs = {k: kwargs[k] for k in intpars if k in kwargs}
+68
+69    integration_result = squad(ifunc, bval[0], bval[1], **ikwargs)
+70    val = integration_result[0]
+71
+72    jac = jacobian(func)
+73
+74    derivint = []
+75    for i in range(Np):
+76        if isobs[i]:
+77            ifunc = np.vectorize(lambda x, i=i: jac(pval, x)[i])
+78            derivint.append(squad(ifunc, bounds[0], bounds[1], **ikwargs)[0])
+79
+80    for i in range(2):
+81        if isobs_b[i]:
+82            derivint.append(bsign[i] * func(pval, bval[i]))
+83
+84    if len(derivint) == 0:
+85        return integration_result
+86
+87    res = derived_observable(lambda x, **kwargs: 0 * (x[0] + np.finfo(np.float64).eps) * (pval[0] + np.finfo(np.float64).eps) + val, pobs + bobs, man_grad=derivint)
+88
+89    return (res, *integration_result[1:])
 
diff --git a/docs/pyerrors/linalg.html b/docs/pyerrors/linalg.html index b0c51ff7..8c5bff0c 100644 --- a/docs/pyerrors/linalg.html +++ b/docs/pyerrors/linalg.html @@ -106,296 +106,297 @@ -
  1import numpy as np
-  2import autograd.numpy as anp  # Thinly-wrapped numpy
-  3from .obs import derived_observable, CObs, Obs, import_jackknife
-  4
+                        
  1import autograd.numpy as anp  # Thinly-wrapped numpy
+  2import numpy as np
+  3
+  4from .obs import CObs, Obs, derived_observable, import_jackknife
   5
-  6def matmul(*operands):
-  7    """Matrix multiply all operands.
-  8
-  9    Parameters
- 10    ----------
- 11    operands : numpy.ndarray
- 12        Arbitrary number of 2d-numpy arrays which can be real or complex
- 13        Obs valued.
- 14
- 15    This implementation is faster compared to standard multiplication via the @ operator.
- 16    """
- 17    if any(isinstance(o[0, 0], CObs) for o in operands):
- 18        extended_operands = []
- 19        for op in operands:
- 20            tmp = np.vectorize(lambda x: (np.real(x), np.imag(x)))(op)
- 21            extended_operands.append(tmp[0])
- 22            extended_operands.append(tmp[1])
- 23
- 24        def multi_dot(operands, part):
- 25            stack_r = operands[0]
- 26            stack_i = operands[1]
- 27            for op_r, op_i in zip(operands[2::2], operands[3::2]):
- 28                tmp_r = stack_r @ op_r - stack_i @ op_i
- 29                tmp_i = stack_r @ op_i + stack_i @ op_r
- 30
- 31                stack_r = tmp_r
- 32                stack_i = tmp_i
- 33
- 34            if part == 'Real':
- 35                return stack_r
- 36            else:
- 37                return stack_i
- 38
- 39        def multi_dot_r(operands):
- 40            return multi_dot(operands, 'Real')
- 41
- 42        def multi_dot_i(operands):
- 43            return multi_dot(operands, 'Imag')
- 44
- 45        Nr = derived_observable(multi_dot_r, extended_operands, array_mode=True)
- 46        Ni = derived_observable(multi_dot_i, extended_operands, array_mode=True)
- 47
- 48        res = np.empty_like(Nr)
- 49        for (n, m), entry in np.ndenumerate(Nr):
- 50            res[n, m] = CObs(Nr[n, m], Ni[n, m])
- 51
- 52        return res
- 53    else:
- 54        def multi_dot(operands):
- 55            stack = operands[0]
- 56            for op in operands[1:]:
- 57                stack = stack @ op
- 58            return stack
- 59        return derived_observable(multi_dot, operands, array_mode=True)
- 60
+  6
+  7def matmul(*operands):
+  8    """Matrix multiply all operands.
+  9
+ 10    Parameters
+ 11    ----------
+ 12    operands : numpy.ndarray
+ 13        Arbitrary number of 2d-numpy arrays which can be real or complex
+ 14        Obs valued.
+ 15
+ 16    This implementation is faster compared to standard multiplication via the @ operator.
+ 17    """
+ 18    if any(isinstance(o[0, 0], CObs) for o in operands):
+ 19        extended_operands = []
+ 20        for op in operands:
+ 21            tmp = np.vectorize(lambda x: (np.real(x), np.imag(x)))(op)
+ 22            extended_operands.append(tmp[0])
+ 23            extended_operands.append(tmp[1])
+ 24
+ 25        def multi_dot(operands, part):
+ 26            stack_r = operands[0]
+ 27            stack_i = operands[1]
+ 28            for op_r, op_i in zip(operands[2::2], operands[3::2], strict=True):
+ 29                tmp_r = stack_r @ op_r - stack_i @ op_i
+ 30                tmp_i = stack_r @ op_i + stack_i @ op_r
+ 31
+ 32                stack_r = tmp_r
+ 33                stack_i = tmp_i
+ 34
+ 35            if part == 'Real':
+ 36                return stack_r
+ 37            else:
+ 38                return stack_i
+ 39
+ 40        def multi_dot_r(operands):
+ 41            return multi_dot(operands, 'Real')
+ 42
+ 43        def multi_dot_i(operands):
+ 44            return multi_dot(operands, 'Imag')
+ 45
+ 46        Nr = derived_observable(multi_dot_r, extended_operands, array_mode=True)
+ 47        Ni = derived_observable(multi_dot_i, extended_operands, array_mode=True)
+ 48
+ 49        res = np.empty_like(Nr)
+ 50        for (n, m), _entry in np.ndenumerate(Nr):
+ 51            res[n, m] = CObs(Nr[n, m], Ni[n, m])
+ 52
+ 53        return res
+ 54    else:
+ 55        def multi_dot(operands):
+ 56            stack = operands[0]
+ 57            for op in operands[1:]:
+ 58                stack = stack @ op
+ 59            return stack
+ 60        return derived_observable(multi_dot, operands, array_mode=True)
  61
- 62def jack_matmul(*operands):
- 63    """Matrix multiply both operands making use of the jackknife approximation.
- 64
- 65    Parameters
- 66    ----------
- 67    operands : numpy.ndarray
- 68        Arbitrary number of 2d-numpy arrays which can be real or complex
- 69        Obs valued.
- 70
- 71    For large matrices this is considerably faster compared to matmul.
- 72    """
- 73
- 74    def _exp_to_jack(matrix):
- 75        base_matrix = np.empty_like(matrix)
- 76        for index, entry in np.ndenumerate(matrix):
- 77            base_matrix[index] = entry.export_jackknife()
- 78        return base_matrix
- 79
- 80    def _imp_from_jack(matrix, name, idl):
- 81        base_matrix = np.empty_like(matrix)
- 82        for index, entry in np.ndenumerate(matrix):
- 83            base_matrix[index] = import_jackknife(entry, name, [idl])
- 84        return base_matrix
- 85
- 86    def _exp_to_jack_c(matrix):
- 87        base_matrix = np.empty_like(matrix)
- 88        for index, entry in np.ndenumerate(matrix):
- 89            base_matrix[index] = entry.real.export_jackknife() + 1j * entry.imag.export_jackknife()
- 90        return base_matrix
- 91
- 92    def _imp_from_jack_c(matrix, name, idl):
- 93        base_matrix = np.empty_like(matrix)
- 94        for index, entry in np.ndenumerate(matrix):
- 95            base_matrix[index] = CObs(import_jackknife(entry.real, name, [idl]),
- 96                                      import_jackknife(entry.imag, name, [idl]))
- 97        return base_matrix
- 98
- 99    if any(isinstance(o.flat[0], CObs) for o in operands):
-100        name = operands[0].flat[0].real.names[0]
-101        idl = operands[0].flat[0].real.idl[name]
-102
-103        r = _exp_to_jack_c(operands[0])
-104        for op in operands[1:]:
-105            if isinstance(op.flat[0], CObs):
-106                r = r @ _exp_to_jack_c(op)
-107            else:
-108                r = r @ op
-109        return _imp_from_jack_c(r, name, idl)
-110    else:
-111        name = operands[0].flat[0].names[0]
-112        idl = operands[0].flat[0].idl[name]
-113
-114        r = _exp_to_jack(operands[0])
-115        for op in operands[1:]:
-116            if isinstance(op.flat[0], Obs):
-117                r = r @ _exp_to_jack(op)
-118            else:
-119                r = r @ op
-120        return _imp_from_jack(r, name, idl)
-121
+ 62
+ 63def jack_matmul(*operands):
+ 64    """Matrix multiply both operands making use of the jackknife approximation.
+ 65
+ 66    Parameters
+ 67    ----------
+ 68    operands : numpy.ndarray
+ 69        Arbitrary number of 2d-numpy arrays which can be real or complex
+ 70        Obs valued.
+ 71
+ 72    For large matrices this is considerably faster compared to matmul.
+ 73    """
+ 74
+ 75    def _exp_to_jack(matrix):
+ 76        base_matrix = np.empty_like(matrix)
+ 77        for index, entry in np.ndenumerate(matrix):
+ 78            base_matrix[index] = entry.export_jackknife()
+ 79        return base_matrix
+ 80
+ 81    def _imp_from_jack(matrix, name, idl):
+ 82        base_matrix = np.empty_like(matrix)
+ 83        for index, entry in np.ndenumerate(matrix):
+ 84            base_matrix[index] = import_jackknife(entry, name, [idl])
+ 85        return base_matrix
+ 86
+ 87    def _exp_to_jack_c(matrix):
+ 88        base_matrix = np.empty_like(matrix)
+ 89        for index, entry in np.ndenumerate(matrix):
+ 90            base_matrix[index] = entry.real.export_jackknife() + 1j * entry.imag.export_jackknife()
+ 91        return base_matrix
+ 92
+ 93    def _imp_from_jack_c(matrix, name, idl):
+ 94        base_matrix = np.empty_like(matrix)
+ 95        for index, entry in np.ndenumerate(matrix):
+ 96            base_matrix[index] = CObs(import_jackknife(entry.real, name, [idl]),
+ 97                                      import_jackknife(entry.imag, name, [idl]))
+ 98        return base_matrix
+ 99
+100    if any(isinstance(o.flat[0], CObs) for o in operands):
+101        name = operands[0].flat[0].real.names[0]
+102        idl = operands[0].flat[0].real.idl[name]
+103
+104        r = _exp_to_jack_c(operands[0])
+105        for op in operands[1:]:
+106            if isinstance(op.flat[0], CObs):
+107                r = r @ _exp_to_jack_c(op)
+108            else:
+109                r = r @ op
+110        return _imp_from_jack_c(r, name, idl)
+111    else:
+112        name = operands[0].flat[0].names[0]
+113        idl = operands[0].flat[0].idl[name]
+114
+115        r = _exp_to_jack(operands[0])
+116        for op in operands[1:]:
+117            if isinstance(op.flat[0], Obs):
+118                r = r @ _exp_to_jack(op)
+119            else:
+120                r = r @ op
+121        return _imp_from_jack(r, name, idl)
 122
-123def einsum(subscripts, *operands):
-124    """Wrapper for numpy.einsum
-125
-126    Parameters
-127    ----------
-128    subscripts : str
-129        Subscripts for summation (see numpy documentation for details)
-130    operands : numpy.ndarray
-131        Arbitrary number of 2d-numpy arrays which can be real or complex
-132        Obs valued.
-133    """
-134
-135    def _exp_to_jack(matrix):
-136        base_matrix = []
-137        for index, entry in np.ndenumerate(matrix):
-138            base_matrix.append(entry.export_jackknife())
-139        return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape)
-140
-141    def _exp_to_jack_c(matrix):
-142        base_matrix = []
-143        for index, entry in np.ndenumerate(matrix):
-144            base_matrix.append(entry.real.export_jackknife() + 1j * entry.imag.export_jackknife())
-145        return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape)
-146
-147    def _imp_from_jack(matrix, name, idl):
-148        base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object)
-149        for index in np.ndindex(matrix.shape[:-1]):
-150            base_matrix[index] = import_jackknife(matrix[index], name, [idl])
-151        return base_matrix
-152
-153    def _imp_from_jack_c(matrix, name, idl):
-154        base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object)
-155        for index in np.ndindex(matrix.shape[:-1]):
-156            base_matrix[index] = CObs(import_jackknife(matrix[index].real, name, [idl]),
-157                                      import_jackknife(matrix[index].imag, name, [idl]))
-158        return base_matrix
-159
-160    for op in operands:
-161        if isinstance(op.flat[0], CObs):
-162            name = op.flat[0].real.names[0]
-163            idl = op.flat[0].real.idl[name]
-164            break
-165        elif isinstance(op.flat[0], Obs):
-166            name = op.flat[0].names[0]
-167            idl = op.flat[0].idl[name]
-168            break
-169
-170    conv_operands = []
-171    for op in operands:
-172        if isinstance(op.flat[0], CObs):
-173            conv_operands.append(_exp_to_jack_c(op))
-174        elif isinstance(op.flat[0], Obs):
-175            conv_operands.append(_exp_to_jack(op))
-176        else:
-177            conv_operands.append(op)
-178
-179    tmp_subscripts = ','.join([o + '...' for o in subscripts.split(',')])
-180    extended_subscripts = '->'.join([o + '...' for o in tmp_subscripts.split('->')[:-1]] + [tmp_subscripts.split('->')[-1]])
-181    einsum_path = np.einsum_path(extended_subscripts, *conv_operands, optimize='optimal')[0]
-182    jack_einsum = np.einsum(extended_subscripts, *conv_operands, optimize=einsum_path)
-183
-184    if jack_einsum.dtype == complex:
-185        result = _imp_from_jack_c(jack_einsum, name, idl)
-186    elif jack_einsum.dtype == float:
-187        result = _imp_from_jack(jack_einsum, name, idl)
-188    else:
-189        raise Exception("Result has unexpected datatype")
-190
-191    if result.shape == ():
-192        return result.flat[0]
-193    else:
-194        return result
-195
+123
+124def einsum(subscripts, *operands):
+125    """Wrapper for numpy.einsum
+126
+127    Parameters
+128    ----------
+129    subscripts : str
+130        Subscripts for summation (see numpy documentation for details)
+131    operands : numpy.ndarray
+132        Arbitrary number of 2d-numpy arrays which can be real or complex
+133        Obs valued.
+134    """
+135
+136    def _exp_to_jack(matrix):
+137        base_matrix = []
+138        for _index, entry in np.ndenumerate(matrix):
+139            base_matrix.append(entry.export_jackknife())
+140        return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape)
+141
+142    def _exp_to_jack_c(matrix):
+143        base_matrix = []
+144        for _index, entry in np.ndenumerate(matrix):
+145            base_matrix.append(entry.real.export_jackknife() + 1j * entry.imag.export_jackknife())
+146        return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape)
+147
+148    def _imp_from_jack(matrix, name, idl):
+149        base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object)
+150        for index in np.ndindex(matrix.shape[:-1]):
+151            base_matrix[index] = import_jackknife(matrix[index], name, [idl])
+152        return base_matrix
+153
+154    def _imp_from_jack_c(matrix, name, idl):
+155        base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object)
+156        for index in np.ndindex(matrix.shape[:-1]):
+157            base_matrix[index] = CObs(import_jackknife(matrix[index].real, name, [idl]),
+158                                      import_jackknife(matrix[index].imag, name, [idl]))
+159        return base_matrix
+160
+161    for op in operands:
+162        if isinstance(op.flat[0], CObs):
+163            name = op.flat[0].real.names[0]
+164            idl = op.flat[0].real.idl[name]
+165            break
+166        elif isinstance(op.flat[0], Obs):
+167            name = op.flat[0].names[0]
+168            idl = op.flat[0].idl[name]
+169            break
+170
+171    conv_operands = []
+172    for op in operands:
+173        if isinstance(op.flat[0], CObs):
+174            conv_operands.append(_exp_to_jack_c(op))
+175        elif isinstance(op.flat[0], Obs):
+176            conv_operands.append(_exp_to_jack(op))
+177        else:
+178            conv_operands.append(op)
+179
+180    tmp_subscripts = ','.join([o + '...' for o in subscripts.split(',')])
+181    extended_subscripts = '->'.join([o + '...' for o in tmp_subscripts.split('->')[:-1]] + [tmp_subscripts.split('->')[-1]])
+182    einsum_path = np.einsum_path(extended_subscripts, *conv_operands, optimize='optimal')[0]
+183    jack_einsum = np.einsum(extended_subscripts, *conv_operands, optimize=einsum_path)
+184
+185    if jack_einsum.dtype == complex:
+186        result = _imp_from_jack_c(jack_einsum, name, idl)
+187    elif jack_einsum.dtype == float:
+188        result = _imp_from_jack(jack_einsum, name, idl)
+189    else:
+190        raise Exception("Result has unexpected datatype")
+191
+192    if result.shape == ():
+193        return result.flat[0]
+194    else:
+195        return result
 196
-197def inv(x):
-198    """Inverse of Obs or CObs valued matrices."""
-199    return _mat_mat_op(anp.linalg.inv, x)
-200
+197
+198def inv(x):
+199    """Inverse of Obs or CObs valued matrices."""
+200    return _mat_mat_op(anp.linalg.inv, x)
 201
-202def cholesky(x):
-203    """Cholesky decomposition of Obs valued matrices."""
-204    if any(isinstance(o, CObs) for o in x.ravel()):
-205        raise Exception("Cholesky decomposition is not implemented for CObs.")
-206    return _mat_mat_op(anp.linalg.cholesky, x)
-207
+202
+203def cholesky(x):
+204    """Cholesky decomposition of Obs valued matrices."""
+205    if any(isinstance(o, CObs) for o in x.ravel()):
+206        raise Exception("Cholesky decomposition is not implemented for CObs.")
+207    return _mat_mat_op(anp.linalg.cholesky, x)
 208
-209def det(x):
-210    """Determinant of Obs valued matrices."""
-211    return _scalar_mat_op(anp.linalg.det, x)
-212
+209
+210def det(x):
+211    """Determinant of Obs valued matrices."""
+212    return _scalar_mat_op(anp.linalg.det, x)
 213
-214def _scalar_mat_op(op, obs, **kwargs):
-215    """Computes the matrix to scalar operation op to a given matrix of Obs."""
-216    def _mat(x, **kwargs):
-217        dim = int(np.sqrt(len(x)))
-218
-219        mat = []
-220        for i in range(dim):
-221            row = []
-222            for j in range(dim):
-223                row.append(x[j + dim * i])
-224            mat.append(row)
-225
-226        return op(anp.array(mat))
-227
-228    if isinstance(obs, np.ndarray):
-229        raveled_obs = (1 * (obs.ravel())).tolist()
-230    else:
-231        raise TypeError('Unproper type of input.')
-232    return derived_observable(_mat, raveled_obs, **kwargs)
-233
+214
+215def _scalar_mat_op(op, obs, **kwargs):
+216    """Computes the matrix to scalar operation op to a given matrix of Obs."""
+217    def _mat(x, **kwargs):
+218        dim = int(np.sqrt(len(x)))
+219
+220        mat = []
+221        for i in range(dim):
+222            row = []
+223            for j in range(dim):
+224                row.append(x[j + dim * i])
+225            mat.append(row)
+226
+227        return op(anp.array(mat))
+228
+229    if isinstance(obs, np.ndarray):
+230        raveled_obs = (1 * (obs.ravel())).tolist()
+231    else:
+232        raise TypeError('Unproper type of input.')
+233    return derived_observable(_mat, raveled_obs, **kwargs)
 234
-235def _mat_mat_op(op, obs, **kwargs):
-236    """Computes the matrix to matrix operation op to a given matrix of Obs."""
-237    # Use real representation to calculate matrix operations for complex matrices
-238    if any(isinstance(o, CObs) for o in obs.ravel()):
-239        A = np.empty_like(obs)
-240        B = np.empty_like(obs)
-241        for (n, m), entry in np.ndenumerate(obs):
-242            if hasattr(entry, 'real') and hasattr(entry, 'imag'):
-243                A[n, m] = entry.real
-244                B[n, m] = entry.imag
-245            else:
-246                A[n, m] = entry
-247                B[n, m] = 0.0
-248        big_matrix = np.block([[A, -B], [B, A]])
-249        op_big_matrix = derived_observable(lambda x, **kwargs: op(x), [big_matrix], array_mode=True)[0]
-250        dim = op_big_matrix.shape[0]
-251        op_A = op_big_matrix[0: dim // 2, 0: dim // 2]
-252        op_B = op_big_matrix[dim // 2:, 0: dim // 2]
-253        res = np.empty_like(op_A)
-254        for (n, m), entry in np.ndenumerate(op_A):
-255            res[n, m] = CObs(op_A[n, m], op_B[n, m])
-256        return res
-257    else:
-258        return derived_observable(lambda x, **kwargs: op(x), [obs], array_mode=True)[0]
-259
+235
+236def _mat_mat_op(op, obs, **kwargs):
+237    """Computes the matrix to matrix operation op to a given matrix of Obs."""
+238    # Use real representation to calculate matrix operations for complex matrices
+239    if any(isinstance(o, CObs) for o in obs.ravel()):
+240        A = np.empty_like(obs)
+241        B = np.empty_like(obs)
+242        for (n, m), entry in np.ndenumerate(obs):
+243            if hasattr(entry, 'real') and hasattr(entry, 'imag'):
+244                A[n, m] = entry.real
+245                B[n, m] = entry.imag
+246            else:
+247                A[n, m] = entry
+248                B[n, m] = 0.0
+249        big_matrix = np.block([[A, -B], [B, A]])
+250        op_big_matrix = derived_observable(lambda x, **kwargs: op(x), [big_matrix], array_mode=True)[0]
+251        dim = op_big_matrix.shape[0]
+252        op_A = op_big_matrix[0: dim // 2, 0: dim // 2]
+253        op_B = op_big_matrix[dim // 2:, 0: dim // 2]
+254        res = np.empty_like(op_A)
+255        for (n, m), _entry in np.ndenumerate(op_A):
+256            res[n, m] = CObs(op_A[n, m], op_B[n, m])
+257        return res
+258    else:
+259        return derived_observable(lambda x, **kwargs: op(x), [obs], array_mode=True)[0]
 260
-261def eigh(obs, **kwargs):
-262    """Computes the eigenvalues and eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh."""
-263    w = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[0], obs)
-264    v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs)
-265    return w, v
-266
+261
+262def eigh(obs, **kwargs):
+263    """Computes the eigenvalues and eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh."""
+264    w = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[0], obs)
+265    v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs)
+266    return w, v
 267
-268def eig(obs, **kwargs):
-269    """Computes the eigenvalues of a given matrix of Obs according to np.linalg.eig."""
-270    w = derived_observable(lambda x, **kwargs: anp.real(anp.linalg.eig(x)[0]), obs)
-271    return w
-272
+268
+269def eig(obs, **kwargs):
+270    """Computes the eigenvalues of a given matrix of Obs according to np.linalg.eig."""
+271    w = derived_observable(lambda x, **kwargs: anp.real(anp.linalg.eig(x)[0]), obs)
+272    return w
 273
-274def eigv(obs, **kwargs):
-275    """Computes the eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh."""
-276    v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs)
-277    return v
-278
+274
+275def eigv(obs, **kwargs):
+276    """Computes the eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh."""
+277    v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs)
+278    return v
 279
-280def pinv(obs, **kwargs):
-281    """Computes the Moore-Penrose pseudoinverse of a matrix of Obs."""
-282    return derived_observable(lambda x, **kwargs: anp.linalg.pinv(x), obs)
-283
+280
+281def pinv(obs, **kwargs):
+282    """Computes the Moore-Penrose pseudoinverse of a matrix of Obs."""
+283    return derived_observable(lambda x, **kwargs: anp.linalg.pinv(x), obs)
 284
-285def svd(obs, **kwargs):
-286    """Computes the singular value decomposition of a matrix of Obs."""
-287    u = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[0], obs)
-288    s = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[1], obs)
-289    vh = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[2], obs)
-290    return (u, s, vh)
+285
+286def svd(obs, **kwargs):
+287    """Computes the singular value decomposition of a matrix of Obs."""
+288    u = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[0], obs)
+289    s = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[1], obs)
+290    vh = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[2], obs)
+291    return (u, s, vh)
 
@@ -411,60 +412,60 @@
-
 7def matmul(*operands):
- 8    """Matrix multiply all operands.
- 9
-10    Parameters
-11    ----------
-12    operands : numpy.ndarray
-13        Arbitrary number of 2d-numpy arrays which can be real or complex
-14        Obs valued.
-15
-16    This implementation is faster compared to standard multiplication via the @ operator.
-17    """
-18    if any(isinstance(o[0, 0], CObs) for o in operands):
-19        extended_operands = []
-20        for op in operands:
-21            tmp = np.vectorize(lambda x: (np.real(x), np.imag(x)))(op)
-22            extended_operands.append(tmp[0])
-23            extended_operands.append(tmp[1])
-24
-25        def multi_dot(operands, part):
-26            stack_r = operands[0]
-27            stack_i = operands[1]
-28            for op_r, op_i in zip(operands[2::2], operands[3::2]):
-29                tmp_r = stack_r @ op_r - stack_i @ op_i
-30                tmp_i = stack_r @ op_i + stack_i @ op_r
-31
-32                stack_r = tmp_r
-33                stack_i = tmp_i
-34
-35            if part == 'Real':
-36                return stack_r
-37            else:
-38                return stack_i
-39
-40        def multi_dot_r(operands):
-41            return multi_dot(operands, 'Real')
-42
-43        def multi_dot_i(operands):
-44            return multi_dot(operands, 'Imag')
-45
-46        Nr = derived_observable(multi_dot_r, extended_operands, array_mode=True)
-47        Ni = derived_observable(multi_dot_i, extended_operands, array_mode=True)
-48
-49        res = np.empty_like(Nr)
-50        for (n, m), entry in np.ndenumerate(Nr):
-51            res[n, m] = CObs(Nr[n, m], Ni[n, m])
-52
-53        return res
-54    else:
-55        def multi_dot(operands):
-56            stack = operands[0]
-57            for op in operands[1:]:
-58                stack = stack @ op
-59            return stack
-60        return derived_observable(multi_dot, operands, array_mode=True)
+            
 8def matmul(*operands):
+ 9    """Matrix multiply all operands.
+10
+11    Parameters
+12    ----------
+13    operands : numpy.ndarray
+14        Arbitrary number of 2d-numpy arrays which can be real or complex
+15        Obs valued.
+16
+17    This implementation is faster compared to standard multiplication via the @ operator.
+18    """
+19    if any(isinstance(o[0, 0], CObs) for o in operands):
+20        extended_operands = []
+21        for op in operands:
+22            tmp = np.vectorize(lambda x: (np.real(x), np.imag(x)))(op)
+23            extended_operands.append(tmp[0])
+24            extended_operands.append(tmp[1])
+25
+26        def multi_dot(operands, part):
+27            stack_r = operands[0]
+28            stack_i = operands[1]
+29            for op_r, op_i in zip(operands[2::2], operands[3::2], strict=True):
+30                tmp_r = stack_r @ op_r - stack_i @ op_i
+31                tmp_i = stack_r @ op_i + stack_i @ op_r
+32
+33                stack_r = tmp_r
+34                stack_i = tmp_i
+35
+36            if part == 'Real':
+37                return stack_r
+38            else:
+39                return stack_i
+40
+41        def multi_dot_r(operands):
+42            return multi_dot(operands, 'Real')
+43
+44        def multi_dot_i(operands):
+45            return multi_dot(operands, 'Imag')
+46
+47        Nr = derived_observable(multi_dot_r, extended_operands, array_mode=True)
+48        Ni = derived_observable(multi_dot_i, extended_operands, array_mode=True)
+49
+50        res = np.empty_like(Nr)
+51        for (n, m), _entry in np.ndenumerate(Nr):
+52            res[n, m] = CObs(Nr[n, m], Ni[n, m])
+53
+54        return res
+55    else:
+56        def multi_dot(operands):
+57            stack = operands[0]
+58            for op in operands[1:]:
+59                stack = stack @ op
+60            return stack
+61        return derived_observable(multi_dot, operands, array_mode=True)
 
@@ -493,65 +494,65 @@ Obs valued.
-
 63def jack_matmul(*operands):
- 64    """Matrix multiply both operands making use of the jackknife approximation.
- 65
- 66    Parameters
- 67    ----------
- 68    operands : numpy.ndarray
- 69        Arbitrary number of 2d-numpy arrays which can be real or complex
- 70        Obs valued.
- 71
- 72    For large matrices this is considerably faster compared to matmul.
- 73    """
- 74
- 75    def _exp_to_jack(matrix):
- 76        base_matrix = np.empty_like(matrix)
- 77        for index, entry in np.ndenumerate(matrix):
- 78            base_matrix[index] = entry.export_jackknife()
- 79        return base_matrix
- 80
- 81    def _imp_from_jack(matrix, name, idl):
- 82        base_matrix = np.empty_like(matrix)
- 83        for index, entry in np.ndenumerate(matrix):
- 84            base_matrix[index] = import_jackknife(entry, name, [idl])
- 85        return base_matrix
- 86
- 87    def _exp_to_jack_c(matrix):
- 88        base_matrix = np.empty_like(matrix)
- 89        for index, entry in np.ndenumerate(matrix):
- 90            base_matrix[index] = entry.real.export_jackknife() + 1j * entry.imag.export_jackknife()
- 91        return base_matrix
- 92
- 93    def _imp_from_jack_c(matrix, name, idl):
- 94        base_matrix = np.empty_like(matrix)
- 95        for index, entry in np.ndenumerate(matrix):
- 96            base_matrix[index] = CObs(import_jackknife(entry.real, name, [idl]),
- 97                                      import_jackknife(entry.imag, name, [idl]))
- 98        return base_matrix
- 99
-100    if any(isinstance(o.flat[0], CObs) for o in operands):
-101        name = operands[0].flat[0].real.names[0]
-102        idl = operands[0].flat[0].real.idl[name]
-103
-104        r = _exp_to_jack_c(operands[0])
-105        for op in operands[1:]:
-106            if isinstance(op.flat[0], CObs):
-107                r = r @ _exp_to_jack_c(op)
-108            else:
-109                r = r @ op
-110        return _imp_from_jack_c(r, name, idl)
-111    else:
-112        name = operands[0].flat[0].names[0]
-113        idl = operands[0].flat[0].idl[name]
-114
-115        r = _exp_to_jack(operands[0])
-116        for op in operands[1:]:
-117            if isinstance(op.flat[0], Obs):
-118                r = r @ _exp_to_jack(op)
-119            else:
-120                r = r @ op
-121        return _imp_from_jack(r, name, idl)
+            
 64def jack_matmul(*operands):
+ 65    """Matrix multiply both operands making use of the jackknife approximation.
+ 66
+ 67    Parameters
+ 68    ----------
+ 69    operands : numpy.ndarray
+ 70        Arbitrary number of 2d-numpy arrays which can be real or complex
+ 71        Obs valued.
+ 72
+ 73    For large matrices this is considerably faster compared to matmul.
+ 74    """
+ 75
+ 76    def _exp_to_jack(matrix):
+ 77        base_matrix = np.empty_like(matrix)
+ 78        for index, entry in np.ndenumerate(matrix):
+ 79            base_matrix[index] = entry.export_jackknife()
+ 80        return base_matrix
+ 81
+ 82    def _imp_from_jack(matrix, name, idl):
+ 83        base_matrix = np.empty_like(matrix)
+ 84        for index, entry in np.ndenumerate(matrix):
+ 85            base_matrix[index] = import_jackknife(entry, name, [idl])
+ 86        return base_matrix
+ 87
+ 88    def _exp_to_jack_c(matrix):
+ 89        base_matrix = np.empty_like(matrix)
+ 90        for index, entry in np.ndenumerate(matrix):
+ 91            base_matrix[index] = entry.real.export_jackknife() + 1j * entry.imag.export_jackknife()
+ 92        return base_matrix
+ 93
+ 94    def _imp_from_jack_c(matrix, name, idl):
+ 95        base_matrix = np.empty_like(matrix)
+ 96        for index, entry in np.ndenumerate(matrix):
+ 97            base_matrix[index] = CObs(import_jackknife(entry.real, name, [idl]),
+ 98                                      import_jackknife(entry.imag, name, [idl]))
+ 99        return base_matrix
+100
+101    if any(isinstance(o.flat[0], CObs) for o in operands):
+102        name = operands[0].flat[0].real.names[0]
+103        idl = operands[0].flat[0].real.idl[name]
+104
+105        r = _exp_to_jack_c(operands[0])
+106        for op in operands[1:]:
+107            if isinstance(op.flat[0], CObs):
+108                r = r @ _exp_to_jack_c(op)
+109            else:
+110                r = r @ op
+111        return _imp_from_jack_c(r, name, idl)
+112    else:
+113        name = operands[0].flat[0].names[0]
+114        idl = operands[0].flat[0].idl[name]
+115
+116        r = _exp_to_jack(operands[0])
+117        for op in operands[1:]:
+118            if isinstance(op.flat[0], Obs):
+119                r = r @ _exp_to_jack(op)
+120            else:
+121                r = r @ op
+122        return _imp_from_jack(r, name, idl)
 
@@ -580,78 +581,78 @@ Obs valued.
-
124def einsum(subscripts, *operands):
-125    """Wrapper for numpy.einsum
-126
-127    Parameters
-128    ----------
-129    subscripts : str
-130        Subscripts for summation (see numpy documentation for details)
-131    operands : numpy.ndarray
-132        Arbitrary number of 2d-numpy arrays which can be real or complex
-133        Obs valued.
-134    """
-135
-136    def _exp_to_jack(matrix):
-137        base_matrix = []
-138        for index, entry in np.ndenumerate(matrix):
-139            base_matrix.append(entry.export_jackknife())
-140        return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape)
-141
-142    def _exp_to_jack_c(matrix):
-143        base_matrix = []
-144        for index, entry in np.ndenumerate(matrix):
-145            base_matrix.append(entry.real.export_jackknife() + 1j * entry.imag.export_jackknife())
-146        return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape)
-147
-148    def _imp_from_jack(matrix, name, idl):
-149        base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object)
-150        for index in np.ndindex(matrix.shape[:-1]):
-151            base_matrix[index] = import_jackknife(matrix[index], name, [idl])
-152        return base_matrix
-153
-154    def _imp_from_jack_c(matrix, name, idl):
-155        base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object)
-156        for index in np.ndindex(matrix.shape[:-1]):
-157            base_matrix[index] = CObs(import_jackknife(matrix[index].real, name, [idl]),
-158                                      import_jackknife(matrix[index].imag, name, [idl]))
-159        return base_matrix
-160
-161    for op in operands:
-162        if isinstance(op.flat[0], CObs):
-163            name = op.flat[0].real.names[0]
-164            idl = op.flat[0].real.idl[name]
-165            break
-166        elif isinstance(op.flat[0], Obs):
-167            name = op.flat[0].names[0]
-168            idl = op.flat[0].idl[name]
-169            break
-170
-171    conv_operands = []
-172    for op in operands:
-173        if isinstance(op.flat[0], CObs):
-174            conv_operands.append(_exp_to_jack_c(op))
-175        elif isinstance(op.flat[0], Obs):
-176            conv_operands.append(_exp_to_jack(op))
-177        else:
-178            conv_operands.append(op)
-179
-180    tmp_subscripts = ','.join([o + '...' for o in subscripts.split(',')])
-181    extended_subscripts = '->'.join([o + '...' for o in tmp_subscripts.split('->')[:-1]] + [tmp_subscripts.split('->')[-1]])
-182    einsum_path = np.einsum_path(extended_subscripts, *conv_operands, optimize='optimal')[0]
-183    jack_einsum = np.einsum(extended_subscripts, *conv_operands, optimize=einsum_path)
-184
-185    if jack_einsum.dtype == complex:
-186        result = _imp_from_jack_c(jack_einsum, name, idl)
-187    elif jack_einsum.dtype == float:
-188        result = _imp_from_jack(jack_einsum, name, idl)
-189    else:
-190        raise Exception("Result has unexpected datatype")
-191
-192    if result.shape == ():
-193        return result.flat[0]
-194    else:
-195        return result
+            
125def einsum(subscripts, *operands):
+126    """Wrapper for numpy.einsum
+127
+128    Parameters
+129    ----------
+130    subscripts : str
+131        Subscripts for summation (see numpy documentation for details)
+132    operands : numpy.ndarray
+133        Arbitrary number of 2d-numpy arrays which can be real or complex
+134        Obs valued.
+135    """
+136
+137    def _exp_to_jack(matrix):
+138        base_matrix = []
+139        for _index, entry in np.ndenumerate(matrix):
+140            base_matrix.append(entry.export_jackknife())
+141        return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape)
+142
+143    def _exp_to_jack_c(matrix):
+144        base_matrix = []
+145        for _index, entry in np.ndenumerate(matrix):
+146            base_matrix.append(entry.real.export_jackknife() + 1j * entry.imag.export_jackknife())
+147        return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape)
+148
+149    def _imp_from_jack(matrix, name, idl):
+150        base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object)
+151        for index in np.ndindex(matrix.shape[:-1]):
+152            base_matrix[index] = import_jackknife(matrix[index], name, [idl])
+153        return base_matrix
+154
+155    def _imp_from_jack_c(matrix, name, idl):
+156        base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object)
+157        for index in np.ndindex(matrix.shape[:-1]):
+158            base_matrix[index] = CObs(import_jackknife(matrix[index].real, name, [idl]),
+159                                      import_jackknife(matrix[index].imag, name, [idl]))
+160        return base_matrix
+161
+162    for op in operands:
+163        if isinstance(op.flat[0], CObs):
+164            name = op.flat[0].real.names[0]
+165            idl = op.flat[0].real.idl[name]
+166            break
+167        elif isinstance(op.flat[0], Obs):
+168            name = op.flat[0].names[0]
+169            idl = op.flat[0].idl[name]
+170            break
+171
+172    conv_operands = []
+173    for op in operands:
+174        if isinstance(op.flat[0], CObs):
+175            conv_operands.append(_exp_to_jack_c(op))
+176        elif isinstance(op.flat[0], Obs):
+177            conv_operands.append(_exp_to_jack(op))
+178        else:
+179            conv_operands.append(op)
+180
+181    tmp_subscripts = ','.join([o + '...' for o in subscripts.split(',')])
+182    extended_subscripts = '->'.join([o + '...' for o in tmp_subscripts.split('->')[:-1]] + [tmp_subscripts.split('->')[-1]])
+183    einsum_path = np.einsum_path(extended_subscripts, *conv_operands, optimize='optimal')[0]
+184    jack_einsum = np.einsum(extended_subscripts, *conv_operands, optimize=einsum_path)
+185
+186    if jack_einsum.dtype == complex:
+187        result = _imp_from_jack_c(jack_einsum, name, idl)
+188    elif jack_einsum.dtype == float:
+189        result = _imp_from_jack(jack_einsum, name, idl)
+190    else:
+191        raise Exception("Result has unexpected datatype")
+192
+193    if result.shape == ():
+194        return result.flat[0]
+195    else:
+196        return result
 
@@ -681,9 +682,9 @@ Obs valued.
-
198def inv(x):
-199    """Inverse of Obs or CObs valued matrices."""
-200    return _mat_mat_op(anp.linalg.inv, x)
+            
199def inv(x):
+200    """Inverse of Obs or CObs valued matrices."""
+201    return _mat_mat_op(anp.linalg.inv, x)
 
@@ -703,11 +704,11 @@ Obs valued.
-
203def cholesky(x):
-204    """Cholesky decomposition of Obs valued matrices."""
-205    if any(isinstance(o, CObs) for o in x.ravel()):
-206        raise Exception("Cholesky decomposition is not implemented for CObs.")
-207    return _mat_mat_op(anp.linalg.cholesky, x)
+            
204def cholesky(x):
+205    """Cholesky decomposition of Obs valued matrices."""
+206    if any(isinstance(o, CObs) for o in x.ravel()):
+207        raise Exception("Cholesky decomposition is not implemented for CObs.")
+208    return _mat_mat_op(anp.linalg.cholesky, x)
 
@@ -727,9 +728,9 @@ Obs valued.
-
210def det(x):
-211    """Determinant of Obs valued matrices."""
-212    return _scalar_mat_op(anp.linalg.det, x)
+            
211def det(x):
+212    """Determinant of Obs valued matrices."""
+213    return _scalar_mat_op(anp.linalg.det, x)
 
@@ -749,11 +750,11 @@ Obs valued.
-
262def eigh(obs, **kwargs):
-263    """Computes the eigenvalues and eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh."""
-264    w = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[0], obs)
-265    v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs)
-266    return w, v
+            
263def eigh(obs, **kwargs):
+264    """Computes the eigenvalues and eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh."""
+265    w = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[0], obs)
+266    v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs)
+267    return w, v
 
@@ -773,10 +774,10 @@ Obs valued.
-
269def eig(obs, **kwargs):
-270    """Computes the eigenvalues of a given matrix of Obs according to np.linalg.eig."""
-271    w = derived_observable(lambda x, **kwargs: anp.real(anp.linalg.eig(x)[0]), obs)
-272    return w
+            
270def eig(obs, **kwargs):
+271    """Computes the eigenvalues of a given matrix of Obs according to np.linalg.eig."""
+272    w = derived_observable(lambda x, **kwargs: anp.real(anp.linalg.eig(x)[0]), obs)
+273    return w
 
@@ -796,10 +797,10 @@ Obs valued.
-
275def eigv(obs, **kwargs):
-276    """Computes the eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh."""
-277    v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs)
-278    return v
+            
276def eigv(obs, **kwargs):
+277    """Computes the eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh."""
+278    v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs)
+279    return v
 
@@ -819,9 +820,9 @@ Obs valued.
-
281def pinv(obs, **kwargs):
-282    """Computes the Moore-Penrose pseudoinverse of a matrix of Obs."""
-283    return derived_observable(lambda x, **kwargs: anp.linalg.pinv(x), obs)
+            
282def pinv(obs, **kwargs):
+283    """Computes the Moore-Penrose pseudoinverse of a matrix of Obs."""
+284    return derived_observable(lambda x, **kwargs: anp.linalg.pinv(x), obs)
 
@@ -841,12 +842,12 @@ Obs valued.
-
286def svd(obs, **kwargs):
-287    """Computes the singular value decomposition of a matrix of Obs."""
-288    u = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[0], obs)
-289    s = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[1], obs)
-290    vh = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[2], obs)
-291    return (u, s, vh)
+            
287def svd(obs, **kwargs):
+288    """Computes the singular value decomposition of a matrix of Obs."""
+289    u = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[0], obs)
+290    s = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[1], obs)
+291    vh = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[2], obs)
+292    return (u, s, vh)
 
diff --git a/docs/pyerrors/misc.html b/docs/pyerrors/misc.html index cbd41e92..cfacc211 100644 --- a/docs/pyerrors/misc.html +++ b/docs/pyerrors/misc.html @@ -91,191 +91,193 @@ -
  1import platform
-  2import numpy as np
-  3import scipy
+                        
  1import pickle
+  2import platform
+  3
   4import matplotlib
   5import matplotlib.pyplot as plt
-  6import pandas as pd
-  7import pickle
-  8from .obs import Obs
-  9from .version import __version__
- 10
- 11
- 12def print_config():
- 13    """Print information about version of python, pyerrors and dependencies."""
- 14    config = {"system": platform.system(),
- 15              "python": platform.python_version(),
- 16              "pyerrors": __version__,
- 17              "numpy": np.__version__,
- 18              "scipy": scipy.__version__,
- 19              "matplotlib": matplotlib.__version__,
- 20              "pandas": pd.__version__}
- 21
- 22    for key, value in config.items():
- 23        print(f"{key: <10}\t {value}")
- 24
- 25
- 26def errorbar(x, y, axes=plt, **kwargs):
- 27    """pyerrors wrapper for the errorbars method of matplotlib
- 28
- 29    Parameters
- 30    ----------
- 31    x : list
- 32        A list of x-values which can be Obs.
- 33    y : list
- 34        A list of y-values which can be Obs.
- 35    axes : (matplotlib.pyplot.axes)
- 36        The axes to plot on. default is plt.
- 37    """
- 38    val = {}
- 39    err = {}
- 40    for name, comp in zip(["x", "y"], [x, y]):
- 41        if all(isinstance(o, Obs) for o in comp):
- 42            if not all(hasattr(o, 'e_dvalue') for o in comp):
- 43                [o.gamma_method() for o in comp]
- 44            val[name] = [o.value for o in comp]
- 45            err[name] = [o.dvalue for o in comp]
- 46        else:
- 47            val[name] = comp
- 48            err[name] = None
- 49
- 50        if f"{name}err" in kwargs:
- 51            err[name] = kwargs.get(f"{name}err")
- 52            kwargs.pop(f"{name}err", None)
- 53
- 54    axes.errorbar(val["x"], val["y"], xerr=err["x"], yerr=err["y"], **kwargs)
+  6import numpy as np
+  7import pandas as pd
+  8import scipy
+  9
+ 10from .obs import Obs
+ 11from .version import __version__
+ 12
+ 13
+ 14def print_config():
+ 15    """Print information about version of python, pyerrors and dependencies."""
+ 16    config = {"system": platform.system(),
+ 17              "python": platform.python_version(),
+ 18              "pyerrors": __version__,
+ 19              "numpy": np.__version__,
+ 20              "scipy": scipy.__version__,
+ 21              "matplotlib": matplotlib.__version__,
+ 22              "pandas": pd.__version__}
+ 23
+ 24    for key, value in config.items():
+ 25        print(f"{key: <10}\t {value}")
+ 26
+ 27
+ 28def errorbar(x, y, axes=plt, **kwargs):
+ 29    """pyerrors wrapper for the errorbars method of matplotlib
+ 30
+ 31    Parameters
+ 32    ----------
+ 33    x : list
+ 34        A list of x-values which can be Obs.
+ 35    y : list
+ 36        A list of y-values which can be Obs.
+ 37    axes : (matplotlib.pyplot.axes)
+ 38        The axes to plot on. default is plt.
+ 39    """
+ 40    val = {}
+ 41    err = {}
+ 42    for name, comp in zip(["x", "y"], [x, y], strict=True):
+ 43        if all(isinstance(o, Obs) for o in comp):
+ 44            if not all(hasattr(o, 'e_dvalue') for o in comp):
+ 45                [o.gamma_method() for o in comp]
+ 46            val[name] = [o.value for o in comp]
+ 47            err[name] = [o.dvalue for o in comp]
+ 48        else:
+ 49            val[name] = comp
+ 50            err[name] = None
+ 51
+ 52        if f"{name}err" in kwargs:
+ 53            err[name] = kwargs.get(f"{name}err")
+ 54            kwargs.pop(f"{name}err", None)
  55
- 56
- 57def dump_object(obj, name, **kwargs):
- 58    """Dump object into pickle file.
- 59
- 60    Parameters
- 61    ----------
- 62    obj : object
- 63        object to be saved in the pickle file
- 64    name : str
- 65        name of the file
- 66    path : str
- 67        specifies a custom path for the file (default '.')
- 68
- 69    Returns
- 70    -------
- 71    None
- 72    """
- 73    if 'path' in kwargs:
- 74        file_name = kwargs.get('path') + '/' + name + '.p'
- 75    else:
- 76        file_name = name + '.p'
- 77    with open(file_name, 'wb') as fb:
- 78        pickle.dump(obj, fb)
- 79
- 80
- 81def load_object(path):
- 82    """Load object from pickle file.
- 83
- 84    Parameters
- 85    ----------
- 86    path : str
- 87        path to the file
- 88
- 89    Returns
- 90    -------
- 91    object : Obs
- 92        Loaded Object
- 93    """
- 94    with open(path, 'rb') as file:
- 95        return pickle.load(file)
- 96
- 97
- 98def pseudo_Obs(value, dvalue, name, samples=1000):
- 99    """Generate an Obs object with given value, dvalue and name for test purposes
-100
-101    Parameters
-102    ----------
-103    value : float
-104        central value of the Obs to be generated.
-105    dvalue : float
-106        error of the Obs to be generated.
-107    name : str
-108        name of the ensemble for which the Obs is to be generated.
-109    samples: int
-110        number of samples for the Obs (default 1000).
-111
-112    Returns
-113    -------
-114    res : Obs
-115        Generated Observable
-116    """
-117    if dvalue <= 0.0:
-118        return Obs([np.zeros(samples) + value], [name])
-119    else:
-120        for _ in range(100):
-121            deltas = [np.random.normal(0.0, dvalue * np.sqrt(samples), samples)]
-122            deltas -= np.mean(deltas)
-123            deltas *= dvalue / np.sqrt((np.var(deltas) / samples)) / np.sqrt(1 + 3 / samples)
-124            deltas += value
-125            res = Obs(deltas, [name])
-126            res.gamma_method(S=2, tau_exp=0)
-127            if abs(res.dvalue - dvalue) < 1e-10 * dvalue:
-128                break
-129
-130        res._value = float(value)
+ 56    axes.errorbar(val["x"], val["y"], xerr=err["x"], yerr=err["y"], **kwargs)
+ 57
+ 58
+ 59def dump_object(obj, name, **kwargs):
+ 60    """Dump object into pickle file.
+ 61
+ 62    Parameters
+ 63    ----------
+ 64    obj : object
+ 65        object to be saved in the pickle file
+ 66    name : str
+ 67        name of the file
+ 68    path : str
+ 69        specifies a custom path for the file (default '.')
+ 70
+ 71    Returns
+ 72    -------
+ 73    None
+ 74    """
+ 75    if 'path' in kwargs:
+ 76        file_name = kwargs.get('path') + '/' + name + '.p'
+ 77    else:
+ 78        file_name = name + '.p'
+ 79    with open(file_name, 'wb') as fb:
+ 80        pickle.dump(obj, fb)
+ 81
+ 82
+ 83def load_object(path):
+ 84    """Load object from pickle file.
+ 85
+ 86    Parameters
+ 87    ----------
+ 88    path : str
+ 89        path to the file
+ 90
+ 91    Returns
+ 92    -------
+ 93    object : Obs
+ 94        Loaded Object
+ 95    """
+ 96    with open(path, 'rb') as file:
+ 97        return pickle.load(file)
+ 98
+ 99
+100def pseudo_Obs(value, dvalue, name, samples=1000):
+101    """Generate an Obs object with given value, dvalue and name for test purposes
+102
+103    Parameters
+104    ----------
+105    value : float
+106        central value of the Obs to be generated.
+107    dvalue : float
+108        error of the Obs to be generated.
+109    name : str
+110        name of the ensemble for which the Obs is to be generated.
+111    samples: int
+112        number of samples for the Obs (default 1000).
+113
+114    Returns
+115    -------
+116    res : Obs
+117        Generated Observable
+118    """
+119    if dvalue <= 0.0:
+120        return Obs([np.zeros(samples) + value], [name])
+121    else:
+122        for _ in range(100):
+123            deltas = [np.random.normal(0.0, dvalue * np.sqrt(samples), samples)]  # noqa: NPY002
+124            deltas -= np.mean(deltas)
+125            deltas *= dvalue / np.sqrt(np.var(deltas) / samples) / np.sqrt(1 + 3 / samples)
+126            deltas += value
+127            res = Obs(deltas, [name])
+128            res.gamma_method(S=2, tau_exp=0)
+129            if abs(res.dvalue - dvalue) < 1e-10 * dvalue:
+130                break
 131
-132        return res
+132        res._value = float(value)
 133
-134
-135def gen_correlated_data(means, cov, name, tau=0.5, samples=1000):
-136    """ Generate observables with given covariance and autocorrelation times.
-137
-138    Parameters
-139    ----------
-140    means : list
-141        list containing the mean value of each observable.
-142    cov : numpy.ndarray
-143        covariance matrix for the data to be generated.
-144    name : str
-145        ensemble name for the data to be geneated.
-146    tau : float or list
-147        can either be a real number or a list with an entry for
-148        every dataset.
-149    samples : int
-150        number of samples to be generated for each observable.
-151
-152    Returns
-153    -------
-154    corr_obs : list[Obs]
-155        Generated observable list
-156    """
-157
-158    assert len(means) == cov.shape[-1]
-159    tau = np.asarray(tau)
-160    if np.min(tau) < 0.5:
-161        raise Exception('All integrated autocorrelations have to be >= 0.5.')
-162
-163    a = (2 * tau - 1) / (2 * tau + 1)
-164    rand = np.random.multivariate_normal(np.zeros_like(means), cov * samples, samples)
-165
-166    # Normalize samples such that sample variance matches input
-167    norm = np.array([np.var(o, ddof=1) / samples for o in rand.T])
-168    rand = rand @ np.diag(np.sqrt(np.diag(cov))) @ np.diag(1 / np.sqrt(norm))
-169
-170    data = [rand[0]]
-171    for i in range(1, samples):
-172        data.append(np.sqrt(1 - a ** 2) * rand[i] + a * data[-1])
-173    corr_data = np.array(data) - np.mean(data, axis=0) + means
-174    return [Obs([dat], [name]) for dat in corr_data.T]
-175
-176
-177def _assert_equal_properties(ol, otype=Obs):
-178    otype = type(ol[0])
-179    for o in ol[1:]:
-180        if not isinstance(o, otype):
-181            raise Exception("Wrong data type in list.")
-182        for attr in ["reweighted", "e_content", "idl"]:
-183            if hasattr(ol[0], attr):
-184                if not getattr(ol[0], attr) == getattr(o, attr):
-185                    raise Exception(f"All Obs in list have to have the same state '{attr}'.")
+134        return res
+135
+136
+137def gen_correlated_data(means, cov, name, tau=0.5, samples=1000):
+138    """ Generate observables with given covariance and autocorrelation times.
+139
+140    Parameters
+141    ----------
+142    means : list
+143        list containing the mean value of each observable.
+144    cov : numpy.ndarray
+145        covariance matrix for the data to be generated.
+146    name : str
+147        ensemble name for the data to be geneated.
+148    tau : float or list
+149        can either be a real number or a list with an entry for
+150        every dataset.
+151    samples : int
+152        number of samples to be generated for each observable.
+153
+154    Returns
+155    -------
+156    corr_obs : list[Obs]
+157        Generated observable list
+158    """
+159
+160    assert len(means) == cov.shape[-1]
+161    tau = np.asarray(tau)
+162    if np.min(tau) < 0.5:
+163        raise Exception('All integrated autocorrelations have to be >= 0.5.')
+164
+165    a = (2 * tau - 1) / (2 * tau + 1)
+166    rand = np.random.multivariate_normal(np.zeros_like(means), cov * samples, samples)  # noqa: NPY002
+167
+168    # Normalize samples such that sample variance matches input
+169    norm = np.array([np.var(o, ddof=1) / samples for o in rand.T])
+170    rand = rand @ np.diag(np.sqrt(np.diag(cov))) @ np.diag(1 / np.sqrt(norm))
+171
+172    data = [rand[0]]
+173    for i in range(1, samples):
+174        data.append(np.sqrt(1 - a ** 2) * rand[i] + a * data[-1])
+175    corr_data = np.array(data) - np.mean(data, axis=0) + means
+176    return [Obs([dat], [name]) for dat in corr_data.T]
+177
+178
+179def _assert_equal_properties(ol, otype=Obs):
+180    otype = type(ol[0])
+181    for o in ol[1:]:
+182        if not isinstance(o, otype):
+183            raise Exception("Wrong data type in list.")
+184        for attr in ["reweighted", "e_content", "idl"]:
+185            if hasattr(ol[0], attr):
+186                if not getattr(ol[0], attr) == getattr(o, attr):
+187                    raise Exception(f"All Obs in list have to have the same state '{attr}'.")
 
@@ -291,18 +293,18 @@
-
13def print_config():
-14    """Print information about version of python, pyerrors and dependencies."""
-15    config = {"system": platform.system(),
-16              "python": platform.python_version(),
-17              "pyerrors": __version__,
-18              "numpy": np.__version__,
-19              "scipy": scipy.__version__,
-20              "matplotlib": matplotlib.__version__,
-21              "pandas": pd.__version__}
-22
-23    for key, value in config.items():
-24        print(f"{key: <10}\t {value}")
+            
15def print_config():
+16    """Print information about version of python, pyerrors and dependencies."""
+17    config = {"system": platform.system(),
+18              "python": platform.python_version(),
+19              "pyerrors": __version__,
+20              "numpy": np.__version__,
+21              "scipy": scipy.__version__,
+22              "matplotlib": matplotlib.__version__,
+23              "pandas": pd.__version__}
+24
+25    for key, value in config.items():
+26        print(f"{key: <10}\t {value}")
 
@@ -322,35 +324,35 @@
-
27def errorbar(x, y, axes=plt, **kwargs):
-28    """pyerrors wrapper for the errorbars method of matplotlib
-29
-30    Parameters
-31    ----------
-32    x : list
-33        A list of x-values which can be Obs.
-34    y : list
-35        A list of y-values which can be Obs.
-36    axes : (matplotlib.pyplot.axes)
-37        The axes to plot on. default is plt.
-38    """
-39    val = {}
-40    err = {}
-41    for name, comp in zip(["x", "y"], [x, y]):
-42        if all(isinstance(o, Obs) for o in comp):
-43            if not all(hasattr(o, 'e_dvalue') for o in comp):
-44                [o.gamma_method() for o in comp]
-45            val[name] = [o.value for o in comp]
-46            err[name] = [o.dvalue for o in comp]
-47        else:
-48            val[name] = comp
-49            err[name] = None
-50
-51        if f"{name}err" in kwargs:
-52            err[name] = kwargs.get(f"{name}err")
-53            kwargs.pop(f"{name}err", None)
-54
-55    axes.errorbar(val["x"], val["y"], xerr=err["x"], yerr=err["y"], **kwargs)
+            
29def errorbar(x, y, axes=plt, **kwargs):
+30    """pyerrors wrapper for the errorbars method of matplotlib
+31
+32    Parameters
+33    ----------
+34    x : list
+35        A list of x-values which can be Obs.
+36    y : list
+37        A list of y-values which can be Obs.
+38    axes : (matplotlib.pyplot.axes)
+39        The axes to plot on. default is plt.
+40    """
+41    val = {}
+42    err = {}
+43    for name, comp in zip(["x", "y"], [x, y], strict=True):
+44        if all(isinstance(o, Obs) for o in comp):
+45            if not all(hasattr(o, 'e_dvalue') for o in comp):
+46                [o.gamma_method() for o in comp]
+47            val[name] = [o.value for o in comp]
+48            err[name] = [o.dvalue for o in comp]
+49        else:
+50            val[name] = comp
+51            err[name] = None
+52
+53        if f"{name}err" in kwargs:
+54            err[name] = kwargs.get(f"{name}err")
+55            kwargs.pop(f"{name}err", None)
+56
+57    axes.errorbar(val["x"], val["y"], xerr=err["x"], yerr=err["y"], **kwargs)
 
@@ -381,28 +383,28 @@ The axes to plot on. default is plt.
-
58def dump_object(obj, name, **kwargs):
-59    """Dump object into pickle file.
-60
-61    Parameters
-62    ----------
-63    obj : object
-64        object to be saved in the pickle file
-65    name : str
-66        name of the file
-67    path : str
-68        specifies a custom path for the file (default '.')
-69
-70    Returns
-71    -------
-72    None
-73    """
-74    if 'path' in kwargs:
-75        file_name = kwargs.get('path') + '/' + name + '.p'
-76    else:
-77        file_name = name + '.p'
-78    with open(file_name, 'wb') as fb:
-79        pickle.dump(obj, fb)
+            
60def dump_object(obj, name, **kwargs):
+61    """Dump object into pickle file.
+62
+63    Parameters
+64    ----------
+65    obj : object
+66        object to be saved in the pickle file
+67    name : str
+68        name of the file
+69    path : str
+70        specifies a custom path for the file (default '.')
+71
+72    Returns
+73    -------
+74    None
+75    """
+76    if 'path' in kwargs:
+77        file_name = kwargs.get('path') + '/' + name + '.p'
+78    else:
+79        file_name = name + '.p'
+80    with open(file_name, 'wb') as fb:
+81        pickle.dump(obj, fb)
 
@@ -439,21 +441,21 @@ specifies a custom path for the file (default '.')
-
82def load_object(path):
-83    """Load object from pickle file.
-84
-85    Parameters
-86    ----------
-87    path : str
-88        path to the file
-89
-90    Returns
-91    -------
-92    object : Obs
-93        Loaded Object
-94    """
-95    with open(path, 'rb') as file:
-96        return pickle.load(file)
+            
84def load_object(path):
+85    """Load object from pickle file.
+86
+87    Parameters
+88    ----------
+89    path : str
+90        path to the file
+91
+92    Returns
+93    -------
+94    object : Obs
+95        Loaded Object
+96    """
+97    with open(path, 'rb') as file:
+98        return pickle.load(file)
 
@@ -487,41 +489,41 @@ Loaded Object
-
 99def pseudo_Obs(value, dvalue, name, samples=1000):
-100    """Generate an Obs object with given value, dvalue and name for test purposes
-101
-102    Parameters
-103    ----------
-104    value : float
-105        central value of the Obs to be generated.
-106    dvalue : float
-107        error of the Obs to be generated.
-108    name : str
-109        name of the ensemble for which the Obs is to be generated.
-110    samples: int
-111        number of samples for the Obs (default 1000).
-112
-113    Returns
-114    -------
-115    res : Obs
-116        Generated Observable
-117    """
-118    if dvalue <= 0.0:
-119        return Obs([np.zeros(samples) + value], [name])
-120    else:
-121        for _ in range(100):
-122            deltas = [np.random.normal(0.0, dvalue * np.sqrt(samples), samples)]
-123            deltas -= np.mean(deltas)
-124            deltas *= dvalue / np.sqrt((np.var(deltas) / samples)) / np.sqrt(1 + 3 / samples)
-125            deltas += value
-126            res = Obs(deltas, [name])
-127            res.gamma_method(S=2, tau_exp=0)
-128            if abs(res.dvalue - dvalue) < 1e-10 * dvalue:
-129                break
-130
-131        res._value = float(value)
+            
101def pseudo_Obs(value, dvalue, name, samples=1000):
+102    """Generate an Obs object with given value, dvalue and name for test purposes
+103
+104    Parameters
+105    ----------
+106    value : float
+107        central value of the Obs to be generated.
+108    dvalue : float
+109        error of the Obs to be generated.
+110    name : str
+111        name of the ensemble for which the Obs is to be generated.
+112    samples: int
+113        number of samples for the Obs (default 1000).
+114
+115    Returns
+116    -------
+117    res : Obs
+118        Generated Observable
+119    """
+120    if dvalue <= 0.0:
+121        return Obs([np.zeros(samples) + value], [name])
+122    else:
+123        for _ in range(100):
+124            deltas = [np.random.normal(0.0, dvalue * np.sqrt(samples), samples)]  # noqa: NPY002
+125            deltas -= np.mean(deltas)
+126            deltas *= dvalue / np.sqrt(np.var(deltas) / samples) / np.sqrt(1 + 3 / samples)
+127            deltas += value
+128            res = Obs(deltas, [name])
+129            res.gamma_method(S=2, tau_exp=0)
+130            if abs(res.dvalue - dvalue) < 1e-10 * dvalue:
+131                break
 132
-133        return res
+133        res._value = float(value)
+134
+135        return res
 
@@ -561,46 +563,46 @@ Generated Observable
-
136def gen_correlated_data(means, cov, name, tau=0.5, samples=1000):
-137    """ Generate observables with given covariance and autocorrelation times.
-138
-139    Parameters
-140    ----------
-141    means : list
-142        list containing the mean value of each observable.
-143    cov : numpy.ndarray
-144        covariance matrix for the data to be generated.
-145    name : str
-146        ensemble name for the data to be geneated.
-147    tau : float or list
-148        can either be a real number or a list with an entry for
-149        every dataset.
-150    samples : int
-151        number of samples to be generated for each observable.
-152
-153    Returns
-154    -------
-155    corr_obs : list[Obs]
-156        Generated observable list
-157    """
-158
-159    assert len(means) == cov.shape[-1]
-160    tau = np.asarray(tau)
-161    if np.min(tau) < 0.5:
-162        raise Exception('All integrated autocorrelations have to be >= 0.5.')
-163
-164    a = (2 * tau - 1) / (2 * tau + 1)
-165    rand = np.random.multivariate_normal(np.zeros_like(means), cov * samples, samples)
-166
-167    # Normalize samples such that sample variance matches input
-168    norm = np.array([np.var(o, ddof=1) / samples for o in rand.T])
-169    rand = rand @ np.diag(np.sqrt(np.diag(cov))) @ np.diag(1 / np.sqrt(norm))
-170
-171    data = [rand[0]]
-172    for i in range(1, samples):
-173        data.append(np.sqrt(1 - a ** 2) * rand[i] + a * data[-1])
-174    corr_data = np.array(data) - np.mean(data, axis=0) + means
-175    return [Obs([dat], [name]) for dat in corr_data.T]
+            
138def gen_correlated_data(means, cov, name, tau=0.5, samples=1000):
+139    """ Generate observables with given covariance and autocorrelation times.
+140
+141    Parameters
+142    ----------
+143    means : list
+144        list containing the mean value of each observable.
+145    cov : numpy.ndarray
+146        covariance matrix for the data to be generated.
+147    name : str
+148        ensemble name for the data to be geneated.
+149    tau : float or list
+150        can either be a real number or a list with an entry for
+151        every dataset.
+152    samples : int
+153        number of samples to be generated for each observable.
+154
+155    Returns
+156    -------
+157    corr_obs : list[Obs]
+158        Generated observable list
+159    """
+160
+161    assert len(means) == cov.shape[-1]
+162    tau = np.asarray(tau)
+163    if np.min(tau) < 0.5:
+164        raise Exception('All integrated autocorrelations have to be >= 0.5.')
+165
+166    a = (2 * tau - 1) / (2 * tau + 1)
+167    rand = np.random.multivariate_normal(np.zeros_like(means), cov * samples, samples)  # noqa: NPY002
+168
+169    # Normalize samples such that sample variance matches input
+170    norm = np.array([np.var(o, ddof=1) / samples for o in rand.T])
+171    rand = rand @ np.diag(np.sqrt(np.diag(cov))) @ np.diag(1 / np.sqrt(norm))
+172
+173    data = [rand[0]]
+174    for i in range(1, samples):
+175        data.append(np.sqrt(1 - a ** 2) * rand[i] + a * data[-1])
+176    corr_data = np.array(data) - np.mean(data, axis=0) + means
+177    return [Obs([dat], [name]) for dat in corr_data.T]
 
diff --git a/docs/pyerrors/mpm.html b/docs/pyerrors/mpm.html index 73bff584..2ae95e77 100644 --- a/docs/pyerrors/mpm.html +++ b/docs/pyerrors/mpm.html @@ -78,67 +78,68 @@
 1import numpy as np
  2import scipy.linalg
- 3from .obs import Obs
- 4from .linalg import svd, eig
- 5
+ 3
+ 4from .linalg import eig, svd
+ 5from .obs import Obs
  6
- 7def matrix_pencil_method(corrs, k=1, p=None, **kwargs):
- 8    """Matrix pencil method to extract k energy levels from data
- 9
-10    Implementation of the matrix pencil method based on
-11    eq. (2.17) of Y. Hua, T. K. Sarkar, IEEE Trans. Acoust. 38, 814-824 (1990)
-12
-13    Parameters
-14    ----------
-15    data : list
-16        can be a list of Obs for the analysis of a single correlator, or a list of lists
-17        of Obs if several correlators are to analyzed at once.
-18    k : int
-19        Number of states to extract (default 1).
-20    p : int
-21        matrix pencil parameter which filters noise. The optimal value is expected between
-22        len(data)/3 and 2*len(data)/3. The computation is more expensive the closer p is
-23        to len(data)/2 but could possibly suppress more noise (default len(data)//2).
-24
-25    Returns
-26    -------
-27    energy_levels : list[Obs]
-28        Extracted energy levels
-29    """
-30    if isinstance(corrs[0], Obs):
-31        data = [corrs]
-32    else:
-33        data = corrs
-34
-35    lengths = [len(d) for d in data]
-36    if lengths.count(lengths[0]) != len(lengths):
-37        raise Exception('All datasets have to have the same length.')
-38
-39    data_sets = len(data)
-40    n_data = len(data[0])
-41
-42    if p is None:
-43        p = max(n_data // 2, k)
-44    if n_data <= p:
-45        raise Exception('The pencil p has to be smaller than the number of data samples.')
-46    if p < k or n_data - p < k:
-47        raise Exception('Cannot extract', k, 'energy levels with p=', p, 'and N-p=', n_data - p)
-48
-49    # Construct the hankel matrices
-50    matrix = []
-51    for n in range(data_sets):
-52        matrix.append(scipy.linalg.hankel(data[n][:n_data - p], data[n][n_data - p - 1:]))
-53    matrix = np.array(matrix)
-54    # Construct y1 and y2
-55    y1 = np.concatenate(matrix[:, :, :p])
-56    y2 = np.concatenate(matrix[:, :, 1:])
-57    # Apply SVD to y2
-58    u, s, vh = svd(y2, **kwargs)
-59    # Construct z from y1 and SVD of y2, setting all singular values beyond the kth to zero
-60    z = np.diag(1. / s[:k]) @ u[:, :k].T @ y1 @ vh.T[:, :k]
-61    # Return the sorted logarithms of the real eigenvalues as Obs
-62    energy_levels = np.log(np.abs(eig(z, **kwargs)))
-63    return sorted(energy_levels, key=lambda x: abs(x.value))
+ 7
+ 8def matrix_pencil_method(corrs, k=1, p=None, **kwargs):
+ 9    """Matrix pencil method to extract k energy levels from data
+10
+11    Implementation of the matrix pencil method based on
+12    eq. (2.17) of Y. Hua, T. K. Sarkar, IEEE Trans. Acoust. 38, 814-824 (1990)
+13
+14    Parameters
+15    ----------
+16    data : list
+17        can be a list of Obs for the analysis of a single correlator, or a list of lists
+18        of Obs if several correlators are to analyzed at once.
+19    k : int
+20        Number of states to extract (default 1).
+21    p : int
+22        matrix pencil parameter which filters noise. The optimal value is expected between
+23        len(data)/3 and 2*len(data)/3. The computation is more expensive the closer p is
+24        to len(data)/2 but could possibly suppress more noise (default len(data)//2).
+25
+26    Returns
+27    -------
+28    energy_levels : list[Obs]
+29        Extracted energy levels
+30    """
+31    if isinstance(corrs[0], Obs):
+32        data = [corrs]
+33    else:
+34        data = corrs
+35
+36    lengths = [len(d) for d in data]
+37    if lengths.count(lengths[0]) != len(lengths):
+38        raise Exception('All datasets have to have the same length.')
+39
+40    data_sets = len(data)
+41    n_data = len(data[0])
+42
+43    if p is None:
+44        p = max(n_data // 2, k)
+45    if n_data <= p:
+46        raise Exception('The pencil p has to be smaller than the number of data samples.')
+47    if p < k or n_data - p < k:
+48        raise Exception('Cannot extract', k, 'energy levels with p=', p, 'and N-p=', n_data - p)
+49
+50    # Construct the hankel matrices
+51    matrix = []
+52    for n in range(data_sets):
+53        matrix.append(scipy.linalg.hankel(data[n][:n_data - p], data[n][n_data - p - 1:]))
+54    matrix = np.array(matrix)
+55    # Construct y1 and y2
+56    y1 = np.concatenate(matrix[:, :, :p])
+57    y2 = np.concatenate(matrix[:, :, 1:])
+58    # Apply SVD to y2
+59    u, s, vh = svd(y2, **kwargs)
+60    # Construct z from y1 and SVD of y2, setting all singular values beyond the kth to zero
+61    z = np.diag(1. / s[:k]) @ u[:, :k].T @ y1 @ vh.T[:, :k]
+62    # Return the sorted logarithms of the real eigenvalues as Obs
+63    energy_levels = np.log(np.abs(eig(z, **kwargs)))
+64    return sorted(energy_levels, key=lambda x: abs(x.value))
 
@@ -154,63 +155,63 @@
-
 8def matrix_pencil_method(corrs, k=1, p=None, **kwargs):
- 9    """Matrix pencil method to extract k energy levels from data
-10
-11    Implementation of the matrix pencil method based on
-12    eq. (2.17) of Y. Hua, T. K. Sarkar, IEEE Trans. Acoust. 38, 814-824 (1990)
-13
-14    Parameters
-15    ----------
-16    data : list
-17        can be a list of Obs for the analysis of a single correlator, or a list of lists
-18        of Obs if several correlators are to analyzed at once.
-19    k : int
-20        Number of states to extract (default 1).
-21    p : int
-22        matrix pencil parameter which filters noise. The optimal value is expected between
-23        len(data)/3 and 2*len(data)/3. The computation is more expensive the closer p is
-24        to len(data)/2 but could possibly suppress more noise (default len(data)//2).
-25
-26    Returns
-27    -------
-28    energy_levels : list[Obs]
-29        Extracted energy levels
-30    """
-31    if isinstance(corrs[0], Obs):
-32        data = [corrs]
-33    else:
-34        data = corrs
-35
-36    lengths = [len(d) for d in data]
-37    if lengths.count(lengths[0]) != len(lengths):
-38        raise Exception('All datasets have to have the same length.')
-39
-40    data_sets = len(data)
-41    n_data = len(data[0])
-42
-43    if p is None:
-44        p = max(n_data // 2, k)
-45    if n_data <= p:
-46        raise Exception('The pencil p has to be smaller than the number of data samples.')
-47    if p < k or n_data - p < k:
-48        raise Exception('Cannot extract', k, 'energy levels with p=', p, 'and N-p=', n_data - p)
-49
-50    # Construct the hankel matrices
-51    matrix = []
-52    for n in range(data_sets):
-53        matrix.append(scipy.linalg.hankel(data[n][:n_data - p], data[n][n_data - p - 1:]))
-54    matrix = np.array(matrix)
-55    # Construct y1 and y2
-56    y1 = np.concatenate(matrix[:, :, :p])
-57    y2 = np.concatenate(matrix[:, :, 1:])
-58    # Apply SVD to y2
-59    u, s, vh = svd(y2, **kwargs)
-60    # Construct z from y1 and SVD of y2, setting all singular values beyond the kth to zero
-61    z = np.diag(1. / s[:k]) @ u[:, :k].T @ y1 @ vh.T[:, :k]
-62    # Return the sorted logarithms of the real eigenvalues as Obs
-63    energy_levels = np.log(np.abs(eig(z, **kwargs)))
-64    return sorted(energy_levels, key=lambda x: abs(x.value))
+            
 9def matrix_pencil_method(corrs, k=1, p=None, **kwargs):
+10    """Matrix pencil method to extract k energy levels from data
+11
+12    Implementation of the matrix pencil method based on
+13    eq. (2.17) of Y. Hua, T. K. Sarkar, IEEE Trans. Acoust. 38, 814-824 (1990)
+14
+15    Parameters
+16    ----------
+17    data : list
+18        can be a list of Obs for the analysis of a single correlator, or a list of lists
+19        of Obs if several correlators are to analyzed at once.
+20    k : int
+21        Number of states to extract (default 1).
+22    p : int
+23        matrix pencil parameter which filters noise. The optimal value is expected between
+24        len(data)/3 and 2*len(data)/3. The computation is more expensive the closer p is
+25        to len(data)/2 but could possibly suppress more noise (default len(data)//2).
+26
+27    Returns
+28    -------
+29    energy_levels : list[Obs]
+30        Extracted energy levels
+31    """
+32    if isinstance(corrs[0], Obs):
+33        data = [corrs]
+34    else:
+35        data = corrs
+36
+37    lengths = [len(d) for d in data]
+38    if lengths.count(lengths[0]) != len(lengths):
+39        raise Exception('All datasets have to have the same length.')
+40
+41    data_sets = len(data)
+42    n_data = len(data[0])
+43
+44    if p is None:
+45        p = max(n_data // 2, k)
+46    if n_data <= p:
+47        raise Exception('The pencil p has to be smaller than the number of data samples.')
+48    if p < k or n_data - p < k:
+49        raise Exception('Cannot extract', k, 'energy levels with p=', p, 'and N-p=', n_data - p)
+50
+51    # Construct the hankel matrices
+52    matrix = []
+53    for n in range(data_sets):
+54        matrix.append(scipy.linalg.hankel(data[n][:n_data - p], data[n][n_data - p - 1:]))
+55    matrix = np.array(matrix)
+56    # Construct y1 and y2
+57    y1 = np.concatenate(matrix[:, :, :p])
+58    y2 = np.concatenate(matrix[:, :, 1:])
+59    # Apply SVD to y2
+60    u, s, vh = svd(y2, **kwargs)
+61    # Construct z from y1 and SVD of y2, setting all singular values beyond the kth to zero
+62    z = np.diag(1. / s[:k]) @ u[:, :k].T @ y1 @ vh.T[:, :k]
+63    # Return the sorted logarithms of the real eigenvalues as Obs
+64    energy_levels = np.log(np.abs(eig(z, **kwargs)))
+65    return sorted(energy_levels, key=lambda x: abs(x.value))
 
diff --git a/docs/pyerrors/obs.html b/docs/pyerrors/obs.html index 2093a7fc..63becad6 100644 --- a/docs/pyerrors/obs.html +++ b/docs/pyerrors/obs.html @@ -334,1874 +334,1901 @@ -
   1import warnings
-   2import hashlib
-   3import pickle
-   4import numpy as np
-   5import autograd.numpy as anp  # Thinly-wrapped numpy
-   6import scipy
-   7from autograd import jacobian
+                        
   1import hashlib
+   2import pickle
+   3import warnings
+   4from itertools import groupby
+   5from typing import ClassVar
+   6
+   7import autograd.numpy as anp  # Thinly-wrapped numpy
    8import matplotlib.pyplot as plt
-   9from scipy.stats import skew, skewtest, kurtosis, kurtosistest
-  10import numdifftools as nd
-  11from itertools import groupby
-  12from .covobs import Covobs
-  13
-  14# Improve print output of numpy.ndarrays containing Obs objects.
-  15np.set_printoptions(formatter={'object': lambda x: str(x)})
+   9import numdifftools as nd
+  10import numpy as np
+  11import scipy
+  12from autograd import jacobian
+  13from scipy.stats import kurtosis, kurtosistest, skew, skewtest
+  14
+  15from .covobs import Covobs
   16
-  17
-  18class Obs:
-  19    """Class for a general observable.
+  17# Improve print output of numpy.ndarrays containing Obs objects.
+  18np.set_printoptions(formatter={'object': str})
+  19
   20
-  21    Instances of Obs are the basic objects of a pyerrors error analysis.
-  22    They are initialized with a list which contains arrays of samples for
-  23    different ensembles/replica and another list of same length which contains
-  24    the names of the ensembles/replica. Mathematical operations can be
-  25    performed on instances. The result is another instance of Obs. The error of
-  26    an instance can be computed with the gamma_method. Also contains additional
-  27    methods for output and visualization of the error calculation.
-  28
-  29    Attributes
-  30    ----------
-  31    S_global : float
-  32        Standard value for S (default 2.0)
-  33    S_dict : dict
-  34        Dictionary for S values. If an entry for a given ensemble
-  35        exists this overwrites the standard value for that ensemble.
-  36    tau_exp_global : float
-  37        Standard value for tau_exp (default 0.0)
-  38    tau_exp_dict : dict
-  39        Dictionary for tau_exp values. If an entry for a given ensemble exists
-  40        this overwrites the standard value for that ensemble.
-  41    N_sigma_global : float
-  42        Standard value for N_sigma (default 1.0)
-  43    N_sigma_dict : dict
-  44        Dictionary for N_sigma values. If an entry for a given ensemble exists
-  45        this overwrites the standard value for that ensemble.
-  46    """
-  47    __slots__ = ['names', 'shape', 'r_values', 'deltas', 'N', '_value', '_dvalue',
-  48                 'ddvalue', 'reweighted', 'S', 'tau_exp', 'N_sigma',
-  49                 'e_dvalue', 'e_ddvalue', 'e_tauint', 'e_dtauint',
-  50                 'e_windowsize', 'e_rho', 'e_drho', 'e_n_tauint', 'e_n_dtauint',
-  51                 'idl', 'tag', '_covobs', '__dict__']
-  52
-  53    S_global = 2.0
-  54    S_dict = {}
-  55    tau_exp_global = 0.0
-  56    tau_exp_dict = {}
-  57    N_sigma_global = 1.0
-  58    N_sigma_dict = {}
-  59
-  60    def __init__(self, samples, names, idl=None, **kwargs):
-  61        """ Initialize Obs object.
-  62
-  63        Parameters
-  64        ----------
-  65        samples : list
-  66            list of numpy arrays containing the Monte Carlo samples
-  67        names : list
-  68            list of strings labeling the individual samples
-  69        idl : list, optional
-  70            list of ranges or lists on which the samples are defined
-  71        """
-  72
-  73        if kwargs.get("means") is None and len(samples):
-  74            if len(samples) != len(names):
-  75                raise ValueError('Length of samples and names incompatible.')
-  76            if idl is not None:
-  77                if len(idl) != len(names):
-  78                    raise ValueError('Length of idl incompatible with samples and names.')
-  79            name_length = len(names)
-  80            if name_length > 1:
-  81                if name_length != len(set(names)):
-  82                    raise ValueError('Names are not unique.')
-  83                if not all(isinstance(x, str) for x in names):
-  84                    raise TypeError('All names have to be strings.')
-  85                if len(set([o.split('|')[0] for o in names])) > 1:
-  86                    raise ValueError('Cannot initialize Obs based on multiple ensembles. Please average separate Obs from each ensemble.')
-  87            else:
-  88                if not isinstance(names[0], str):
-  89                    raise TypeError('All names have to be strings.')
-  90            if min(len(x) for x in samples) <= 4:
-  91                raise ValueError('Samples have to have at least 5 entries.')
-  92
-  93        self.names = sorted(names)
-  94        self.shape = {}
-  95        self.r_values = {}
-  96        self.deltas = {}
-  97        self._covobs = {}
-  98
-  99        self._value = 0
- 100        self.N = 0
- 101        self.idl = {}
- 102        if idl is not None:
- 103            for name, idx in sorted(zip(names, idl)):
- 104                if isinstance(idx, range):
- 105                    self.idl[name] = idx
- 106                elif isinstance(idx, (list, np.ndarray)):
- 107                    dc = np.unique(np.diff(idx))
- 108                    if np.any(dc < 0):
- 109                        raise ValueError("Unsorted idx for idl[%s] at position %s" % (name, ' '.join(['%s' % (pos + 1) for pos in np.where(np.diff(idx) < 0)[0]])))
- 110                    elif np.any(dc == 0):
- 111                        raise ValueError("Duplicate entries in idx for idl[%s] at position %s" % (name, ' '.join(['%s' % (pos + 1) for pos in np.where(np.diff(idx) == 0)[0]])))
- 112                    if len(dc) == 1:
- 113                        self.idl[name] = range(idx[0], idx[-1] + dc[0], dc[0])
- 114                    else:
- 115                        self.idl[name] = list(idx)
- 116                else:
- 117                    raise TypeError('incompatible type for idl[%s].' % (name))
- 118        else:
- 119            for name, sample in sorted(zip(names, samples)):
- 120                self.idl[name] = range(1, len(sample) + 1)
- 121
- 122        if kwargs.get("means") is not None:
- 123            for name, sample, mean in sorted(zip(names, samples, kwargs.get("means"))):
- 124                self.shape[name] = len(self.idl[name])
- 125                self.N += self.shape[name]
- 126                self.r_values[name] = mean
- 127                self.deltas[name] = sample
- 128        else:
- 129            for name, sample in sorted(zip(names, samples)):
- 130                self.shape[name] = len(self.idl[name])
- 131                self.N += self.shape[name]
- 132                if len(sample) != self.shape[name]:
- 133                    raise ValueError('Incompatible samples and idx for %s: %d vs. %d' % (name, len(sample), self.shape[name]))
- 134                self.r_values[name] = np.mean(sample)
- 135                self.deltas[name] = sample - self.r_values[name]
- 136                self._value += self.shape[name] * self.r_values[name]
- 137            self._value /= self.N
- 138
- 139        self._dvalue = 0.0
- 140        self.ddvalue = 0.0
- 141        self.reweighted = False
- 142
- 143        self.tag = None
- 144
- 145    @property
- 146    def value(self):
- 147        return self._value
- 148
- 149    @property
- 150    def dvalue(self):
- 151        return self._dvalue
- 152
- 153    @property
- 154    def e_names(self):
- 155        return sorted(set([o.split('|')[0] for o in self.names]))
- 156
- 157    @property
- 158    def cov_names(self):
- 159        return sorted(set([o for o in self.covobs.keys()]))
- 160
- 161    @property
- 162    def mc_names(self):
- 163        return sorted(set([o.split('|')[0] for o in self.names if o not in self.cov_names]))
- 164
- 165    @property
- 166    def e_content(self):
- 167        res = {}
- 168        for e, e_name in enumerate(self.e_names):
- 169            res[e_name] = sorted(filter(lambda x: x.startswith(e_name + '|'), self.names))
- 170            if e_name in self.names:
- 171                res[e_name].append(e_name)
- 172        return res
+  21class Obs:
+  22    """Class for a general observable.
+  23
+  24    Instances of Obs are the basic objects of a pyerrors error analysis.
+  25    They are initialized with a list which contains arrays of samples for
+  26    different ensembles/replica and another list of same length which contains
+  27    the names of the ensembles/replica. Mathematical operations can be
+  28    performed on instances. The result is another instance of Obs. The error of
+  29    an instance can be computed with the gamma_method. Also contains additional
+  30    methods for output and visualization of the error calculation.
+  31
+  32    Attributes
+  33    ----------
+  34    S_global : float
+  35        Standard value for S (default 2.0)
+  36    S_dict : dict
+  37        Dictionary for S values. If an entry for a given ensemble
+  38        exists this overwrites the standard value for that ensemble.
+  39    tau_exp_global : float
+  40        Standard value for tau_exp (default 0.0)
+  41    tau_exp_dict : dict
+  42        Dictionary for tau_exp values. If an entry for a given ensemble exists
+  43        this overwrites the standard value for that ensemble.
+  44    N_sigma_global : float
+  45        Standard value for N_sigma (default 1.0)
+  46    N_sigma_dict : dict
+  47        Dictionary for N_sigma values. If an entry for a given ensemble exists
+  48        this overwrites the standard value for that ensemble.
+  49    """
+  50    __slots__ = [
+  51        'N',
+  52        'N_sigma',
+  53        'S',
+  54        '__dict__',
+  55        '_covobs',
+  56        '_dvalue',
+  57        '_value',
+  58        'ddvalue',
+  59        'deltas',
+  60        'e_ddvalue',
+  61        'e_drho',
+  62        'e_dtauint',
+  63        'e_dvalue',
+  64        'e_n_dtauint',
+  65        'e_n_tauint',
+  66        'e_rho',
+  67        'e_tauint',
+  68        'e_windowsize',
+  69        'idl',
+  70        'names',
+  71        'r_values',
+  72        'reweighted',
+  73        'shape',
+  74        'tag',
+  75        'tau_exp',
+  76    ]
+  77
+  78    S_global = 2.0
+  79    S_dict: ClassVar[dict] = {}
+  80    tau_exp_global = 0.0
+  81    tau_exp_dict: ClassVar[dict] = {}
+  82    N_sigma_global = 1.0
+  83    N_sigma_dict: ClassVar[dict] = {}
+  84
+  85    def __init__(self, samples, names, idl=None, **kwargs):
+  86        """ Initialize Obs object.
+  87
+  88        Parameters
+  89        ----------
+  90        samples : list
+  91            list of numpy arrays containing the Monte Carlo samples
+  92        names : list
+  93            list of strings labeling the individual samples
+  94        idl : list, optional
+  95            list of ranges or lists on which the samples are defined
+  96        """
+  97
+  98        if kwargs.get("means") is None and len(samples):
+  99            if len(samples) != len(names):
+ 100                raise ValueError('Length of samples and names incompatible.')
+ 101            if idl is not None:
+ 102                if len(idl) != len(names):
+ 103                    raise ValueError('Length of idl incompatible with samples and names.')
+ 104            name_length = len(names)
+ 105            if name_length > 1:
+ 106                if name_length != len(set(names)):
+ 107                    raise ValueError('Names are not unique.')
+ 108                if not all(isinstance(x, str) for x in names):
+ 109                    raise TypeError('All names have to be strings.')
+ 110                if len(set([o.split('|')[0] for o in names])) > 1:
+ 111                    raise ValueError('Cannot initialize Obs based on multiple ensembles. Please average separate Obs from each ensemble.')
+ 112            else:
+ 113                if not isinstance(names[0], str):
+ 114                    raise TypeError('All names have to be strings.')
+ 115            if min(len(x) for x in samples) <= 4:
+ 116                raise ValueError('Samples have to have at least 5 entries.')
+ 117
+ 118        self.names = sorted(names)
+ 119        self.shape = {}
+ 120        self.r_values = {}
+ 121        self.deltas = {}
+ 122        self._covobs = {}
+ 123
+ 124        self._value = 0
+ 125        self.N = 0
+ 126        self.idl = {}
+ 127        if idl is not None:
+ 128            for name, idx in sorted(zip(names, idl, strict=True)):
+ 129                if isinstance(idx, range):
+ 130                    self.idl[name] = idx
+ 131                elif isinstance(idx, (list, np.ndarray)):
+ 132                    dc = np.unique(np.diff(idx))
+ 133                    if np.any(dc < 0):
+ 134                        raise ValueError("Unsorted idx for idl[{}] at position {}".format(name, ' '.join(['%s' % (pos + 1) for pos in np.where(np.diff(idx) < 0)[0]])))
+ 135                    elif np.any(dc == 0):
+ 136                        raise ValueError("Duplicate entries in idx for idl[{}] at position {}".format(name, ' '.join(['%s' % (pos + 1) for pos in np.where(np.diff(idx) == 0)[0]])))
+ 137                    if len(dc) == 1:
+ 138                        self.idl[name] = range(idx[0], idx[-1] + dc[0], dc[0])
+ 139                    else:
+ 140                        self.idl[name] = list(idx)
+ 141                else:
+ 142                    raise TypeError(f'incompatible type for idl[{name}].')
+ 143        else:
+ 144            for name, sample in sorted(zip(names, samples, strict=True)):
+ 145                self.idl[name] = range(1, len(sample) + 1)
+ 146
+ 147        if kwargs.get("means") is not None:
+ 148            for name, sample, mean in sorted(zip(names, samples, kwargs.get("means"), strict=True)):
+ 149                self.shape[name] = len(self.idl[name])
+ 150                self.N += self.shape[name]
+ 151                self.r_values[name] = mean
+ 152                self.deltas[name] = sample
+ 153        else:
+ 154            for name, sample in sorted(zip(names, samples, strict=True)):
+ 155                self.shape[name] = len(self.idl[name])
+ 156                self.N += self.shape[name]
+ 157                if len(sample) != self.shape[name]:
+ 158                    raise ValueError(f'Incompatible samples and idx for {name}: {len(sample)} vs. {self.shape[name]}')
+ 159                self.r_values[name] = np.mean(sample)
+ 160                self.deltas[name] = sample - self.r_values[name]
+ 161                self._value += self.shape[name] * self.r_values[name]
+ 162            self._value /= self.N
+ 163
+ 164        self._dvalue = 0.0
+ 165        self.ddvalue = 0.0
+ 166        self.reweighted = False
+ 167
+ 168        self.tag = None
+ 169
+ 170    @property
+ 171    def value(self):
+ 172        return self._value
  173
  174    @property
- 175    def covobs(self):
- 176        return self._covobs
+ 175    def dvalue(self):
+ 176        return self._dvalue
  177
- 178    def gamma_method(self, **kwargs):
- 179        """Estimate the error and related properties of the Obs.
- 180
- 181        Parameters
- 182        ----------
- 183        S : float
- 184            specifies a custom value for the parameter S (default 2.0).
- 185            If set to 0 it is assumed that the data exhibits no
- 186            autocorrelation. In this case the error estimates coincides
- 187            with the sample standard error.
- 188        tau_exp : float
- 189            positive value triggers the critical slowing down analysis
- 190            (default 0.0).
- 191        N_sigma : float
- 192            number of standard deviations from zero until the tail is
- 193            attached to the autocorrelation function (default 1).
- 194        fft : bool
- 195            determines whether the fft algorithm is used for the computation
- 196            of the autocorrelation function (default True)
- 197        """
+ 178    @property
+ 179    def e_names(self):
+ 180        return sorted(set([o.split('|')[0] for o in self.names]))
+ 181
+ 182    @property
+ 183    def cov_names(self):
+ 184        return sorted(set([o for o in self.covobs.keys()]))
+ 185
+ 186    @property
+ 187    def mc_names(self):
+ 188        return sorted(set([o.split('|')[0] for o in self.names if o not in self.cov_names]))
+ 189
+ 190    @property
+ 191    def e_content(self):
+ 192        res = {}
+ 193        for _e, e_name in enumerate(self.e_names):
+ 194            res[e_name] = sorted(filter(lambda x: x.startswith(e_name + '|'), self.names))
+ 195            if e_name in self.names:
+ 196                res[e_name].append(e_name)
+ 197        return res
  198
- 199        e_content = self.e_content
- 200        self.e_dvalue = {}
- 201        self.e_ddvalue = {}
- 202        self.e_tauint = {}
- 203        self.e_dtauint = {}
- 204        self.e_windowsize = {}
- 205        self.e_n_tauint = {}
- 206        self.e_n_dtauint = {}
- 207        e_gamma = {}
- 208        self.e_rho = {}
- 209        self.e_drho = {}
- 210        self._dvalue = 0
- 211        self.ddvalue = 0
- 212
- 213        self.S = {}
- 214        self.tau_exp = {}
- 215        self.N_sigma = {}
- 216
- 217        if kwargs.get('fft') is False:
- 218            fft = False
- 219        else:
- 220            fft = True
- 221
- 222        def _parse_kwarg(kwarg_name):
- 223            if kwarg_name in kwargs:
- 224                tmp = kwargs.get(kwarg_name)
- 225                if isinstance(tmp, (int, float)):
- 226                    if tmp < 0:
- 227                        raise ValueError(kwarg_name + ' has to be larger or equal to 0.')
- 228                    for e, e_name in enumerate(self.e_names):
- 229                        getattr(self, kwarg_name)[e_name] = tmp
- 230                else:
- 231                    raise TypeError(kwarg_name + ' is not in proper format.')
- 232            else:
- 233                for e, e_name in enumerate(self.e_names):
- 234                    if e_name in getattr(Obs, kwarg_name + '_dict'):
- 235                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_dict')[e_name]
- 236                    else:
- 237                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_global')
- 238
- 239        _parse_kwarg('S')
- 240        _parse_kwarg('tau_exp')
- 241        _parse_kwarg('N_sigma')
- 242
- 243        for e, e_name in enumerate(self.mc_names):
- 244            gapsize = _determine_gap(self, e_content, e_name)
- 245
- 246            r_length = []
- 247            for r_name in e_content[e_name]:
- 248                if isinstance(self.idl[r_name], range):
- 249                    r_length.append(len(self.idl[r_name]) * self.idl[r_name].step // gapsize)
- 250                else:
- 251                    r_length.append((self.idl[r_name][-1] - self.idl[r_name][0] + 1) // gapsize)
- 252
- 253            e_N = np.sum([self.shape[r_name] for r_name in e_content[e_name]])
- 254            w_max = max(r_length) // 2
- 255            e_gamma[e_name] = np.zeros(w_max)
- 256            self.e_rho[e_name] = np.zeros(w_max)
- 257            self.e_drho[e_name] = np.zeros(w_max)
- 258
- 259            for r_name in e_content[e_name]:
- 260                e_gamma[e_name] += self._calc_gamma(self.deltas[r_name], self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
- 261
- 262            gamma_div = np.zeros(w_max)
- 263            for r_name in e_content[e_name]:
- 264                gamma_div += self._calc_gamma(np.ones((self.shape[r_name])), self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
- 265            gamma_div[gamma_div < 1] = 1.0
- 266            e_gamma[e_name] /= gamma_div[:w_max]
+ 199    @property
+ 200    def covobs(self):
+ 201        return self._covobs
+ 202
+ 203    def gamma_method(self, **kwargs):
+ 204        """Estimate the error and related properties of the Obs.
+ 205
+ 206        Parameters
+ 207        ----------
+ 208        S : float
+ 209            specifies a custom value for the parameter S (default 2.0).
+ 210            If set to 0 it is assumed that the data exhibits no
+ 211            autocorrelation. In this case the error estimates coincides
+ 212            with the sample standard error.
+ 213        tau_exp : float
+ 214            positive value triggers the critical slowing down analysis
+ 215            (default 0.0).
+ 216        N_sigma : float
+ 217            number of standard deviations from zero until the tail is
+ 218            attached to the autocorrelation function (default 1).
+ 219        fft : bool
+ 220            determines whether the fft algorithm is used for the computation
+ 221            of the autocorrelation function (default True)
+ 222        """
+ 223
+ 224        e_content = self.e_content
+ 225        self.e_dvalue = {}
+ 226        self.e_ddvalue = {}
+ 227        self.e_tauint = {}
+ 228        self.e_dtauint = {}
+ 229        self.e_windowsize = {}
+ 230        self.e_n_tauint = {}
+ 231        self.e_n_dtauint = {}
+ 232        e_gamma = {}
+ 233        self.e_rho = {}
+ 234        self.e_drho = {}
+ 235        self._dvalue = 0
+ 236        self.ddvalue = 0
+ 237
+ 238        self.S = {}
+ 239        self.tau_exp = {}
+ 240        self.N_sigma = {}
+ 241
+ 242        if kwargs.get('fft') is False:
+ 243            fft = False
+ 244        else:
+ 245            fft = True
+ 246
+ 247        def _parse_kwarg(kwarg_name):
+ 248            if kwarg_name in kwargs:
+ 249                tmp = kwargs.get(kwarg_name)
+ 250                if isinstance(tmp, (int, float)):
+ 251                    if tmp < 0:
+ 252                        raise ValueError(kwarg_name + ' has to be larger or equal to 0.')
+ 253                    for _e, e_name in enumerate(self.e_names):
+ 254                        getattr(self, kwarg_name)[e_name] = tmp
+ 255                else:
+ 256                    raise TypeError(kwarg_name + ' is not in proper format.')
+ 257            else:
+ 258                for _e, e_name in enumerate(self.e_names):
+ 259                    if e_name in getattr(Obs, kwarg_name + '_dict'):
+ 260                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_dict')[e_name]
+ 261                    else:
+ 262                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_global')
+ 263
+ 264        _parse_kwarg('S')
+ 265        _parse_kwarg('tau_exp')
+ 266        _parse_kwarg('N_sigma')
  267
- 268            if np.abs(e_gamma[e_name][0]) < 10 * np.finfo(float).tiny:  # Prevent division by zero
- 269                self.e_tauint[e_name] = 0.5
- 270                self.e_dtauint[e_name] = 0.0
- 271                self.e_dvalue[e_name] = 0.0
- 272                self.e_ddvalue[e_name] = 0.0
- 273                self.e_windowsize[e_name] = 0
- 274                continue
- 275
- 276            self.e_rho[e_name] = e_gamma[e_name][:w_max] / e_gamma[e_name][0]
- 277            self.e_n_tauint[e_name] = np.cumsum(np.concatenate(([0.5], self.e_rho[e_name][1:])))
- 278            # Make sure no entry of tauint is smaller than 0.5
- 279            self.e_n_tauint[e_name][self.e_n_tauint[e_name] <= 0.5] = 0.5 + np.finfo(np.float64).eps
- 280            # hep-lat/0306017 eq. (42)
- 281            self.e_n_dtauint[e_name] = self.e_n_tauint[e_name] * 2 * np.sqrt(np.abs(np.arange(w_max) + 0.5 - self.e_n_tauint[e_name]) / e_N)
- 282            self.e_n_dtauint[e_name][0] = 0.0
+ 268        for _e, e_name in enumerate(self.mc_names):
+ 269            gapsize = _determine_gap(self, e_content, e_name)
+ 270
+ 271            r_length = []
+ 272            for r_name in e_content[e_name]:
+ 273                if isinstance(self.idl[r_name], range):
+ 274                    r_length.append(len(self.idl[r_name]) * self.idl[r_name].step // gapsize)
+ 275                else:
+ 276                    r_length.append((self.idl[r_name][-1] - self.idl[r_name][0] + 1) // gapsize)
+ 277
+ 278            e_N = np.sum([self.shape[r_name] for r_name in e_content[e_name]])
+ 279            w_max = max(r_length) // 2
+ 280            e_gamma[e_name] = np.zeros(w_max)
+ 281            self.e_rho[e_name] = np.zeros(w_max)
+ 282            self.e_drho[e_name] = np.zeros(w_max)
  283
- 284            def _compute_drho(i):
- 285                tmp = (self.e_rho[e_name][i + 1:w_max]
- 286                       + np.concatenate([self.e_rho[e_name][i - 1:None if i - (w_max - 1) // 2 <= 0 else (2 * i - (2 * w_max) // 2):-1],
- 287                                         self.e_rho[e_name][1:max(1, w_max - 2 * i)]])
- 288                       - 2 * self.e_rho[e_name][i] * self.e_rho[e_name][1:w_max - i])
- 289                self.e_drho[e_name][i] = np.sqrt(np.sum(tmp ** 2) / e_N)
- 290
- 291            if self.tau_exp[e_name] > 0:
- 292                _compute_drho(1)
- 293                texp = self.tau_exp[e_name]
- 294                # Critical slowing down analysis
- 295                if w_max // 2 <= 1:
- 296                    raise ValueError("Need at least 8 samples for tau_exp error analysis")
- 297                for n in range(1, w_max // 2):
- 298                    _compute_drho(n + 1)
- 299                    if (self.e_rho[e_name][n] - self.N_sigma[e_name] * self.e_drho[e_name][n]) < 0 or n >= w_max // 2 - 2:
- 300                        # Bias correction hep-lat/0306017 eq. (49) included
- 301                        self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N) + texp * np.abs(self.e_rho[e_name][n + 1])  # The absolute makes sure, that the tail contribution is always positive
- 302                        self.e_dtauint[e_name] = np.sqrt(self.e_n_dtauint[e_name][n] ** 2 + texp ** 2 * self.e_drho[e_name][n + 1] ** 2)
- 303                        # Error of tau_exp neglected so far, missing term: self.e_rho[e_name][n + 1] ** 2 * d_tau_exp ** 2
- 304                        self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
- 305                        self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
- 306                        self.e_windowsize[e_name] = n
- 307                        break
- 308            else:
- 309                if self.S[e_name] == 0.0:
- 310                    self.e_tauint[e_name] = 0.5
- 311                    self.e_dtauint[e_name] = 0.0
- 312                    self.e_dvalue[e_name] = np.sqrt(e_gamma[e_name][0] / (e_N - 1))
- 313                    self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt(0.5 / e_N)
- 314                    self.e_windowsize[e_name] = 0
- 315                else:
- 316                    # Standard automatic windowing procedure
- 317                    tau = self.S[e_name] / np.log((2 * self.e_n_tauint[e_name][1:] + 1) / (2 * self.e_n_tauint[e_name][1:] - 1))
- 318                    g_w = np.exp(- np.arange(1, len(tau) + 1) / tau) - tau / np.sqrt(np.arange(1, len(tau) + 1) * e_N)
- 319                    for n in range(1, w_max):
- 320                        if g_w[n - 1] < 0 or n >= w_max - 1:
- 321                            _compute_drho(n)
- 322                            self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N)  # Bias correction hep-lat/0306017 eq. (49)
- 323                            self.e_dtauint[e_name] = self.e_n_dtauint[e_name][n]
- 324                            self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
- 325                            self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
- 326                            self.e_windowsize[e_name] = n
- 327                            break
- 328
- 329            self._dvalue += self.e_dvalue[e_name] ** 2
- 330            self.ddvalue += (self.e_dvalue[e_name] * self.e_ddvalue[e_name]) ** 2
- 331
- 332        for e_name in self.cov_names:
- 333            self.e_dvalue[e_name] = np.sqrt(self.covobs[e_name].errsq())
- 334            self.e_ddvalue[e_name] = 0
- 335            self._dvalue += self.e_dvalue[e_name]**2
- 336
- 337        self._dvalue = np.sqrt(self._dvalue)
- 338        if self._dvalue == 0.0:
- 339            self.ddvalue = 0.0
- 340        else:
- 341            self.ddvalue = np.sqrt(self.ddvalue) / self._dvalue
- 342        return
- 343
- 344    gm = gamma_method
- 345
- 346    def _calc_gamma(self, deltas, idx, shape, w_max, fft, gapsize):
- 347        """Calculate Gamma_{AA} from the deltas, which are defined on idx.
- 348           idx is assumed to be a contiguous range (possibly with a stepsize != 1)
- 349
- 350        Parameters
- 351        ----------
- 352        deltas : list
- 353            List of fluctuations
- 354        idx : list
- 355            List or range of configurations on which the deltas are defined.
- 356        shape : int
- 357            Number of configurations in idx.
- 358        w_max : int
- 359            Upper bound for the summation window.
- 360        fft : bool
- 361            determines whether the fft algorithm is used for the computation
- 362            of the autocorrelation function.
- 363        gapsize : int
- 364            The target distance between two configurations. If longer distances
- 365            are found in idx, the data is expanded.
- 366        """
- 367        gamma = np.zeros(w_max)
- 368        deltas = _expand_deltas(deltas, idx, shape, gapsize)
- 369        new_shape = len(deltas)
- 370        if fft:
- 371            max_gamma = min(new_shape, w_max)
- 372            # The padding for the fft has to be even
- 373            padding = new_shape + max_gamma + (new_shape + max_gamma) % 2
- 374            gamma[:max_gamma] += np.fft.irfft(np.abs(np.fft.rfft(deltas, padding)) ** 2)[:max_gamma]
- 375        else:
- 376            for n in range(w_max):
- 377                if new_shape - n >= 0:
- 378                    gamma[n] += deltas[0:new_shape - n].dot(deltas[n:new_shape])
- 379
- 380        return gamma
- 381
- 382    def details(self, ens_content=True):
- 383        """Output detailed properties of the Obs.
- 384
- 385        Parameters
- 386        ----------
- 387        ens_content : bool
- 388            print details about the ensembles and replica if true.
- 389        """
- 390        if self.tag is not None:
- 391            print("Description:", self.tag)
- 392        if not hasattr(self, 'e_dvalue'):
- 393            print('Result\t %3.8e' % (self.value))
- 394        else:
- 395            if self.value == 0.0:
- 396                percentage = np.nan
- 397            else:
- 398                percentage = np.abs(self._dvalue / self.value) * 100
- 399            print('Result\t %3.8e +/- %3.8e +/- %3.8e (%3.3f%%)' % (self.value, self._dvalue, self.ddvalue, percentage))
- 400            if len(self.e_names) > 1:
- 401                print(' Ensemble errors:')
- 402            e_content = self.e_content
- 403            for e_name in self.mc_names:
- 404                gap = _determine_gap(self, e_content, e_name)
- 405
- 406                if len(self.e_names) > 1:
- 407                    print('', e_name, '\t %3.6e +/- %3.6e' % (self.e_dvalue[e_name], self.e_ddvalue[e_name]))
- 408                tau_string = " \N{GREEK SMALL LETTER TAU}_int\t " + _format_uncertainty(self.e_tauint[e_name], self.e_dtauint[e_name])
- 409                tau_string += f" in units of {gap} config"
- 410                if gap > 1:
- 411                    tau_string += "s"
- 412                if self.tau_exp[e_name] > 0:
- 413                    tau_string = f"{tau_string: <45}" + '\t(\N{GREEK SMALL LETTER TAU}_exp=%3.2f, N_\N{GREEK SMALL LETTER SIGMA}=%1.0i)' % (self.tau_exp[e_name], self.N_sigma[e_name])
- 414                else:
- 415                    tau_string = f"{tau_string: <45}" + '\t(S=%3.2f)' % (self.S[e_name])
- 416                print(tau_string)
- 417            for e_name in self.cov_names:
- 418                print('', e_name, '\t %3.8e' % (self.e_dvalue[e_name]))
- 419        if ens_content is True:
- 420            if len(self.e_names) == 1:
- 421                print(self.N, 'samples in', len(self.e_names), 'ensemble:')
+ 284            for r_name in e_content[e_name]:
+ 285                e_gamma[e_name] += self._calc_gamma(self.deltas[r_name], self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
+ 286
+ 287            gamma_div = np.zeros(w_max)
+ 288            for r_name in e_content[e_name]:
+ 289                gamma_div += self._calc_gamma(np.ones(self.shape[r_name]), self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
+ 290            gamma_div[gamma_div < 1] = 1.0
+ 291            e_gamma[e_name] /= gamma_div[:w_max]
+ 292
+ 293            if np.abs(e_gamma[e_name][0]) < 10 * np.finfo(float).tiny:  # Prevent division by zero
+ 294                self.e_tauint[e_name] = 0.5
+ 295                self.e_dtauint[e_name] = 0.0
+ 296                self.e_dvalue[e_name] = 0.0
+ 297                self.e_ddvalue[e_name] = 0.0
+ 298                self.e_windowsize[e_name] = 0
+ 299                continue
+ 300
+ 301            self.e_rho[e_name] = e_gamma[e_name][:w_max] / e_gamma[e_name][0]
+ 302            self.e_n_tauint[e_name] = np.cumsum(np.concatenate(([0.5], self.e_rho[e_name][1:])))
+ 303            # Make sure no entry of tauint is smaller than 0.5
+ 304            self.e_n_tauint[e_name][self.e_n_tauint[e_name] <= 0.5] = 0.5 + np.finfo(np.float64).eps
+ 305            # hep-lat/0306017 eq. (42)
+ 306            self.e_n_dtauint[e_name] = self.e_n_tauint[e_name] * 2 * np.sqrt(np.abs(np.arange(w_max) + 0.5 - self.e_n_tauint[e_name]) / e_N)
+ 307            self.e_n_dtauint[e_name][0] = 0.0
+ 308
+ 309            def _compute_drho(i, e_name=e_name, w_max=w_max, e_N=e_N):
+ 310                tmp = (self.e_rho[e_name][i + 1:w_max]
+ 311                       + np.concatenate([self.e_rho[e_name][i - 1:None if i - (w_max - 1) // 2 <= 0 else (2 * i - (2 * w_max) // 2):-1],
+ 312                                         self.e_rho[e_name][1:max(1, w_max - 2 * i)]])
+ 313                       - 2 * self.e_rho[e_name][i] * self.e_rho[e_name][1:w_max - i])
+ 314                self.e_drho[e_name][i] = np.sqrt(np.sum(tmp ** 2) / e_N)
+ 315
+ 316            if self.tau_exp[e_name] > 0:
+ 317                _compute_drho(1)
+ 318                texp = self.tau_exp[e_name]
+ 319                # Critical slowing down analysis
+ 320                if w_max // 2 <= 1:
+ 321                    raise ValueError("Need at least 8 samples for tau_exp error analysis")
+ 322                for n in range(1, w_max // 2):
+ 323                    _compute_drho(n + 1)
+ 324                    if (self.e_rho[e_name][n] - self.N_sigma[e_name] * self.e_drho[e_name][n]) < 0 or n >= w_max // 2 - 2:
+ 325                        # Bias correction hep-lat/0306017 eq. (49) included
+ 326                        self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N) + texp * np.abs(self.e_rho[e_name][n + 1])  # The absolute makes sure, that the tail contribution is always positive
+ 327                        self.e_dtauint[e_name] = np.sqrt(self.e_n_dtauint[e_name][n] ** 2 + texp ** 2 * self.e_drho[e_name][n + 1] ** 2)
+ 328                        # Error of tau_exp neglected so far, missing term: self.e_rho[e_name][n + 1] ** 2 * d_tau_exp ** 2
+ 329                        self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
+ 330                        self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
+ 331                        self.e_windowsize[e_name] = n
+ 332                        break
+ 333            else:
+ 334                if self.S[e_name] == 0.0:
+ 335                    self.e_tauint[e_name] = 0.5
+ 336                    self.e_dtauint[e_name] = 0.0
+ 337                    self.e_dvalue[e_name] = np.sqrt(e_gamma[e_name][0] / (e_N - 1))
+ 338                    self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt(0.5 / e_N)
+ 339                    self.e_windowsize[e_name] = 0
+ 340                else:
+ 341                    # Standard automatic windowing procedure
+ 342                    tau = self.S[e_name] / np.log((2 * self.e_n_tauint[e_name][1:] + 1) / (2 * self.e_n_tauint[e_name][1:] - 1))
+ 343                    g_w = np.exp(- np.arange(1, len(tau) + 1) / tau) - tau / np.sqrt(np.arange(1, len(tau) + 1) * e_N)
+ 344                    for n in range(1, w_max):
+ 345                        if g_w[n - 1] < 0 or n >= w_max - 1:
+ 346                            _compute_drho(n)
+ 347                            self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N)  # Bias correction hep-lat/0306017 eq. (49)
+ 348                            self.e_dtauint[e_name] = self.e_n_dtauint[e_name][n]
+ 349                            self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
+ 350                            self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
+ 351                            self.e_windowsize[e_name] = n
+ 352                            break
+ 353
+ 354            self._dvalue += self.e_dvalue[e_name] ** 2
+ 355            self.ddvalue += (self.e_dvalue[e_name] * self.e_ddvalue[e_name]) ** 2
+ 356
+ 357        for e_name in self.cov_names:
+ 358            self.e_dvalue[e_name] = np.sqrt(self.covobs[e_name].errsq())
+ 359            self.e_ddvalue[e_name] = 0
+ 360            self._dvalue += self.e_dvalue[e_name]**2
+ 361
+ 362        self._dvalue = np.sqrt(self._dvalue)
+ 363        if self._dvalue == 0.0:
+ 364            self.ddvalue = 0.0
+ 365        else:
+ 366            self.ddvalue = np.sqrt(self.ddvalue) / self._dvalue
+ 367        return
+ 368
+ 369    gm = gamma_method
+ 370
+ 371    def _calc_gamma(self, deltas, idx, shape, w_max, fft, gapsize):
+ 372        """Calculate Gamma_{AA} from the deltas, which are defined on idx.
+ 373           idx is assumed to be a contiguous range (possibly with a stepsize != 1)
+ 374
+ 375        Parameters
+ 376        ----------
+ 377        deltas : list
+ 378            List of fluctuations
+ 379        idx : list
+ 380            List or range of configurations on which the deltas are defined.
+ 381        shape : int
+ 382            Number of configurations in idx.
+ 383        w_max : int
+ 384            Upper bound for the summation window.
+ 385        fft : bool
+ 386            determines whether the fft algorithm is used for the computation
+ 387            of the autocorrelation function.
+ 388        gapsize : int
+ 389            The target distance between two configurations. If longer distances
+ 390            are found in idx, the data is expanded.
+ 391        """
+ 392        gamma = np.zeros(w_max)
+ 393        deltas = _expand_deltas(deltas, idx, shape, gapsize)
+ 394        new_shape = len(deltas)
+ 395        if fft:
+ 396            max_gamma = min(new_shape, w_max)
+ 397            # The padding for the fft has to be even
+ 398            padding = new_shape + max_gamma + (new_shape + max_gamma) % 2
+ 399            gamma[:max_gamma] += np.fft.irfft(np.abs(np.fft.rfft(deltas, padding)) ** 2)[:max_gamma]
+ 400        else:
+ 401            for n in range(w_max):
+ 402                if new_shape - n >= 0:
+ 403                    gamma[n] += deltas[0:new_shape - n].dot(deltas[n:new_shape])
+ 404
+ 405        return gamma
+ 406
+ 407    def details(self, ens_content=True):
+ 408        """Output detailed properties of the Obs.
+ 409
+ 410        Parameters
+ 411        ----------
+ 412        ens_content : bool
+ 413            print details about the ensembles and replica if true.
+ 414        """
+ 415        if self.tag is not None:
+ 416            print("Description:", self.tag)
+ 417        if not hasattr(self, 'e_dvalue'):
+ 418            print(f'Result\t {self.value:3.8e}')
+ 419        else:
+ 420            if self.value == 0.0:
+ 421                percentage = np.nan
  422            else:
- 423                print(self.N, 'samples in', len(self.e_names), 'ensembles:')
- 424            my_string_list = []
- 425            for key, value in sorted(self.e_content.items()):
- 426                if key not in self.covobs:
- 427                    my_string = '  ' + "\u00B7 Ensemble '" + key + "' "
- 428                    if len(value) == 1:
- 429                        my_string += f': {self.shape[value[0]]} configurations'
- 430                        if isinstance(self.idl[value[0]], range):
- 431                            my_string += f' (from {self.idl[value[0]].start} to {self.idl[value[0]][-1]}' + int(self.idl[value[0]].step != 1) * f' in steps of {self.idl[value[0]].step}' + ')'
- 432                        else:
- 433                            my_string += f' (irregular range from {self.idl[value[0]][0]} to {self.idl[value[0]][-1]})'
- 434                    else:
- 435                        sublist = []
- 436                        for v in value:
- 437                            my_substring = '    ' + "\u00B7 Replicum '" + v[len(key) + 1:] + "' "
- 438                            my_substring += f': {self.shape[v]} configurations'
- 439                            if isinstance(self.idl[v], range):
- 440                                my_substring += f' (from {self.idl[v].start} to {self.idl[v][-1]}' + int(self.idl[v].step != 1) * f' in steps of {self.idl[v].step}' + ')'
- 441                            else:
- 442                                my_substring += f' (irregular range from {self.idl[v][0]} to {self.idl[v][-1]})'
- 443                            sublist.append(my_substring)
- 444
- 445                        my_string += '\n' + '\n'.join(sublist)
- 446                else:
- 447                    my_string = '  ' + "\u00B7 Covobs   '" + key + "' "
- 448                my_string_list.append(my_string)
- 449            print('\n'.join(my_string_list))
- 450
- 451    def reweight(self, weight):
- 452        """Reweight the obs with given rewighting factors.
- 453
- 454        Parameters
- 455        ----------
- 456        weight : Obs
- 457            Reweighting factor. An Observable that has to be defined on a superset of the
- 458            configurations in obs[i].idl for all i.
- 459        all_configs : bool
- 460            if True, the reweighted observables are normalized by the average of
- 461            the reweighting factor on all configurations in weight.idl and not
- 462            on the configurations in obs[i].idl. Default False.
- 463        """
- 464        return reweight(weight, [self])[0]
- 465
- 466    def is_zero_within_error(self, sigma=1):
- 467        """Checks whether the observable is zero within 'sigma' standard errors.
- 468
- 469        Parameters
- 470        ----------
- 471        sigma : int
- 472            Number of standard errors used for the check.
- 473
- 474        Works only properly when the gamma method was run.
- 475        """
- 476        return self.is_zero() or np.abs(self.value) <= sigma * self._dvalue
- 477
- 478    def is_zero(self, atol=1e-10):
- 479        """Checks whether the observable is zero within a given tolerance.
- 480
- 481        Parameters
- 482        ----------
- 483        atol : float
- 484            Absolute tolerance (for details see numpy documentation).
- 485        """
- 486        return np.isclose(0.0, self.value, 1e-14, atol) and all(np.allclose(0.0, delta, 1e-14, atol) for delta in self.deltas.values()) and all(np.allclose(0.0, delta.errsq(), 1e-14, atol) for delta in self.covobs.values())
- 487
- 488    def plot_tauint(self, save=None):
- 489        """Plot integrated autocorrelation time for each ensemble.
+ 423                percentage = np.abs(self._dvalue / self.value) * 100
+ 424            print(f'Result\t {self.value:3.8e} +/- {self._dvalue:3.8e} +/- {self.ddvalue:3.8e} ({percentage:3.3f}%)')
+ 425            if len(self.e_names) > 1:
+ 426                print(' Ensemble errors:')
+ 427            e_content = self.e_content
+ 428            for e_name in self.mc_names:
+ 429                gap = _determine_gap(self, e_content, e_name)
+ 430
+ 431                if len(self.e_names) > 1:
+ 432                    print('', e_name, f'\t {self.e_dvalue[e_name]:3.6e} +/- {self.e_ddvalue[e_name]:3.6e}')
+ 433                tau_string = " \N{GREEK SMALL LETTER TAU}_int\t " + _format_uncertainty(self.e_tauint[e_name], self.e_dtauint[e_name])
+ 434                tau_string += f" in units of {gap} config"
+ 435                if gap > 1:
+ 436                    tau_string += "s"
+ 437                if self.tau_exp[e_name] > 0:
+ 438                    tau_string = f"{tau_string: <45}" + f'\t(\N{GREEK SMALL LETTER TAU}_exp={self.tau_exp[e_name]:3.2f}, N_\N{GREEK SMALL LETTER SIGMA}={self.N_sigma[e_name]:g})'
+ 439                else:
+ 440                    tau_string = f"{tau_string: <45}" + f'\t(S={self.S[e_name]:3.2f})'
+ 441                print(tau_string)
+ 442            for e_name in self.cov_names:
+ 443                print('', e_name, f'\t {self.e_dvalue[e_name]:3.8e}')
+ 444        if ens_content is True:
+ 445            if len(self.e_names) == 1:
+ 446                print(self.N, 'samples in', len(self.e_names), 'ensemble:')
+ 447            else:
+ 448                print(self.N, 'samples in', len(self.e_names), 'ensembles:')
+ 449            my_string_list = []
+ 450            for key, value in sorted(self.e_content.items()):
+ 451                if key not in self.covobs:
+ 452                    my_string = '  ' + "\u00B7 Ensemble '" + key + "' "
+ 453                    if len(value) == 1:
+ 454                        my_string += f': {self.shape[value[0]]} configurations'
+ 455                        if isinstance(self.idl[value[0]], range):
+ 456                            my_string += f' (from {self.idl[value[0]].start} to {self.idl[value[0]][-1]}' + int(self.idl[value[0]].step != 1) * f' in steps of {self.idl[value[0]].step}' + ')'
+ 457                        else:
+ 458                            my_string += f' (irregular range from {self.idl[value[0]][0]} to {self.idl[value[0]][-1]})'
+ 459                    else:
+ 460                        sublist = []
+ 461                        for v in value:
+ 462                            my_substring = '    ' + "\u00B7 Replicum '" + v[len(key) + 1:] + "' "
+ 463                            my_substring += f': {self.shape[v]} configurations'
+ 464                            if isinstance(self.idl[v], range):
+ 465                                my_substring += f' (from {self.idl[v].start} to {self.idl[v][-1]}' + int(self.idl[v].step != 1) * f' in steps of {self.idl[v].step}' + ')'
+ 466                            else:
+ 467                                my_substring += f' (irregular range from {self.idl[v][0]} to {self.idl[v][-1]})'
+ 468                            sublist.append(my_substring)
+ 469
+ 470                        my_string += '\n' + '\n'.join(sublist)
+ 471                else:
+ 472                    my_string = '  ' + "\u00B7 Covobs   '" + key + "' "
+ 473                my_string_list.append(my_string)
+ 474            print('\n'.join(my_string_list))
+ 475
+ 476    def reweight(self, weight):
+ 477        """Reweight the obs with given rewighting factors.
+ 478
+ 479        Parameters
+ 480        ----------
+ 481        weight : Obs
+ 482            Reweighting factor. An Observable that has to be defined on a superset of the
+ 483            configurations in obs[i].idl for all i.
+ 484        all_configs : bool
+ 485            if True, the reweighted observables are normalized by the average of
+ 486            the reweighting factor on all configurations in weight.idl and not
+ 487            on the configurations in obs[i].idl. Default False.
+ 488        """
+ 489        return reweight(weight, [self])[0]
  490
- 491        Parameters
- 492        ----------
- 493        save : str
- 494            saves the figure to a file named 'save' if.
- 495        """
- 496        if not hasattr(self, 'e_dvalue'):
- 497            raise Exception('Run the gamma method first.')
+ 491    def is_zero_within_error(self, sigma=1):
+ 492        """Checks whether the observable is zero within 'sigma' standard errors.
+ 493
+ 494        Parameters
+ 495        ----------
+ 496        sigma : int
+ 497            Number of standard errors used for the check.
  498
- 499        for e, e_name in enumerate(self.mc_names):
- 500            fig = plt.figure()
- 501            plt.xlabel(r'$W$')
- 502            plt.ylabel(r'$\tau_\mathrm{int}$')
- 503            length = int(len(self.e_n_tauint[e_name]))
- 504            if self.tau_exp[e_name] > 0:
- 505                base = self.e_n_tauint[e_name][self.e_windowsize[e_name]]
- 506                x_help = np.arange(2 * self.tau_exp[e_name])
- 507                y_help = (x_help + 1) * np.abs(self.e_rho[e_name][self.e_windowsize[e_name] + 1]) * (1 - x_help / (2 * (2 * self.tau_exp[e_name] - 1))) + base
- 508                x_arr = np.arange(self.e_windowsize[e_name] + 1, self.e_windowsize[e_name] + 1 + 2 * self.tau_exp[e_name])
- 509                plt.plot(x_arr, y_help, 'C' + str(e), linewidth=1, ls='--', marker=',')
- 510                plt.errorbar([self.e_windowsize[e_name] + 2 * self.tau_exp[e_name]], [self.e_tauint[e_name]],
- 511                             yerr=[self.e_dtauint[e_name]], fmt='C' + str(e), linewidth=1, capsize=2, marker='o', mfc=plt.rcParams['axes.facecolor'])
- 512                xmax = self.e_windowsize[e_name] + 2 * self.tau_exp[e_name] + 1.5
- 513                label = e_name + r', $\tau_\mathrm{exp}$=' + str(np.around(self.tau_exp[e_name], decimals=2))
- 514            else:
- 515                label = e_name + ', S=' + str(np.around(self.S[e_name], decimals=2))
- 516                xmax = max(10.5, 2 * self.e_windowsize[e_name] - 0.5)
- 517
- 518            plt.errorbar(np.arange(length)[:int(xmax) + 1], self.e_n_tauint[e_name][:int(xmax) + 1], yerr=self.e_n_dtauint[e_name][:int(xmax) + 1], linewidth=1, capsize=2, label=label)
- 519            plt.axvline(x=self.e_windowsize[e_name], color='C' + str(e), alpha=0.5, marker=',', ls='--')
- 520            plt.legend()
- 521            plt.xlim(-0.5, xmax)
- 522            ylim = plt.ylim()
- 523            plt.ylim(bottom=0.0, top=max(1.0, ylim[1]))
- 524            plt.draw()
- 525            if save:
- 526                fig.savefig(save + "_" + str(e))
- 527
- 528    def plot_rho(self, save=None):
- 529        """Plot normalized autocorrelation function time for each ensemble.
- 530
- 531        Parameters
- 532        ----------
- 533        save : str
- 534            saves the figure to a file named 'save' if.
- 535        """
- 536        if not hasattr(self, 'e_dvalue'):
- 537            raise Exception('Run the gamma method first.')
- 538        for e, e_name in enumerate(self.mc_names):
- 539            fig = plt.figure()
- 540            plt.xlabel('W')
- 541            plt.ylabel('rho')
- 542            length = int(len(self.e_drho[e_name]))
- 543            plt.errorbar(np.arange(length), self.e_rho[e_name][:length], yerr=self.e_drho[e_name][:], linewidth=1, capsize=2)
- 544            plt.axvline(x=self.e_windowsize[e_name], color='r', alpha=0.25, ls='--', marker=',')
- 545            if self.tau_exp[e_name] > 0:
- 546                plt.plot([self.e_windowsize[e_name] + 1, self.e_windowsize[e_name] + 1 + 2 * self.tau_exp[e_name]],
- 547                         [self.e_rho[e_name][self.e_windowsize[e_name] + 1], 0], 'k-', lw=1)
- 548                xmax = self.e_windowsize[e_name] + 2 * self.tau_exp[e_name] + 1.5
- 549                plt.title('Rho ' + e_name + r', tau\_exp=' + str(np.around(self.tau_exp[e_name], decimals=2)))
- 550            else:
- 551                xmax = max(10.5, 2 * self.e_windowsize[e_name] - 0.5)
- 552                plt.title('Rho ' + e_name + ', S=' + str(np.around(self.S[e_name], decimals=2)))
- 553            plt.plot([-0.5, xmax], [0, 0], 'k--', lw=1)
- 554            plt.xlim(-0.5, xmax)
- 555            plt.draw()
- 556            if save:
- 557                fig.savefig(save + "_" + str(e))
- 558
- 559    def plot_rep_dist(self):
- 560        """Plot replica distribution for each ensemble with more than one replicum."""
+ 499        Works only properly when the gamma method was run.
+ 500        """
+ 501        return self.is_zero() or np.abs(self.value) <= sigma * self._dvalue
+ 502
+ 503    def is_zero(self, atol=1e-10):
+ 504        """Checks whether the observable is zero within a given tolerance.
+ 505
+ 506        Parameters
+ 507        ----------
+ 508        atol : float
+ 509            Absolute tolerance (for details see numpy documentation).
+ 510        """
+ 511        return np.isclose(0.0, self.value, 1e-14, atol) and all(np.allclose(0.0, delta, 1e-14, atol) for delta in self.deltas.values()) and all(np.allclose(0.0, delta.errsq(), 1e-14, atol) for delta in self.covobs.values())
+ 512
+ 513    def plot_tauint(self, save=None):
+ 514        """Plot integrated autocorrelation time for each ensemble.
+ 515
+ 516        Parameters
+ 517        ----------
+ 518        save : str
+ 519            saves the figure to a file named 'save' if.
+ 520        """
+ 521        if not hasattr(self, 'e_dvalue'):
+ 522            raise Exception('Run the gamma method first.')
+ 523
+ 524        for e, e_name in enumerate(self.mc_names):
+ 525            fig = plt.figure()
+ 526            plt.xlabel(r'$W$')
+ 527            plt.ylabel(r'$\tau_\mathrm{int}$')
+ 528            length = len(self.e_n_tauint[e_name])
+ 529            if self.tau_exp[e_name] > 0:
+ 530                base = self.e_n_tauint[e_name][self.e_windowsize[e_name]]
+ 531                x_help = np.arange(2 * self.tau_exp[e_name])
+ 532                y_help = (x_help + 1) * np.abs(self.e_rho[e_name][self.e_windowsize[e_name] + 1]) * (1 - x_help / (2 * (2 * self.tau_exp[e_name] - 1))) + base
+ 533                x_arr = np.arange(self.e_windowsize[e_name] + 1, self.e_windowsize[e_name] + 1 + 2 * self.tau_exp[e_name])
+ 534                plt.plot(x_arr, y_help, 'C' + str(e), linewidth=1, ls='--', marker=',')
+ 535                plt.errorbar([self.e_windowsize[e_name] + 2 * self.tau_exp[e_name]], [self.e_tauint[e_name]],
+ 536                             yerr=[self.e_dtauint[e_name]], fmt='C' + str(e), linewidth=1, capsize=2, marker='o', mfc=plt.rcParams['axes.facecolor'])
+ 537                xmax = self.e_windowsize[e_name] + 2 * self.tau_exp[e_name] + 1.5
+ 538                label = e_name + r', $\tau_\mathrm{exp}$=' + str(np.around(self.tau_exp[e_name], decimals=2))
+ 539            else:
+ 540                label = e_name + ', S=' + str(np.around(self.S[e_name], decimals=2))
+ 541                xmax = max(10.5, 2 * self.e_windowsize[e_name] - 0.5)
+ 542
+ 543            plt.errorbar(np.arange(length)[:int(xmax) + 1], self.e_n_tauint[e_name][:int(xmax) + 1], yerr=self.e_n_dtauint[e_name][:int(xmax) + 1], linewidth=1, capsize=2, label=label)
+ 544            plt.axvline(x=self.e_windowsize[e_name], color='C' + str(e), alpha=0.5, marker=',', ls='--')
+ 545            plt.legend()
+ 546            plt.xlim(-0.5, xmax)
+ 547            ylim = plt.ylim()
+ 548            plt.ylim(bottom=0.0, top=max(1.0, ylim[1]))
+ 549            plt.draw()
+ 550            if save:
+ 551                fig.savefig(save + "_" + str(e))
+ 552
+ 553    def plot_rho(self, save=None):
+ 554        """Plot normalized autocorrelation function time for each ensemble.
+ 555
+ 556        Parameters
+ 557        ----------
+ 558        save : str
+ 559            saves the figure to a file named 'save' if.
+ 560        """
  561        if not hasattr(self, 'e_dvalue'):
  562            raise Exception('Run the gamma method first.')
  563        for e, e_name in enumerate(self.mc_names):
- 564            if len(self.e_content[e_name]) == 1:
- 565                print('No replica distribution for a single replicum (', e_name, ')')
- 566                continue
- 567            r_length = []
- 568            sub_r_mean = 0
- 569            for r, r_name in enumerate(self.e_content[e_name]):
- 570                r_length.append(len(self.deltas[r_name]))
- 571                sub_r_mean += self.shape[r_name] * self.r_values[r_name]
- 572            e_N = np.sum(r_length)
- 573            sub_r_mean /= e_N
- 574            arr = np.zeros(len(self.e_content[e_name]))
- 575            for r, r_name in enumerate(self.e_content[e_name]):
- 576                arr[r] = (self.r_values[r_name] - sub_r_mean) / (self.e_dvalue[e_name] * np.sqrt(e_N / self.shape[r_name] - 1))
- 577            plt.hist(arr, rwidth=0.8, bins=len(self.e_content[e_name]))
- 578            plt.title('Replica distribution' + e_name + ' (mean=0, var=1)')
- 579            plt.draw()
- 580
- 581    def plot_history(self, expand=True):
- 582        """Plot derived Monte Carlo history for each ensemble
+ 564            fig = plt.figure()
+ 565            plt.xlabel('W')
+ 566            plt.ylabel('rho')
+ 567            length = len(self.e_drho[e_name])
+ 568            plt.errorbar(np.arange(length), self.e_rho[e_name][:length], yerr=self.e_drho[e_name][:], linewidth=1, capsize=2)
+ 569            plt.axvline(x=self.e_windowsize[e_name], color='r', alpha=0.25, ls='--', marker=',')
+ 570            if self.tau_exp[e_name] > 0:
+ 571                plt.plot([self.e_windowsize[e_name] + 1, self.e_windowsize[e_name] + 1 + 2 * self.tau_exp[e_name]],
+ 572                         [self.e_rho[e_name][self.e_windowsize[e_name] + 1], 0], 'k-', lw=1)
+ 573                xmax = self.e_windowsize[e_name] + 2 * self.tau_exp[e_name] + 1.5
+ 574                plt.title('Rho ' + e_name + r', tau\_exp=' + str(np.around(self.tau_exp[e_name], decimals=2)))
+ 575            else:
+ 576                xmax = max(10.5, 2 * self.e_windowsize[e_name] - 0.5)
+ 577                plt.title('Rho ' + e_name + ', S=' + str(np.around(self.S[e_name], decimals=2)))
+ 578            plt.plot([-0.5, xmax], [0, 0], 'k--', lw=1)
+ 579            plt.xlim(-0.5, xmax)
+ 580            plt.draw()
+ 581            if save:
+ 582                fig.savefig(save + "_" + str(e))
  583
- 584        Parameters
- 585        ----------
- 586        expand : bool
- 587            show expanded history for irregular Monte Carlo chains (default: True).
- 588        """
- 589        for e, e_name in enumerate(self.mc_names):
- 590            plt.figure()
- 591            r_length = []
- 592            tmp = []
- 593            tmp_expanded = []
- 594            for r, r_name in enumerate(self.e_content[e_name]):
- 595                tmp.append(self.deltas[r_name] + self.r_values[r_name])
- 596                if expand:
- 597                    tmp_expanded.append(_expand_deltas(self.deltas[r_name], list(self.idl[r_name]), self.shape[r_name], 1) + self.r_values[r_name])
- 598                    r_length.append(len(tmp_expanded[-1]))
- 599                else:
- 600                    r_length.append(len(tmp[-1]))
- 601            e_N = np.sum(r_length)
- 602            x = np.arange(e_N)
- 603            y_test = np.concatenate(tmp, axis=0)
- 604            if expand:
- 605                y = np.concatenate(tmp_expanded, axis=0)
- 606            else:
- 607                y = y_test
- 608            plt.errorbar(x, y, fmt='.', markersize=3)
- 609            plt.xlim(-0.5, e_N - 0.5)
- 610            plt.title(e_name + f'\nskew: {skew(y_test):.3f} (p={skewtest(y_test).pvalue:.3f}), kurtosis: {kurtosis(y_test):.3f} (p={kurtosistest(y_test).pvalue:.3f})')
- 611            plt.draw()
- 612
- 613    def plot_piechart(self, save=None):
- 614        """Plot piechart which shows the fractional contribution of each
- 615        ensemble to the error and returns a dictionary containing the fractions.
- 616
- 617        Parameters
- 618        ----------
- 619        save : str
- 620            saves the figure to a file named 'save' if.
- 621        """
- 622        if not hasattr(self, 'e_dvalue'):
- 623            raise Exception('Run the gamma method first.')
- 624        if np.isclose(0.0, self._dvalue, atol=1e-15):
- 625            raise ValueError('Error is 0.0')
- 626        labels = self.e_names
- 627        sizes = [self.e_dvalue[name] ** 2 for name in labels] / self._dvalue ** 2
- 628        fig1, ax1 = plt.subplots()
- 629        ax1.pie(sizes, labels=labels, startangle=90, normalize=True)
- 630        ax1.axis('equal')
- 631        plt.draw()
- 632        if save:
- 633            fig1.savefig(save)
- 634
- 635        return dict(zip(labels, sizes))
- 636
- 637    def dump(self, filename, datatype="json.gz", description="", **kwargs):
- 638        """Dump the Obs to a file 'name' of chosen format.
- 639
- 640        Parameters
- 641        ----------
- 642        filename : str
- 643            name of the file to be saved.
- 644        datatype : str
- 645            Format of the exported file. Supported formats include
- 646            "json.gz" and "pickle"
- 647        description : str
- 648            Description for output file, only relevant for json.gz format.
- 649        path : str
- 650            specifies a custom path for the file (default '.')
- 651        """
- 652        if 'path' in kwargs:
- 653            file_name = kwargs.get('path') + '/' + filename
- 654        else:
- 655            file_name = filename
- 656
- 657        if datatype == "json.gz":
- 658            from .input.json import dump_to_json
- 659            dump_to_json([self], file_name, description=description)
- 660        elif datatype == "pickle":
- 661            with open(file_name + '.p', 'wb') as fb:
- 662                pickle.dump(self, fb)
- 663        else:
- 664            raise TypeError("Unknown datatype " + str(datatype))
- 665
- 666    def export_jackknife(self):
- 667        """Export jackknife samples from the Obs
- 668
- 669        Returns
- 670        -------
- 671        numpy.ndarray
- 672            Returns a numpy array of length N + 1 where N is the number of samples
- 673            for the given ensemble and replicum. The zeroth entry of the array contains
- 674            the mean value of the Obs, entries 1 to N contain the N jackknife samples
- 675            derived from the Obs. The current implementation only works for observables
- 676            defined on exactly one ensemble and replicum. The derived jackknife samples
- 677            should agree with samples from a full jackknife analysis up to O(1/N).
- 678        """
- 679
- 680        if len(self.names) != 1:
- 681            raise ValueError("'export_jackknife' is only implemented for Obs defined on one ensemble and replicum.")
- 682
- 683        name = self.names[0]
- 684        full_data = self.deltas[name] + self.r_values[name]
- 685        n = full_data.size
- 686        mean = self.value
- 687        tmp_jacks = np.zeros(n + 1)
- 688        tmp_jacks[0] = mean
- 689        tmp_jacks[1:] = (n * mean - full_data) / (n - 1)
- 690        return tmp_jacks
- 691
- 692    def export_bootstrap(self, samples=500, random_numbers=None, save_rng=None):
- 693        """Export bootstrap samples from the Obs
- 694
- 695        Parameters
- 696        ----------
- 697        samples : int
- 698            Number of bootstrap samples to generate.
- 699        random_numbers : np.ndarray
- 700            Array of shape (samples, length) containing the random numbers to generate the bootstrap samples.
- 701            If not provided the bootstrap samples are generated bashed on the md5 hash of the enesmble name.
- 702        save_rng : str
- 703            Save the random numbers to a file if a path is specified.
+ 584    def plot_rep_dist(self):
+ 585        """Plot replica distribution for each ensemble with more than one replicum."""
+ 586        if not hasattr(self, 'e_dvalue'):
+ 587            raise Exception('Run the gamma method first.')
+ 588        for _e, e_name in enumerate(self.mc_names):
+ 589            if len(self.e_content[e_name]) == 1:
+ 590                print('No replica distribution for a single replicum (', e_name, ')')
+ 591                continue
+ 592            r_length = []
+ 593            sub_r_mean = 0
+ 594            for r_name in self.e_content[e_name]:
+ 595                r_length.append(len(self.deltas[r_name]))
+ 596                sub_r_mean += self.shape[r_name] * self.r_values[r_name]
+ 597            e_N = np.sum(r_length)
+ 598            sub_r_mean /= e_N
+ 599            arr = np.zeros(len(self.e_content[e_name]))
+ 600            for r, r_name in enumerate(self.e_content[e_name]):
+ 601                arr[r] = (self.r_values[r_name] - sub_r_mean) / (self.e_dvalue[e_name] * np.sqrt(e_N / self.shape[r_name] - 1))
+ 602            plt.hist(arr, rwidth=0.8, bins=len(self.e_content[e_name]))
+ 603            plt.title('Replica distribution' + e_name + ' (mean=0, var=1)')
+ 604            plt.draw()
+ 605
+ 606    def plot_history(self, expand=True):
+ 607        """Plot derived Monte Carlo history for each ensemble
+ 608
+ 609        Parameters
+ 610        ----------
+ 611        expand : bool
+ 612            show expanded history for irregular Monte Carlo chains (default: True).
+ 613        """
+ 614        for _e, e_name in enumerate(self.mc_names):
+ 615            plt.figure()
+ 616            r_length = []
+ 617            tmp = []
+ 618            tmp_expanded = []
+ 619            for _r, r_name in enumerate(self.e_content[e_name]):
+ 620                tmp.append(self.deltas[r_name] + self.r_values[r_name])
+ 621                if expand:
+ 622                    tmp_expanded.append(_expand_deltas(self.deltas[r_name], list(self.idl[r_name]), self.shape[r_name], 1) + self.r_values[r_name])
+ 623                    r_length.append(len(tmp_expanded[-1]))
+ 624                else:
+ 625                    r_length.append(len(tmp[-1]))
+ 626            e_N = np.sum(r_length)
+ 627            x = np.arange(e_N)
+ 628            y_test = np.concatenate(tmp, axis=0)
+ 629            if expand:
+ 630                y = np.concatenate(tmp_expanded, axis=0)
+ 631            else:
+ 632                y = y_test
+ 633            plt.errorbar(x, y, fmt='.', markersize=3)
+ 634            plt.xlim(-0.5, e_N - 0.5)
+ 635            plt.title(e_name + f'\nskew: {skew(y_test):.3f} (p={skewtest(y_test).pvalue:.3f}), kurtosis: {kurtosis(y_test):.3f} (p={kurtosistest(y_test).pvalue:.3f})')
+ 636            plt.draw()
+ 637
+ 638    def plot_piechart(self, save=None):
+ 639        """Plot piechart which shows the fractional contribution of each
+ 640        ensemble to the error and returns a dictionary containing the fractions.
+ 641
+ 642        Parameters
+ 643        ----------
+ 644        save : str
+ 645            saves the figure to a file named 'save' if.
+ 646        """
+ 647        if not hasattr(self, 'e_dvalue'):
+ 648            raise Exception('Run the gamma method first.')
+ 649        if np.isclose(0.0, self._dvalue, atol=1e-15):
+ 650            raise ValueError('Error is 0.0')
+ 651        labels = self.e_names
+ 652        sizes = [self.e_dvalue[name] ** 2 for name in labels] / self._dvalue ** 2
+ 653        fig1, ax1 = plt.subplots()
+ 654        ax1.pie(sizes, labels=labels, startangle=90, normalize=True)
+ 655        ax1.axis('equal')
+ 656        plt.draw()
+ 657        if save:
+ 658            fig1.savefig(save)
+ 659
+ 660        return dict(zip(labels, sizes, strict=True))
+ 661
+ 662    def dump(self, filename, datatype="json.gz", description="", **kwargs):
+ 663        """Dump the Obs to a file 'name' of chosen format.
+ 664
+ 665        Parameters
+ 666        ----------
+ 667        filename : str
+ 668            name of the file to be saved.
+ 669        datatype : str
+ 670            Format of the exported file. Supported formats include
+ 671            "json.gz" and "pickle"
+ 672        description : str
+ 673            Description for output file, only relevant for json.gz format.
+ 674        path : str
+ 675            specifies a custom path for the file (default '.')
+ 676        """
+ 677        if 'path' in kwargs:
+ 678            file_name = kwargs.get('path') + '/' + filename
+ 679        else:
+ 680            file_name = filename
+ 681
+ 682        if datatype == "json.gz":
+ 683            from .input.json import dump_to_json
+ 684            dump_to_json([self], file_name, description=description)
+ 685        elif datatype == "pickle":
+ 686            with open(file_name + '.p', 'wb') as fb:
+ 687                pickle.dump(self, fb)
+ 688        else:
+ 689            raise TypeError("Unknown datatype " + str(datatype))
+ 690
+ 691    def export_jackknife(self):
+ 692        """Export jackknife samples from the Obs
+ 693
+ 694        Returns
+ 695        -------
+ 696        numpy.ndarray
+ 697            Returns a numpy array of length N + 1 where N is the number of samples
+ 698            for the given ensemble and replicum. The zeroth entry of the array contains
+ 699            the mean value of the Obs, entries 1 to N contain the N jackknife samples
+ 700            derived from the Obs. The current implementation only works for observables
+ 701            defined on exactly one ensemble and replicum. The derived jackknife samples
+ 702            should agree with samples from a full jackknife analysis up to O(1/N).
+ 703        """
  704
- 705        Returns
- 706        -------
- 707        numpy.ndarray
- 708            Returns a numpy array of length N + 1 where N is the number of samples
- 709            for the given ensemble and replicum. The zeroth entry of the array contains
- 710            the mean value of the Obs, entries 1 to N contain the N import_bootstrap samples
- 711            derived from the Obs. The current implementation only works for observables
- 712            defined on exactly one ensemble and replicum. The derived bootstrap samples
- 713            should agree with samples from a full bootstrap analysis up to O(1/N).
- 714        """
- 715        if len(self.names) != 1:
- 716            raise ValueError("'export_boostrap' is only implemented for Obs defined on one ensemble and replicum.")
- 717
- 718        name = self.names[0]
- 719        length = self.N
- 720
- 721        if random_numbers is None:
- 722            seed = int(hashlib.md5(name.encode()).hexdigest(), 16) & 0xFFFFFFFF
- 723            rng = np.random.default_rng(seed)
- 724            random_numbers = rng.integers(0, length, size=(samples, length))
- 725
- 726        if save_rng is not None:
- 727            np.savetxt(save_rng, random_numbers, fmt='%i')
- 728
- 729        proj = np.vstack([np.bincount(o, minlength=length) for o in random_numbers]) / length
- 730        ret = np.zeros(samples + 1)
- 731        ret[0] = self.value
- 732        ret[1:] = proj @ (self.deltas[name] + self.r_values[name])
- 733        return ret
- 734
- 735    def __float__(self):
- 736        return float(self.value)
- 737
- 738    def __repr__(self):
- 739        return 'Obs[' + str(self) + ']'
- 740
- 741    def __str__(self):
- 742        return _format_uncertainty(self.value, self._dvalue)
- 743
- 744    def __format__(self, format_type):
- 745        if format_type == "":
- 746            significance = 2
- 747        else:
- 748            significance = int(float(format_type.replace("+", "").replace("-", "")))
- 749        my_str = _format_uncertainty(self.value, self._dvalue,
- 750                                     significance=significance)
- 751        for char in ["+", " "]:
- 752            if format_type.startswith(char):
- 753                if my_str[0] != "-":
- 754                    my_str = char + my_str
- 755        return my_str
- 756
- 757    def __hash__(self):
- 758        hash_tuple = (np.array([self.value]).astype(np.float32).data.tobytes(),)
- 759        hash_tuple += tuple([o.astype(np.float32).data.tobytes() for o in self.deltas.values()])
- 760        hash_tuple += tuple([np.array([o.errsq()]).astype(np.float32).data.tobytes() for o in self.covobs.values()])
- 761        hash_tuple += tuple([o.encode() for o in self.names])
- 762        m = hashlib.md5()
- 763        [m.update(o) for o in hash_tuple]
- 764        return int(m.hexdigest(), 16) & 0xFFFFFFFF
+ 705        if len(self.names) != 1:
+ 706            raise ValueError("'export_jackknife' is only implemented for Obs defined on one ensemble and replicum.")
+ 707
+ 708        name = self.names[0]
+ 709        full_data = self.deltas[name] + self.r_values[name]
+ 710        n = full_data.size
+ 711        mean = self.value
+ 712        tmp_jacks = np.zeros(n + 1)
+ 713        tmp_jacks[0] = mean
+ 714        tmp_jacks[1:] = (n * mean - full_data) / (n - 1)
+ 715        return tmp_jacks
+ 716
+ 717    def export_bootstrap(self, samples=500, random_numbers=None, save_rng=None):
+ 718        """Export bootstrap samples from the Obs
+ 719
+ 720        Parameters
+ 721        ----------
+ 722        samples : int
+ 723            Number of bootstrap samples to generate.
+ 724        random_numbers : np.ndarray
+ 725            Array of shape (samples, length) containing the random numbers to generate the bootstrap samples.
+ 726            If not provided the bootstrap samples are generated bashed on the md5 hash of the enesmble name.
+ 727        save_rng : str
+ 728            Save the random numbers to a file if a path is specified.
+ 729
+ 730        Returns
+ 731        -------
+ 732        numpy.ndarray
+ 733            Returns a numpy array of length N + 1 where N is the number of samples
+ 734            for the given ensemble and replicum. The zeroth entry of the array contains
+ 735            the mean value of the Obs, entries 1 to N contain the N import_bootstrap samples
+ 736            derived from the Obs. The current implementation only works for observables
+ 737            defined on exactly one ensemble and replicum. The derived bootstrap samples
+ 738            should agree with samples from a full bootstrap analysis up to O(1/N).
+ 739        """
+ 740        if len(self.names) != 1:
+ 741            raise ValueError("'export_boostrap' is only implemented for Obs defined on one ensemble and replicum.")
+ 742
+ 743        name = self.names[0]
+ 744        length = self.N
+ 745
+ 746        if random_numbers is None:
+ 747            seed = int(hashlib.md5(name.encode()).hexdigest(), 16) & 0xFFFFFFFF
+ 748            rng = np.random.default_rng(seed)
+ 749            random_numbers = rng.integers(0, length, size=(samples, length))
+ 750
+ 751        if save_rng is not None:
+ 752            np.savetxt(save_rng, random_numbers, fmt='%i')
+ 753
+ 754        proj = np.vstack([np.bincount(o, minlength=length) for o in random_numbers]) / length
+ 755        ret = np.zeros(samples + 1)
+ 756        ret[0] = self.value
+ 757        ret[1:] = proj @ (self.deltas[name] + self.r_values[name])
+ 758        return ret
+ 759
+ 760    def __float__(self):
+ 761        return float(self.value)
+ 762
+ 763    def __repr__(self):
+ 764        return 'Obs[' + str(self) + ']'
  765
- 766    # Overload comparisons
- 767    def __lt__(self, other):
- 768        return self.value < other
- 769
- 770    def __le__(self, other):
- 771        return self.value <= other
- 772
- 773    def __gt__(self, other):
- 774        return self.value > other
- 775
- 776    def __ge__(self, other):
- 777        return self.value >= other
- 778
- 779    def __eq__(self, other):
- 780        if other is None:
- 781            return False
- 782        return (self - other).is_zero()
- 783
- 784    # Overload math operations
- 785    def __add__(self, y):
- 786        if isinstance(y, Obs):
- 787            return derived_observable(lambda x, **kwargs: x[0] + x[1], [self, y], man_grad=[1, 1])
- 788        else:
- 789            if isinstance(y, np.ndarray):
- 790                return np.array([self + o for o in y])
- 791            elif isinstance(y, complex):
- 792                return CObs(self, 0) + y
- 793            elif y.__class__.__name__ in ['Corr', 'CObs']:
- 794                return NotImplemented
- 795            else:
- 796                return derived_observable(lambda x, **kwargs: x[0] + y, [self], man_grad=[1])
+ 766    def __str__(self):
+ 767        return _format_uncertainty(self.value, self._dvalue)
+ 768
+ 769    def __format__(self, format_type):
+ 770        if format_type == "":
+ 771            significance = 2
+ 772        else:
+ 773            significance = int(float(format_type.replace("+", "").replace("-", "")))
+ 774        my_str = _format_uncertainty(self.value, self._dvalue,
+ 775                                     significance=significance)
+ 776        for char in ["+", " "]:
+ 777            if format_type.startswith(char):
+ 778                if my_str[0] != "-":
+ 779                    my_str = char + my_str
+ 780        return my_str
+ 781
+ 782    def __hash__(self):
+ 783        hash_tuple = (np.array([self.value]).astype(np.float32).data.tobytes(),)
+ 784        hash_tuple += tuple([o.astype(np.float32).data.tobytes() for o in self.deltas.values()])
+ 785        hash_tuple += tuple([np.array([o.errsq()]).astype(np.float32).data.tobytes() for o in self.covobs.values()])
+ 786        hash_tuple += tuple([o.encode() for o in self.names])
+ 787        m = hashlib.md5()
+ 788        [m.update(o) for o in hash_tuple]
+ 789        return int(m.hexdigest(), 16) & 0xFFFFFFFF
+ 790
+ 791    # Overload comparisons
+ 792    def __lt__(self, other):
+ 793        return self.value < other
+ 794
+ 795    def __le__(self, other):
+ 796        return self.value <= other
  797
- 798    def __radd__(self, y):
- 799        return self + y
+ 798    def __gt__(self, other):
+ 799        return self.value > other
  800
- 801    def __mul__(self, y):
- 802        if isinstance(y, Obs):
- 803            return derived_observable(lambda x, **kwargs: x[0] * x[1], [self, y], man_grad=[y.value, self.value])
- 804        else:
- 805            if isinstance(y, np.ndarray):
- 806                return np.array([self * o for o in y])
- 807            elif isinstance(y, complex):
- 808                return CObs(self * y.real, self * y.imag)
- 809            elif y.__class__.__name__ in ['Corr', 'CObs']:
- 810                return NotImplemented
- 811            else:
- 812                return derived_observable(lambda x, **kwargs: x[0] * y, [self], man_grad=[y])
- 813
- 814    def __rmul__(self, y):
- 815        return self * y
- 816
- 817    def __sub__(self, y):
- 818        if isinstance(y, Obs):
- 819            return derived_observable(lambda x, **kwargs: x[0] - x[1], [self, y], man_grad=[1, -1])
- 820        else:
- 821            if isinstance(y, np.ndarray):
- 822                return np.array([self - o for o in y])
- 823            elif y.__class__.__name__ in ['Corr', 'CObs']:
- 824                return NotImplemented
- 825            else:
- 826                return derived_observable(lambda x, **kwargs: x[0] - y, [self], man_grad=[1])
- 827
- 828    def __rsub__(self, y):
- 829        return -1 * (self - y)
- 830
- 831    def __pos__(self):
- 832        return self
- 833
- 834    def __neg__(self):
- 835        return -1 * self
- 836
- 837    def __truediv__(self, y):
- 838        if isinstance(y, Obs):
- 839            return derived_observable(lambda x, **kwargs: x[0] / x[1], [self, y], man_grad=[1 / y.value, - self.value / y.value ** 2])
- 840        else:
- 841            if isinstance(y, np.ndarray):
- 842                return np.array([self / o for o in y])
- 843            elif y.__class__.__name__ in ['Corr', 'CObs']:
- 844                return NotImplemented
- 845            else:
- 846                return derived_observable(lambda x, **kwargs: x[0] / y, [self], man_grad=[1 / y])
- 847
- 848    def __rtruediv__(self, y):
- 849        if isinstance(y, Obs):
- 850            return derived_observable(lambda x, **kwargs: x[0] / x[1], [y, self], man_grad=[1 / self.value, - y.value / self.value ** 2])
- 851        else:
- 852            if isinstance(y, np.ndarray):
- 853                return np.array([o / self for o in y])
- 854            elif y.__class__.__name__ in ['Corr', 'CObs']:
- 855                return NotImplemented
- 856            else:
- 857                return derived_observable(lambda x, **kwargs: y / x[0], [self], man_grad=[-y / self.value ** 2])
+ 801    def __ge__(self, other):
+ 802        return self.value >= other
+ 803
+ 804    def __eq__(self, other):
+ 805        if other is None:
+ 806            return False
+ 807        return (self - other).is_zero()
+ 808
+ 809    # Overload math operations
+ 810    def __add__(self, y):
+ 811        if isinstance(y, Obs):
+ 812            return derived_observable(lambda x, **kwargs: x[0] + x[1], [self, y], man_grad=[1, 1])
+ 813        else:
+ 814            if isinstance(y, np.ndarray):
+ 815                return np.array([self + o for o in y])
+ 816            elif isinstance(y, complex):
+ 817                return CObs(self, 0) + y
+ 818            elif y.__class__.__name__ in ['Corr', 'CObs']:
+ 819                return NotImplemented
+ 820            else:
+ 821                return derived_observable(lambda x, **kwargs: x[0] + y, [self], man_grad=[1])
+ 822
+ 823    def __radd__(self, y):
+ 824        return self + y
+ 825
+ 826    def __mul__(self, y):
+ 827        if isinstance(y, Obs):
+ 828            return derived_observable(lambda x, **kwargs: x[0] * x[1], [self, y], man_grad=[y.value, self.value])
+ 829        else:
+ 830            if isinstance(y, np.ndarray):
+ 831                return np.array([self * o for o in y])
+ 832            elif isinstance(y, complex):
+ 833                return CObs(self * y.real, self * y.imag)
+ 834            elif y.__class__.__name__ in ['Corr', 'CObs']:
+ 835                return NotImplemented
+ 836            else:
+ 837                return derived_observable(lambda x, **kwargs: x[0] * y, [self], man_grad=[y])
+ 838
+ 839    def __rmul__(self, y):
+ 840        return self * y
+ 841
+ 842    def __sub__(self, y):
+ 843        if isinstance(y, Obs):
+ 844            return derived_observable(lambda x, **kwargs: x[0] - x[1], [self, y], man_grad=[1, -1])
+ 845        else:
+ 846            if isinstance(y, np.ndarray):
+ 847                return np.array([self - o for o in y])
+ 848            elif y.__class__.__name__ in ['Corr', 'CObs']:
+ 849                return NotImplemented
+ 850            else:
+ 851                return derived_observable(lambda x, **kwargs: x[0] - y, [self], man_grad=[1])
+ 852
+ 853    def __rsub__(self, y):
+ 854        return -1 * (self - y)
+ 855
+ 856    def __pos__(self):
+ 857        return self
  858
- 859    def __pow__(self, y):
- 860        if isinstance(y, Obs):
- 861            return derived_observable(lambda x, **kwargs: x[0] ** x[1], [self, y], man_grad=[y.value * self.value ** (y.value - 1), self.value ** y.value * np.log(self.value)])
- 862        else:
- 863            return derived_observable(lambda x, **kwargs: x[0] ** y, [self], man_grad=[y * self.value ** (y - 1)])
- 864
- 865    def __rpow__(self, y):
- 866        return derived_observable(lambda x, **kwargs: y ** x[0], [self], man_grad=[y ** self.value * np.log(y)])
- 867
- 868    def __abs__(self):
- 869        return derived_observable(lambda x: anp.abs(x[0]), [self])
- 870
- 871    # Overload numpy functions
- 872    def sqrt(self):
- 873        return derived_observable(lambda x, **kwargs: np.sqrt(x[0]), [self], man_grad=[1 / 2 / np.sqrt(self.value)])
- 874
- 875    def log(self):
- 876        return derived_observable(lambda x, **kwargs: np.log(x[0]), [self], man_grad=[1 / self.value])
- 877
- 878    def exp(self):
- 879        return derived_observable(lambda x, **kwargs: np.exp(x[0]), [self], man_grad=[np.exp(self.value)])
- 880
- 881    def sin(self):
- 882        return derived_observable(lambda x, **kwargs: np.sin(x[0]), [self], man_grad=[np.cos(self.value)])
+ 859    def __neg__(self):
+ 860        return -1 * self
+ 861
+ 862    def __truediv__(self, y):
+ 863        if isinstance(y, Obs):
+ 864            return derived_observable(lambda x, **kwargs: x[0] / x[1], [self, y], man_grad=[1 / y.value, - self.value / y.value ** 2])
+ 865        else:
+ 866            if isinstance(y, np.ndarray):
+ 867                return np.array([self / o for o in y])
+ 868            elif y.__class__.__name__ in ['Corr', 'CObs']:
+ 869                return NotImplemented
+ 870            else:
+ 871                return derived_observable(lambda x, **kwargs: x[0] / y, [self], man_grad=[1 / y])
+ 872
+ 873    def __rtruediv__(self, y):
+ 874        if isinstance(y, Obs):
+ 875            return derived_observable(lambda x, **kwargs: x[0] / x[1], [y, self], man_grad=[1 / self.value, - y.value / self.value ** 2])
+ 876        else:
+ 877            if isinstance(y, np.ndarray):
+ 878                return np.array([o / self for o in y])
+ 879            elif y.__class__.__name__ in ['Corr', 'CObs']:
+ 880                return NotImplemented
+ 881            else:
+ 882                return derived_observable(lambda x, **kwargs: y / x[0], [self], man_grad=[-y / self.value ** 2])
  883
- 884    def cos(self):
- 885        return derived_observable(lambda x, **kwargs: np.cos(x[0]), [self], man_grad=[-np.sin(self.value)])
- 886
- 887    def tan(self):
- 888        return derived_observable(lambda x, **kwargs: np.tan(x[0]), [self], man_grad=[1 / np.cos(self.value) ** 2])
+ 884    def __pow__(self, y):
+ 885        if isinstance(y, Obs):
+ 886            return derived_observable(lambda x, **kwargs: x[0] ** x[1], [self, y], man_grad=[y.value * self.value ** (y.value - 1), self.value ** y.value * np.log(self.value)])
+ 887        else:
+ 888            return derived_observable(lambda x, **kwargs: x[0] ** y, [self], man_grad=[y * self.value ** (y - 1)])
  889
- 890    def arcsin(self):
- 891        return derived_observable(lambda x: anp.arcsin(x[0]), [self])
+ 890    def __rpow__(self, y):
+ 891        return derived_observable(lambda x, **kwargs: y ** x[0], [self], man_grad=[y ** self.value * np.log(y)])
  892
- 893    def arccos(self):
- 894        return derived_observable(lambda x: anp.arccos(x[0]), [self])
+ 893    def __abs__(self):
+ 894        return derived_observable(lambda x: anp.abs(x[0]), [self])
  895
- 896    def arctan(self):
- 897        return derived_observable(lambda x: anp.arctan(x[0]), [self])
- 898
- 899    def sinh(self):
- 900        return derived_observable(lambda x, **kwargs: np.sinh(x[0]), [self], man_grad=[np.cosh(self.value)])
- 901
- 902    def cosh(self):
- 903        return derived_observable(lambda x, **kwargs: np.cosh(x[0]), [self], man_grad=[np.sinh(self.value)])
- 904
- 905    def tanh(self):
- 906        return derived_observable(lambda x, **kwargs: np.tanh(x[0]), [self], man_grad=[1 / np.cosh(self.value) ** 2])
- 907
- 908    def arcsinh(self):
- 909        return derived_observable(lambda x: anp.arcsinh(x[0]), [self])
- 910
- 911    def arccosh(self):
- 912        return derived_observable(lambda x: anp.arccosh(x[0]), [self])
- 913
- 914    def arctanh(self):
- 915        return derived_observable(lambda x: anp.arctanh(x[0]), [self])
- 916
+ 896    # Overload numpy functions
+ 897    def sqrt(self):
+ 898        return derived_observable(lambda x, **kwargs: np.sqrt(x[0]), [self], man_grad=[1 / 2 / np.sqrt(self.value)])
+ 899
+ 900    def log(self):
+ 901        return derived_observable(lambda x, **kwargs: np.log(x[0]), [self], man_grad=[1 / self.value])
+ 902
+ 903    def exp(self):
+ 904        return derived_observable(lambda x, **kwargs: np.exp(x[0]), [self], man_grad=[np.exp(self.value)])
+ 905
+ 906    def sin(self):
+ 907        return derived_observable(lambda x, **kwargs: np.sin(x[0]), [self], man_grad=[np.cos(self.value)])
+ 908
+ 909    def cos(self):
+ 910        return derived_observable(lambda x, **kwargs: np.cos(x[0]), [self], man_grad=[-np.sin(self.value)])
+ 911
+ 912    def tan(self):
+ 913        return derived_observable(lambda x, **kwargs: np.tan(x[0]), [self], man_grad=[1 / np.cos(self.value) ** 2])
+ 914
+ 915    def arcsin(self):
+ 916        return derived_observable(lambda x: anp.arcsin(x[0]), [self])
  917
- 918class CObs:
- 919    """Class for a complex valued observable."""
- 920    __slots__ = ['_real', '_imag', 'tag']
- 921
- 922    def __init__(self, real, imag=0.0):
- 923        self._real = real
- 924        self._imag = imag
- 925        self.tag = None
+ 918    def arccos(self):
+ 919        return derived_observable(lambda x: anp.arccos(x[0]), [self])
+ 920
+ 921    def arctan(self):
+ 922        return derived_observable(lambda x: anp.arctan(x[0]), [self])
+ 923
+ 924    def sinh(self):
+ 925        return derived_observable(lambda x, **kwargs: np.sinh(x[0]), [self], man_grad=[np.cosh(self.value)])
  926
- 927    @property
- 928    def real(self):
- 929        return self._real
- 930
- 931    @property
- 932    def imag(self):
- 933        return self._imag
- 934
- 935    def gamma_method(self, **kwargs):
- 936        """Executes the gamma_method for the real and the imaginary part."""
- 937        if isinstance(self.real, Obs):
- 938            self.real.gamma_method(**kwargs)
- 939        if isinstance(self.imag, Obs):
- 940            self.imag.gamma_method(**kwargs)
+ 927    def cosh(self):
+ 928        return derived_observable(lambda x, **kwargs: np.cosh(x[0]), [self], man_grad=[np.sinh(self.value)])
+ 929
+ 930    def tanh(self):
+ 931        return derived_observable(lambda x, **kwargs: np.tanh(x[0]), [self], man_grad=[1 / np.cosh(self.value) ** 2])
+ 932
+ 933    def arcsinh(self):
+ 934        return derived_observable(lambda x: anp.arcsinh(x[0]), [self])
+ 935
+ 936    def arccosh(self):
+ 937        return derived_observable(lambda x: anp.arccosh(x[0]), [self])
+ 938
+ 939    def arctanh(self):
+ 940        return derived_observable(lambda x: anp.arctanh(x[0]), [self])
  941
- 942    def is_zero(self):
- 943        """Checks whether both real and imaginary part are zero within machine precision."""
- 944        return self.real == 0.0 and self.imag == 0.0
- 945
- 946    def conjugate(self):
- 947        return CObs(self.real, -self.imag)
- 948
- 949    def __add__(self, other):
- 950        if isinstance(other, np.ndarray):
- 951            return other + self
- 952        elif hasattr(other, 'real') and hasattr(other, 'imag'):
- 953            return CObs(self.real + other.real,
- 954                        self.imag + other.imag)
- 955        else:
- 956            return CObs(self.real + other, self.imag)
- 957
- 958    def __radd__(self, y):
- 959        return self + y
- 960
- 961    def __sub__(self, other):
- 962        if isinstance(other, np.ndarray):
- 963            return -1 * (other - self)
- 964        elif hasattr(other, 'real') and hasattr(other, 'imag'):
- 965            return CObs(self.real - other.real, self.imag - other.imag)
- 966        else:
- 967            return CObs(self.real - other, self.imag)
- 968
- 969    def __rsub__(self, other):
- 970        return -1 * (self - other)
- 971
- 972    def __mul__(self, other):
- 973        if isinstance(other, np.ndarray):
- 974            return other * self
- 975        elif hasattr(other, 'real') and hasattr(other, 'imag'):
- 976            if all(isinstance(i, Obs) for i in [self.real, self.imag, other.real, other.imag]):
- 977                return CObs(derived_observable(lambda x, **kwargs: x[0] * x[1] - x[2] * x[3],
- 978                                               [self.real, other.real, self.imag, other.imag],
- 979                                               man_grad=[other.real.value, self.real.value, -other.imag.value, -self.imag.value]),
- 980                            derived_observable(lambda x, **kwargs: x[2] * x[1] + x[0] * x[3],
- 981                                               [self.real, other.real, self.imag, other.imag],
- 982                                               man_grad=[other.imag.value, self.imag.value, other.real.value, self.real.value]))
- 983            elif getattr(other, 'imag', 0) != 0:
- 984                return CObs(self.real * other.real - self.imag * other.imag,
- 985                            self.imag * other.real + self.real * other.imag)
- 986            else:
- 987                return CObs(self.real * other.real, self.imag * other.real)
- 988        else:
- 989            return CObs(self.real * other, self.imag * other)
- 990
- 991    def __rmul__(self, other):
- 992        return self * other
+ 942
+ 943class CObs:
+ 944    """Class for a complex valued observable."""
+ 945    __slots__ = ['_imag', '_real', 'tag']
+ 946
+ 947    def __init__(self, real, imag=0.0):
+ 948        self._real = real
+ 949        self._imag = imag
+ 950        self.tag = None
+ 951
+ 952    @property
+ 953    def real(self):
+ 954        return self._real
+ 955
+ 956    @property
+ 957    def imag(self):
+ 958        return self._imag
+ 959
+ 960    def gamma_method(self, **kwargs):
+ 961        """Executes the gamma_method for the real and the imaginary part."""
+ 962        if isinstance(self.real, Obs):
+ 963            self.real.gamma_method(**kwargs)
+ 964        if isinstance(self.imag, Obs):
+ 965            self.imag.gamma_method(**kwargs)
+ 966
+ 967    def is_zero(self):
+ 968        """Checks whether both real and imaginary part are zero within machine precision."""
+ 969        return self.real == 0.0 and self.imag == 0.0
+ 970
+ 971    def conjugate(self):
+ 972        return CObs(self.real, -self.imag)
+ 973
+ 974    def __add__(self, other):
+ 975        if isinstance(other, np.ndarray):
+ 976            return other + self
+ 977        elif hasattr(other, 'real') and hasattr(other, 'imag'):
+ 978            return CObs(self.real + other.real,
+ 979                        self.imag + other.imag)
+ 980        else:
+ 981            return CObs(self.real + other, self.imag)
+ 982
+ 983    def __radd__(self, y):
+ 984        return self + y
+ 985
+ 986    def __sub__(self, other):
+ 987        if isinstance(other, np.ndarray):
+ 988            return -1 * (other - self)
+ 989        elif hasattr(other, 'real') and hasattr(other, 'imag'):
+ 990            return CObs(self.real - other.real, self.imag - other.imag)
+ 991        else:
+ 992            return CObs(self.real - other, self.imag)
  993
- 994    def __truediv__(self, other):
- 995        if isinstance(other, np.ndarray):
- 996            return 1 / (other / self)
- 997        elif hasattr(other, 'real') and hasattr(other, 'imag'):
- 998            r = other.real ** 2 + other.imag ** 2
- 999            return CObs((self.real * other.real + self.imag * other.imag) / r, (self.imag * other.real - self.real * other.imag) / r)
-1000        else:
-1001            return CObs(self.real / other, self.imag / other)
-1002
-1003    def __rtruediv__(self, other):
-1004        r = self.real ** 2 + self.imag ** 2
-1005        if hasattr(other, 'real') and hasattr(other, 'imag'):
-1006            return CObs((self.real * other.real + self.imag * other.imag) / r, (self.real * other.imag - self.imag * other.real) / r)
-1007        else:
-1008            return CObs(self.real * other / r, -self.imag * other / r)
-1009
-1010    def __abs__(self):
-1011        return np.sqrt(self.real**2 + self.imag**2)
-1012
-1013    def __pos__(self):
-1014        return self
+ 994    def __rsub__(self, other):
+ 995        return -1 * (self - other)
+ 996
+ 997    def __mul__(self, other):
+ 998        if isinstance(other, np.ndarray):
+ 999            return other * self
+1000        elif hasattr(other, 'real') and hasattr(other, 'imag'):
+1001            if all(isinstance(i, Obs) for i in [self.real, self.imag, other.real, other.imag]):
+1002                return CObs(derived_observable(lambda x, **kwargs: x[0] * x[1] - x[2] * x[3],
+1003                                               [self.real, other.real, self.imag, other.imag],
+1004                                               man_grad=[other.real.value, self.real.value, -other.imag.value, -self.imag.value]),
+1005                            derived_observable(lambda x, **kwargs: x[2] * x[1] + x[0] * x[3],
+1006                                               [self.real, other.real, self.imag, other.imag],
+1007                                               man_grad=[other.imag.value, self.imag.value, other.real.value, self.real.value]))
+1008            elif getattr(other, 'imag', 0) != 0:
+1009                return CObs(self.real * other.real - self.imag * other.imag,
+1010                            self.imag * other.real + self.real * other.imag)
+1011            else:
+1012                return CObs(self.real * other.real, self.imag * other.real)
+1013        else:
+1014            return CObs(self.real * other, self.imag * other)
 1015
-1016    def __neg__(self):
-1017        return -1 * self
+1016    def __rmul__(self, other):
+1017        return self * other
 1018
-1019    def __eq__(self, other):
-1020        return self.real == other.real and self.imag == other.imag
-1021
-1022    def __str__(self):
-1023        return '(' + str(self.real) + int(self.imag >= 0.0) * '+' + str(self.imag) + 'j)'
-1024
-1025    def __repr__(self):
-1026        return 'CObs[' + str(self) + ']'
+1019    def __truediv__(self, other):
+1020        if isinstance(other, np.ndarray):
+1021            return 1 / (other / self)
+1022        elif hasattr(other, 'real') and hasattr(other, 'imag'):
+1023            r = other.real ** 2 + other.imag ** 2
+1024            return CObs((self.real * other.real + self.imag * other.imag) / r, (self.imag * other.real - self.real * other.imag) / r)
+1025        else:
+1026            return CObs(self.real / other, self.imag / other)
 1027
-1028    def __format__(self, format_type):
-1029        if format_type == "":
-1030            significance = 2
-1031            format_type = "2"
+1028    def __rtruediv__(self, other):
+1029        r = self.real ** 2 + self.imag ** 2
+1030        if hasattr(other, 'real') and hasattr(other, 'imag'):
+1031            return CObs((self.real * other.real + self.imag * other.imag) / r, (self.real * other.imag - self.imag * other.real) / r)
 1032        else:
-1033            significance = int(float(format_type.replace("+", "").replace("-", "")))
-1034        return f"({self.real:{format_type}}{self.imag:+{significance}}j)"
-1035
-1036
-1037def gamma_method(x, **kwargs):
-1038    """Vectorized version of the gamma_method applicable to lists or arrays of Obs.
-1039
-1040    See docstring of pe.Obs.gamma_method for details.
-1041    """
-1042    return np.vectorize(lambda o: o.gm(**kwargs))(x)
+1033            return CObs(self.real * other / r, -self.imag * other / r)
+1034
+1035    def __abs__(self):
+1036        return np.sqrt(self.real**2 + self.imag**2)
+1037
+1038    def __pos__(self):
+1039        return self
+1040
+1041    def __neg__(self):
+1042        return -1 * self
 1043
-1044
-1045gm = gamma_method
+1044    def __eq__(self, other):
+1045        return self.real == other.real and self.imag == other.imag
 1046
-1047
-1048def _format_uncertainty(value, dvalue, significance=2):
-1049    """Creates a string of a value and its error in paranthesis notation, e.g., 13.02(45)"""
-1050    if dvalue == 0.0 or (not np.isfinite(dvalue)):
-1051        return str(value)
-1052    if not isinstance(significance, int):
-1053        raise TypeError("significance needs to be an integer.")
-1054    if significance < 1:
-1055        raise ValueError("significance needs to be larger than zero.")
-1056    fexp = np.floor(np.log10(dvalue))
-1057    if fexp < 0.0:
-1058        return '{:{form}}({:1.0f})'.format(value, dvalue * 10 ** (-fexp + significance - 1), form='.' + str(-int(fexp) + significance - 1) + 'f')
-1059    elif fexp == 0.0:
-1060        return f"{value:.{significance - 1}f}({dvalue:1.{significance - 1}f})"
-1061    else:
-1062        return f"{value:.{max(0, int(significance - fexp - 1))}f}({dvalue:2.{max(0, int(significance - fexp - 1))}f})"
+1047    __hash__ = None
+1048
+1049    def __str__(self):
+1050        return '(' + str(self.real) + int(self.imag >= 0.0) * '+' + str(self.imag) + 'j)'
+1051
+1052    def __repr__(self):
+1053        return 'CObs[' + str(self) + ']'
+1054
+1055    def __format__(self, format_type):
+1056        if format_type == "":
+1057            significance = 2
+1058            format_type = "2"
+1059        else:
+1060            significance = int(float(format_type.replace("+", "").replace("-", "")))
+1061        return f"({self.real:{format_type}}{self.imag:+{significance}}j)"
+1062
 1063
-1064
-1065def _expand_deltas(deltas, idx, shape, gapsize):
-1066    """Expand deltas defined on idx to a regular range with spacing gapsize between two
-1067       configurations and where holes are filled by 0.
-1068       If idx is of type range, the deltas are not changed if the idx.step == gapsize.
-1069
-1070    Parameters
-1071    ----------
-1072    deltas : list
-1073        List of fluctuations
-1074    idx : list
-1075        List or range of configs on which the deltas are defined, has to be sorted in ascending order.
-1076    shape : int
-1077        Number of configs in idx.
-1078    gapsize : int
-1079        The target distance between two configurations. If longer distances
-1080        are found in idx, the data is expanded.
-1081    """
-1082    if isinstance(idx, range):
-1083        if (idx.step == gapsize):
-1084            return deltas
-1085    ret = np.zeros((idx[-1] - idx[0] + gapsize) // gapsize)
-1086    for i in range(shape):
-1087        ret[(idx[i] - idx[0]) // gapsize] = deltas[i]
-1088    return ret
-1089
+1064def gamma_method(x, **kwargs):
+1065    """Vectorized version of the gamma_method applicable to lists or arrays of Obs.
+1066
+1067    See docstring of pe.Obs.gamma_method for details.
+1068    """
+1069    return np.vectorize(lambda o: o.gm(**kwargs))(x)
+1070
+1071
+1072gm = gamma_method
+1073
+1074
+1075def _format_uncertainty(value, dvalue, significance=2):
+1076    """Creates a string of a value and its error in paranthesis notation, e.g., 13.02(45)"""
+1077    if dvalue == 0.0 or (not np.isfinite(dvalue)):
+1078        return str(value)
+1079    if not isinstance(significance, int):
+1080        raise TypeError("significance needs to be an integer.")
+1081    if significance < 1:
+1082        raise ValueError("significance needs to be larger than zero.")
+1083    fexp = np.floor(np.log10(dvalue))
+1084    if fexp < 0.0:
+1085        return '{:{form}}({:1.0f})'.format(value, dvalue * 10 ** (-fexp + significance - 1), form='.' + str(-int(fexp) + significance - 1) + 'f')
+1086    elif fexp == 0.0:
+1087        return f"{value:.{significance - 1}f}({dvalue:1.{significance - 1}f})"
+1088    else:
+1089        return f"{value:.{max(0, int(significance - fexp - 1))}f}({dvalue:2.{max(0, int(significance - fexp - 1))}f})"
 1090
-1091def _merge_idx(idl):
-1092    """Returns the union of all lists in idl as range or sorted list
-1093
-1094    Parameters
-1095    ----------
-1096    idl : list
-1097        List of lists or ranges.
-1098    """
-1099
-1100    if _check_lists_equal(idl):
-1101        return idl[0]
-1102
-1103    idunion = sorted(set().union(*idl))
-1104
-1105    # Check whether idunion can be expressed as range
-1106    idrange = range(idunion[0], idunion[-1] + 1, idunion[1] - idunion[0])
-1107    idtest = [list(idrange), idunion]
-1108    if _check_lists_equal(idtest):
-1109        return idrange
-1110
-1111    return idunion
-1112
-1113
-1114def _intersection_idx(idl):
-1115    """Returns the intersection of all lists in idl as range or sorted list
+1091
+1092def _expand_deltas(deltas, idx, shape, gapsize):
+1093    """Expand deltas defined on idx to a regular range with spacing gapsize between two
+1094       configurations and where holes are filled by 0.
+1095       If idx is of type range, the deltas are not changed if the idx.step == gapsize.
+1096
+1097    Parameters
+1098    ----------
+1099    deltas : list
+1100        List of fluctuations
+1101    idx : list
+1102        List or range of configs on which the deltas are defined, has to be sorted in ascending order.
+1103    shape : int
+1104        Number of configs in idx.
+1105    gapsize : int
+1106        The target distance between two configurations. If longer distances
+1107        are found in idx, the data is expanded.
+1108    """
+1109    if isinstance(idx, range):
+1110        if (idx.step == gapsize):
+1111            return deltas
+1112    ret = np.zeros((idx[-1] - idx[0] + gapsize) // gapsize)
+1113    for i in range(shape):
+1114        ret[(idx[i] - idx[0]) // gapsize] = deltas[i]
+1115    return ret
 1116
-1117    Parameters
-1118    ----------
-1119    idl : list
-1120        List of lists or ranges.
-1121    """
-1122
-1123    if _check_lists_equal(idl):
-1124        return idl[0]
-1125
-1126    idinter = sorted(set.intersection(*[set(o) for o in idl]))
-1127
-1128    # Check whether idinter can be expressed as range
-1129    try:
-1130        idrange = range(idinter[0], idinter[-1] + 1, idinter[1] - idinter[0])
-1131        idtest = [list(idrange), idinter]
-1132        if _check_lists_equal(idtest):
-1133            return idrange
-1134    except IndexError:
-1135        pass
-1136
-1137    return idinter
-1138
+1117
+1118def _merge_idx(idl):
+1119    """Returns the union of all lists in idl as range or sorted list
+1120
+1121    Parameters
+1122    ----------
+1123    idl : list
+1124        List of lists or ranges.
+1125    """
+1126
+1127    if _check_lists_equal(idl):
+1128        return idl[0]
+1129
+1130    idunion = sorted(set().union(*idl))
+1131
+1132    # Check whether idunion can be expressed as range
+1133    idrange = range(idunion[0], idunion[-1] + 1, idunion[1] - idunion[0])
+1134    idtest = [list(idrange), idunion]
+1135    if _check_lists_equal(idtest):
+1136        return idrange
+1137
+1138    return idunion
 1139
-1140def _expand_deltas_for_merge(deltas, idx, shape, new_idx, scalefactor):
-1141    """Expand deltas defined on idx to the list of configs that is defined by new_idx.
-1142       New, empty entries are filled by 0. If idx and new_idx are of type range, the smallest
-1143       common divisor of the step sizes is used as new step size.
-1144
-1145    Parameters
-1146    ----------
-1147    deltas : list
-1148        List of fluctuations
-1149    idx : list
-1150        List or range of configs on which the deltas are defined.
-1151        Has to be a subset of new_idx and has to be sorted in ascending order.
-1152    shape : list
-1153        Number of configs in idx.
-1154    new_idx : list
-1155        List of configs that defines the new range, has to be sorted in ascending order.
-1156    scalefactor : float
-1157        An additional scaling factor that can be applied to scale the fluctuations,
-1158        e.g., when Obs with differing numbers of replica are merged.
-1159    """
-1160    if type(idx) is range and type(new_idx) is range:
-1161        if idx == new_idx:
-1162            if scalefactor == 1:
-1163                return deltas
-1164            else:
-1165                return deltas * scalefactor
-1166    ret = np.zeros(new_idx[-1] - new_idx[0] + 1)
-1167    for i in range(shape):
-1168        ret[idx[i] - new_idx[0]] = deltas[i]
-1169    return np.array([ret[new_idx[i] - new_idx[0]] for i in range(len(new_idx))]) * len(new_idx) / len(idx) * scalefactor
-1170
+1140
+1141def _intersection_idx(idl):
+1142    """Returns the intersection of all lists in idl as range or sorted list
+1143
+1144    Parameters
+1145    ----------
+1146    idl : list
+1147        List of lists or ranges.
+1148    """
+1149
+1150    if _check_lists_equal(idl):
+1151        return idl[0]
+1152
+1153    idinter = sorted(set.intersection(*[set(o) for o in idl]))
+1154
+1155    # Check whether idinter can be expressed as range
+1156    try:
+1157        idrange = range(idinter[0], idinter[-1] + 1, idinter[1] - idinter[0])
+1158        idtest = [list(idrange), idinter]
+1159        if _check_lists_equal(idtest):
+1160            return idrange
+1161    except IndexError:
+1162        pass
+1163
+1164    return idinter
+1165
+1166
+1167def _expand_deltas_for_merge(deltas, idx, shape, new_idx, scalefactor):
+1168    """Expand deltas defined on idx to the list of configs that is defined by new_idx.
+1169       New, empty entries are filled by 0. If idx and new_idx are of type range, the smallest
+1170       common divisor of the step sizes is used as new step size.
 1171
-1172def derived_observable(func, data, array_mode=False, **kwargs):
-1173    """Construct a derived Obs according to func(data, **kwargs) using automatic differentiation.
-1174
-1175    Parameters
-1176    ----------
-1177    func : object
-1178        arbitrary function of the form func(data, **kwargs). For the
-1179        automatic differentiation to work, all numpy functions have to have
-1180        the autograd wrapper (use 'import autograd.numpy as anp').
-1181    data : list
-1182        list of Obs, e.g. [obs1, obs2, obs3].
-1183    num_grad : bool
-1184        if True, numerical derivatives are used instead of autograd
-1185        (default False). To control the numerical differentiation the
-1186        kwargs of numdifftools.step_generators.MaxStepGenerator
-1187        can be used.
-1188    man_grad : list
-1189        manually supply a list or an array which contains the jacobian
-1190        of func. Use cautiously, supplying the wrong derivative will
-1191        not be intercepted.
-1192
-1193    Notes
-1194    -----
-1195    For simple mathematical operations it can be practical to use anonymous
-1196    functions. For the ratio of two observables one can e.g. use
+1172    Parameters
+1173    ----------
+1174    deltas : list
+1175        List of fluctuations
+1176    idx : list
+1177        List or range of configs on which the deltas are defined.
+1178        Has to be a subset of new_idx and has to be sorted in ascending order.
+1179    shape : list
+1180        Number of configs in idx.
+1181    new_idx : list
+1182        List of configs that defines the new range, has to be sorted in ascending order.
+1183    scalefactor : float
+1184        An additional scaling factor that can be applied to scale the fluctuations,
+1185        e.g., when Obs with differing numbers of replica are merged.
+1186    """
+1187    if type(idx) is range and type(new_idx) is range:
+1188        if idx == new_idx:
+1189            if scalefactor == 1:
+1190                return deltas
+1191            else:
+1192                return deltas * scalefactor
+1193    ret = np.zeros(new_idx[-1] - new_idx[0] + 1)
+1194    for i in range(shape):
+1195        ret[idx[i] - new_idx[0]] = deltas[i]
+1196    return np.array([ret[new_idx[i] - new_idx[0]] for i in range(len(new_idx))]) * len(new_idx) / len(idx) * scalefactor
 1197
-1198    new_obs = derived_observable(lambda x: x[0] / x[1], [obs1, obs2])
-1199    """
-1200
-1201    data = np.asarray(data)
-1202    raveled_data = data.ravel()
-1203
-1204    # Workaround for matrix operations containing non Obs data
-1205    if not all(isinstance(x, Obs) for x in raveled_data):
-1206        for i in range(len(raveled_data)):
-1207            if isinstance(raveled_data[i], (int, float)):
-1208                raveled_data[i] = cov_Obs(raveled_data[i], 0.0, "###dummy_covobs###")
-1209
-1210    allcov = {}
-1211    for o in raveled_data:
-1212        for name in o.cov_names:
-1213            if name in allcov:
-1214                if not np.allclose(allcov[name], o.covobs[name].cov):
-1215                    raise Exception('Inconsistent covariance matrices for %s!' % (name))
-1216            else:
-1217                allcov[name] = o.covobs[name].cov
-1218
-1219    n_obs = len(raveled_data)
-1220    new_names = sorted(set([y for x in [o.names for o in raveled_data] for y in x]))
-1221    new_cov_names = sorted(set([y for x in [o.cov_names for o in raveled_data] for y in x]))
-1222    new_sample_names = sorted(set(new_names) - set(new_cov_names))
-1223
-1224    reweighted = len(list(filter(lambda o: o.reweighted is True, raveled_data))) > 0
-1225
-1226    if data.ndim == 1:
-1227        values = np.array([o.value for o in data])
-1228    else:
-1229        values = np.vectorize(lambda x: x.value)(data)
+1198
+1199def derived_observable(func, data, array_mode=False, **kwargs):
+1200    """Construct a derived Obs according to func(data, **kwargs) using automatic differentiation.
+1201
+1202    Parameters
+1203    ----------
+1204    func : object
+1205        arbitrary function of the form func(data, **kwargs). For the
+1206        automatic differentiation to work, all numpy functions have to have
+1207        the autograd wrapper (use 'import autograd.numpy as anp').
+1208    data : list
+1209        list of Obs, e.g. [obs1, obs2, obs3].
+1210    num_grad : bool
+1211        if True, numerical derivatives are used instead of autograd
+1212        (default False). To control the numerical differentiation the
+1213        kwargs of numdifftools.step_generators.MaxStepGenerator
+1214        can be used.
+1215    man_grad : list
+1216        manually supply a list or an array which contains the jacobian
+1217        of func. Use cautiously, supplying the wrong derivative will
+1218        not be intercepted.
+1219
+1220    Notes
+1221    -----
+1222    For simple mathematical operations it can be practical to use anonymous
+1223    functions. For the ratio of two observables one can e.g. use
+1224
+1225    new_obs = derived_observable(lambda x: x[0] / x[1], [obs1, obs2])
+1226    """
+1227
+1228    data = np.asarray(data)
+1229    raveled_data = data.ravel()
 1230
-1231    new_values = func(values, **kwargs)
-1232
-1233    multi = int(isinstance(new_values, np.ndarray))
-1234
-1235    new_r_values = {}
-1236    new_idl_d = {}
-1237    for name in new_sample_names:
-1238        idl = []
-1239        tmp_values = np.zeros(n_obs)
-1240        for i, item in enumerate(raveled_data):
-1241            tmp_values[i] = item.r_values.get(name, item.value)
-1242            tmp_idl = item.idl.get(name)
-1243            if tmp_idl is not None:
-1244                idl.append(tmp_idl)
-1245        if multi > 0:
-1246            tmp_values = np.array(tmp_values).reshape(data.shape)
-1247        new_r_values[name] = func(tmp_values, **kwargs)
-1248        new_idl_d[name] = _merge_idx(idl)
-1249
-1250    def _compute_scalefactor_missing_rep(obs):
-1251        """
-1252        Computes the scale factor that is to be multiplied with the deltas
-1253        in the case where Obs with different subsets of replica are merged.
-1254        Returns a dictionary with the scale factor for each Monte Carlo name.
-1255
-1256        Parameters
-1257        ----------
-1258        obs : Obs
-1259            The observable corresponding to the deltas that are to be scaled
-1260        """
-1261        scalef_d = {}
-1262        for mc_name in obs.mc_names:
-1263            mc_idl_d = [name for name in obs.idl if name.startswith(mc_name + '|')]
-1264            new_mc_idl_d = [name for name in new_idl_d if name.startswith(mc_name + '|')]
-1265            if len(mc_idl_d) > 0 and len(mc_idl_d) < len(new_mc_idl_d):
-1266                scalef_d[mc_name] = sum([len(new_idl_d[name]) for name in new_mc_idl_d]) / sum([len(new_idl_d[name]) for name in mc_idl_d])
-1267        return scalef_d
-1268
-1269    if 'man_grad' in kwargs:
-1270        deriv = np.asarray(kwargs.get('man_grad'))
-1271        if new_values.shape + data.shape != deriv.shape:
-1272            raise ValueError('Manual derivative does not have correct shape.')
-1273    elif kwargs.get('num_grad') is True:
-1274        if multi > 0:
-1275            raise Exception('Multi mode currently not supported for numerical derivative')
-1276        options = {
-1277            'base_step': 0.1,
-1278            'step_ratio': 2.5}
-1279        for key in options.keys():
-1280            kwarg = kwargs.get(key)
-1281            if kwarg is not None:
-1282                options[key] = kwarg
-1283        tmp_df = nd.Gradient(func, order=4, **{k: v for k, v in options.items() if v is not None})(values, **kwargs)
-1284        if tmp_df.size == 1:
-1285            deriv = np.array([tmp_df.real])
-1286        else:
-1287            deriv = tmp_df.real
-1288    else:
-1289        deriv = jacobian(func)(values, **kwargs)
-1290
-1291    final_result = np.zeros(new_values.shape, dtype=object)
-1292
-1293    if array_mode is True:
-1294
-1295        class _Zero_grad():
-1296            def __init__(self, N):
-1297                self.grad = np.zeros((N, 1))
-1298
-1299        new_covobs_lengths = dict(set([y for x in [[(n, o.covobs[n].N) for n in o.cov_names] for o in raveled_data] for y in x]))
-1300        d_extracted = {}
-1301        g_extracted = {}
-1302        for name in new_sample_names:
-1303            d_extracted[name] = []
-1304            ens_length = len(new_idl_d[name])
-1305            for i_dat, dat in enumerate(data):
-1306                d_extracted[name].append(np.array([_expand_deltas_for_merge(o.deltas.get(name, np.zeros(ens_length)), o.idl.get(name, new_idl_d[name]), o.shape.get(name, ens_length), new_idl_d[name], _compute_scalefactor_missing_rep(o).get(name.split('|')[0], 1)) for o in dat.reshape(np.prod(dat.shape))]).reshape(dat.shape + (ens_length, )))
-1307        for name in new_cov_names:
-1308            g_extracted[name] = []
-1309            zero_grad = _Zero_grad(new_covobs_lengths[name])
-1310            for i_dat, dat in enumerate(data):
-1311                g_extracted[name].append(np.array([o.covobs.get(name, zero_grad).grad for o in dat.reshape(np.prod(dat.shape))]).reshape(dat.shape + (new_covobs_lengths[name], 1)))
-1312
-1313    for i_val, new_val in np.ndenumerate(new_values):
-1314        new_deltas = {}
-1315        new_grad = {}
-1316        if array_mode is True:
-1317            for name in new_sample_names:
-1318                ens_length = d_extracted[name][0].shape[-1]
-1319                new_deltas[name] = np.zeros(ens_length)
-1320                for i_dat, dat in enumerate(d_extracted[name]):
-1321                    new_deltas[name] += np.tensordot(deriv[i_val + (i_dat, )], dat)
-1322            for name in new_cov_names:
-1323                new_grad[name] = 0
-1324                for i_dat, dat in enumerate(g_extracted[name]):
-1325                    new_grad[name] += np.tensordot(deriv[i_val + (i_dat, )], dat)
-1326        else:
-1327            for j_obs, obs in np.ndenumerate(data):
-1328                scalef_d = _compute_scalefactor_missing_rep(obs)
-1329                for name in obs.names:
-1330                    if name in obs.cov_names:
-1331                        new_grad[name] = new_grad.get(name, 0) + deriv[i_val + j_obs] * obs.covobs[name].grad
-1332                    else:
-1333                        new_deltas[name] = new_deltas.get(name, 0) + deriv[i_val + j_obs] * _expand_deltas_for_merge(obs.deltas[name], obs.idl[name], obs.shape[name], new_idl_d[name], scalef_d.get(name.split('|')[0], 1))
-1334
-1335        new_covobs = {name: Covobs(0, allcov[name], name, grad=new_grad[name]) for name in new_grad}
-1336
-1337        if not set(new_covobs.keys()).isdisjoint(new_deltas.keys()):
-1338            raise ValueError('The same name has been used for deltas and covobs!')
-1339        new_samples = []
-1340        new_means = []
-1341        new_idl = []
-1342        new_names_obs = []
-1343        for name in new_names:
-1344            if name not in new_covobs:
-1345                new_samples.append(new_deltas[name])
-1346                new_idl.append(new_idl_d[name])
-1347                new_means.append(new_r_values[name][i_val])
-1348                new_names_obs.append(name)
-1349        final_result[i_val] = Obs(new_samples, new_names_obs, means=new_means, idl=new_idl)
-1350        for name in new_covobs:
-1351            final_result[i_val].names.append(name)
-1352        final_result[i_val]._covobs = new_covobs
-1353        final_result[i_val]._value = new_val
-1354        final_result[i_val].reweighted = reweighted
-1355
-1356    if multi == 0:
-1357        final_result = final_result.item()
-1358
-1359    return final_result
-1360
+1231    # Workaround for matrix operations containing non Obs data
+1232    if not all(isinstance(x, Obs) for x in raveled_data):
+1233        for i in range(len(raveled_data)):
+1234            if isinstance(raveled_data[i], (int, float)):
+1235                raveled_data[i] = cov_Obs(raveled_data[i], 0.0, "###dummy_covobs###")
+1236
+1237    allcov = {}
+1238    for o in raveled_data:
+1239        for name in o.cov_names:
+1240            if name in allcov:
+1241                if not np.allclose(allcov[name], o.covobs[name].cov):
+1242                    raise Exception(f'Inconsistent covariance matrices for {name}!')
+1243            else:
+1244                allcov[name] = o.covobs[name].cov
+1245
+1246    n_obs = len(raveled_data)
+1247    new_names = sorted(set([y for x in [o.names for o in raveled_data] for y in x]))
+1248    new_cov_names = sorted(set([y for x in [o.cov_names for o in raveled_data] for y in x]))
+1249    new_sample_names = sorted(set(new_names) - set(new_cov_names))
+1250
+1251    reweighted = len(list(filter(lambda o: o.reweighted is True, raveled_data))) > 0
+1252
+1253    if data.ndim == 1:
+1254        values = np.array([o.value for o in data])
+1255    else:
+1256        values = np.vectorize(lambda x: x.value)(data)
+1257
+1258    new_values = func(values, **kwargs)
+1259
+1260    multi = int(isinstance(new_values, np.ndarray))
+1261
+1262    new_r_values = {}
+1263    new_idl_d = {}
+1264    for name in new_sample_names:
+1265        idl = []
+1266        tmp_values = np.zeros(n_obs)
+1267        for i, item in enumerate(raveled_data):
+1268            tmp_values[i] = item.r_values.get(name, item.value)
+1269            tmp_idl = item.idl.get(name)
+1270            if tmp_idl is not None:
+1271                idl.append(tmp_idl)
+1272        if multi > 0:
+1273            tmp_values = np.array(tmp_values).reshape(data.shape)
+1274        new_r_values[name] = func(tmp_values, **kwargs)
+1275        new_idl_d[name] = _merge_idx(idl)
+1276
+1277    def _compute_scalefactor_missing_rep(obs):
+1278        """
+1279        Computes the scale factor that is to be multiplied with the deltas
+1280        in the case where Obs with different subsets of replica are merged.
+1281        Returns a dictionary with the scale factor for each Monte Carlo name.
+1282
+1283        Parameters
+1284        ----------
+1285        obs : Obs
+1286            The observable corresponding to the deltas that are to be scaled
+1287        """
+1288        scalef_d = {}
+1289        for mc_name in obs.mc_names:
+1290            mc_idl_d = [name for name in obs.idl if name.startswith(mc_name + '|')]
+1291            new_mc_idl_d = [name for name in new_idl_d if name.startswith(mc_name + '|')]
+1292            if len(mc_idl_d) > 0 and len(mc_idl_d) < len(new_mc_idl_d):
+1293                scalef_d[mc_name] = sum([len(new_idl_d[name]) for name in new_mc_idl_d]) / sum([len(new_idl_d[name]) for name in mc_idl_d])
+1294        return scalef_d
+1295
+1296    if 'man_grad' in kwargs:
+1297        deriv = np.asarray(kwargs.get('man_grad'))
+1298        if new_values.shape + data.shape != deriv.shape:
+1299            raise ValueError('Manual derivative does not have correct shape.')
+1300    elif kwargs.get('num_grad') is True:
+1301        if multi > 0:
+1302            raise Exception('Multi mode currently not supported for numerical derivative')
+1303        options = {
+1304            'base_step': 0.1,
+1305            'step_ratio': 2.5}
+1306        for key in options.keys():
+1307            kwarg = kwargs.get(key)
+1308            if kwarg is not None:
+1309                options[key] = kwarg
+1310        tmp_df = nd.Gradient(func, order=4, **{k: v for k, v in options.items() if v is not None})(values, **kwargs)
+1311        if tmp_df.size == 1:
+1312            deriv = np.array([tmp_df.real])
+1313        else:
+1314            deriv = tmp_df.real
+1315    else:
+1316        deriv = jacobian(func)(values, **kwargs)
+1317
+1318    final_result = np.zeros(new_values.shape, dtype=object)
+1319
+1320    if array_mode is True:
+1321
+1322        class _Zero_grad:
+1323            def __init__(self, N):
+1324                self.grad = np.zeros((N, 1))
+1325
+1326        new_covobs_lengths = dict(set([y for x in [[(n, o.covobs[n].N) for n in o.cov_names] for o in raveled_data] for y in x]))
+1327        d_extracted = {}
+1328        g_extracted = {}
+1329        for name in new_sample_names:
+1330            d_extracted[name] = []
+1331            ens_length = len(new_idl_d[name])
+1332            for dat in data:
+1333                d_extracted[name].append(np.array([_expand_deltas_for_merge(o.deltas.get(name, np.zeros(ens_length)), o.idl.get(name, new_idl_d[name]), o.shape.get(name, ens_length), new_idl_d[name], _compute_scalefactor_missing_rep(o).get(name.split('|')[0], 1)) for o in dat.reshape(np.prod(dat.shape))]).reshape((*dat.shape, ens_length)))
+1334        for name in new_cov_names:
+1335            g_extracted[name] = []
+1336            zero_grad = _Zero_grad(new_covobs_lengths[name])
+1337            for dat in data:
+1338                g_extracted[name].append(np.array([o.covobs.get(name, zero_grad).grad for o in dat.reshape(np.prod(dat.shape))]).reshape((*dat.shape, new_covobs_lengths[name], 1)))
+1339
+1340    for i_val, new_val in np.ndenumerate(new_values):
+1341        new_deltas = {}
+1342        new_grad = {}
+1343        if array_mode is True:
+1344            for name in new_sample_names:
+1345                ens_length = d_extracted[name][0].shape[-1]
+1346                new_deltas[name] = np.zeros(ens_length)
+1347                for i_dat, dat in enumerate(d_extracted[name]):
+1348                    new_deltas[name] += np.tensordot(deriv[(*i_val, i_dat)], dat)
+1349            for name in new_cov_names:
+1350                new_grad[name] = 0
+1351                for i_dat, dat in enumerate(g_extracted[name]):
+1352                    new_grad[name] += np.tensordot(deriv[(*i_val, i_dat)], dat)
+1353        else:
+1354            for j_obs, obs in np.ndenumerate(data):
+1355                scalef_d = _compute_scalefactor_missing_rep(obs)
+1356                for name in obs.names:
+1357                    if name in obs.cov_names:
+1358                        new_grad[name] = new_grad.get(name, 0) + deriv[i_val + j_obs] * obs.covobs[name].grad
+1359                    else:
+1360                        new_deltas[name] = new_deltas.get(name, 0) + deriv[i_val + j_obs] * _expand_deltas_for_merge(obs.deltas[name], obs.idl[name], obs.shape[name], new_idl_d[name], scalef_d.get(name.split('|')[0], 1))
 1361
-1362def _reduce_deltas(deltas, idx_old, idx_new):
-1363    """Extract deltas defined on idx_old on all configs of idx_new.
-1364
-1365    Assumes, that idx_old and idx_new are correctly defined idl, i.e., they
-1366    are ordered in an ascending order.
-1367
-1368    Parameters
-1369    ----------
-1370    deltas : list
-1371        List of fluctuations
-1372    idx_old : list
-1373        List or range of configs on which the deltas are defined
-1374    idx_new : list
-1375        List of configs for which we want to extract the deltas.
-1376        Has to be a subset of idx_old.
-1377    """
-1378    if not len(deltas) == len(idx_old):
-1379        raise ValueError('Length of deltas and idx_old have to be the same: %d != %d' % (len(deltas), len(idx_old)))
-1380    if type(idx_old) is range and type(idx_new) is range:
-1381        if idx_old == idx_new:
-1382            return deltas
-1383    if _check_lists_equal([idx_old, idx_new]):
-1384        return deltas
-1385    indices = np.intersect1d(idx_old, idx_new, assume_unique=True, return_indices=True)[1]
-1386    if len(indices) < len(idx_new):
-1387        raise ValueError('Error in _reduce_deltas: Config of idx_new not in idx_old')
-1388    return np.array(deltas)[indices]
-1389
-1390
-1391def reweight(weight, obs, **kwargs):
-1392    """Reweight a list of observables.
-1393
-1394    Parameters
-1395    ----------
-1396    weight : Obs
-1397        Reweighting factor. An Observable that has to be defined on a superset of the
-1398        configurations in obs[i].idl for all i.
-1399    obs : list
-1400        list of Obs, e.g. [obs1, obs2, obs3].
-1401    all_configs : bool
-1402        if True, the reweighted observables are normalized by the average of
-1403        the reweighting factor on all configurations in weight.idl and not
-1404        on the configurations in obs[i].idl. Default False.
-1405    """
-1406    result = []
-1407    for i in range(len(obs)):
-1408        if len(obs[i].cov_names):
-1409            raise ValueError('Error: Not possible to reweight an Obs that contains covobs!')
-1410        if not set(obs[i].names).issubset(weight.names):
-1411            raise ValueError('Error: Ensembles do not fit')
-1412        if len(obs[i].mc_names) > 1 or len(weight.mc_names) > 1:
-1413            raise ValueError('Error: Cannot reweight an Obs that contains multiple ensembles.')
-1414        for name in obs[i].names:
-1415            if not set(obs[i].idl[name]).issubset(weight.idl[name]):
-1416                raise ValueError('obs[%d] has to be defined on a subset of the configs in weight.idl[%s]!' % (i, name))
-1417        new_samples = []
-1418        w_deltas = {}
-1419        for name in sorted(obs[i].names):
-1420            w_deltas[name] = _reduce_deltas(weight.deltas[name], weight.idl[name], obs[i].idl[name])
-1421            new_samples.append((w_deltas[name] + weight.r_values[name]) * (obs[i].deltas[name] + obs[i].r_values[name]))
-1422        tmp_obs = Obs(new_samples, sorted(obs[i].names), idl=[obs[i].idl[name] for name in sorted(obs[i].names)])
-1423
-1424        if kwargs.get('all_configs'):
-1425            new_weight = weight
-1426        else:
-1427            new_weight = Obs([w_deltas[name] + weight.r_values[name] for name in sorted(obs[i].names)], sorted(obs[i].names), idl=[obs[i].idl[name] for name in sorted(obs[i].names)])
-1428
-1429        result.append(tmp_obs / new_weight)
-1430        result[-1].reweighted = True
-1431
-1432    return result
-1433
-1434
-1435def correlate(obs_a, obs_b):
-1436    """Correlate two observables.
-1437
-1438    Parameters
-1439    ----------
-1440    obs_a : Obs
-1441        First observable
-1442    obs_b : Obs
-1443        Second observable
-1444
-1445    Notes
-1446    -----
-1447    Keep in mind to only correlate primary observables which have not been reweighted
-1448    yet. The reweighting has to be applied after correlating the observables.
-1449    Only works if a single ensemble is present in the Obs.
-1450    Currently only works if ensemble content is identical (this is not strictly necessary).
-1451    """
-1452
-1453    if len(obs_a.mc_names) > 1 or len(obs_b.mc_names) > 1:
-1454        raise ValueError('Error: Cannot correlate Obs that contain multiple ensembles.')
-1455    if sorted(obs_a.names) != sorted(obs_b.names):
-1456        raise ValueError(f"Ensembles do not fit {set(sorted(obs_a.names)) ^ set(sorted(obs_b.names))}")
-1457    if len(obs_a.cov_names) or len(obs_b.cov_names):
-1458        raise ValueError('Error: Not possible to correlate Obs that contain covobs!')
-1459    for name in obs_a.names:
-1460        if obs_a.shape[name] != obs_b.shape[name]:
-1461            raise ValueError('Shapes of ensemble', name, 'do not fit')
-1462        if obs_a.idl[name] != obs_b.idl[name]:
-1463            raise ValueError('idl of ensemble', name, 'do not fit')
+1362        new_covobs = {name: Covobs(0, allcov[name], name, grad=new_grad[name]) for name in new_grad}
+1363
+1364        if not set(new_covobs.keys()).isdisjoint(new_deltas.keys()):
+1365            raise ValueError('The same name has been used for deltas and covobs!')
+1366        new_samples = []
+1367        new_means = []
+1368        new_idl = []
+1369        new_names_obs = []
+1370        for name in new_names:
+1371            if name not in new_covobs:
+1372                new_samples.append(new_deltas[name])
+1373                new_idl.append(new_idl_d[name])
+1374                new_means.append(new_r_values[name][i_val])
+1375                new_names_obs.append(name)
+1376        final_result[i_val] = Obs(new_samples, new_names_obs, means=new_means, idl=new_idl)
+1377        for name in new_covobs:
+1378            final_result[i_val].names.append(name)
+1379        final_result[i_val]._covobs = new_covobs
+1380        final_result[i_val]._value = new_val
+1381        final_result[i_val].reweighted = reweighted
+1382
+1383    if multi == 0:
+1384        final_result = final_result.item()
+1385
+1386    return final_result
+1387
+1388
+1389def _reduce_deltas(deltas, idx_old, idx_new):
+1390    """Extract deltas defined on idx_old on all configs of idx_new.
+1391
+1392    Assumes, that idx_old and idx_new are correctly defined idl, i.e., they
+1393    are ordered in an ascending order.
+1394
+1395    Parameters
+1396    ----------
+1397    deltas : list
+1398        List of fluctuations
+1399    idx_old : list
+1400        List or range of configs on which the deltas are defined
+1401    idx_new : list
+1402        List of configs for which we want to extract the deltas.
+1403        Has to be a subset of idx_old.
+1404    """
+1405    if not len(deltas) == len(idx_old):
+1406        raise ValueError(f'Length of deltas and idx_old have to be the same: {len(deltas)} != {len(idx_old)}')
+1407    if type(idx_old) is range and type(idx_new) is range:
+1408        if idx_old == idx_new:
+1409            return deltas
+1410    if _check_lists_equal([idx_old, idx_new]):
+1411        return deltas
+1412    indices = np.intersect1d(idx_old, idx_new, assume_unique=True, return_indices=True)[1]
+1413    if len(indices) < len(idx_new):
+1414        raise ValueError('Error in _reduce_deltas: Config of idx_new not in idx_old')
+1415    return np.array(deltas)[indices]
+1416
+1417
+1418def reweight(weight, obs, **kwargs):
+1419    """Reweight a list of observables.
+1420
+1421    Parameters
+1422    ----------
+1423    weight : Obs
+1424        Reweighting factor. An Observable that has to be defined on a superset of the
+1425        configurations in obs[i].idl for all i.
+1426    obs : list
+1427        list of Obs, e.g. [obs1, obs2, obs3].
+1428    all_configs : bool
+1429        if True, the reweighted observables are normalized by the average of
+1430        the reweighting factor on all configurations in weight.idl and not
+1431        on the configurations in obs[i].idl. Default False.
+1432    """
+1433    result = []
+1434    for i in range(len(obs)):
+1435        if len(obs[i].cov_names):
+1436            raise ValueError('Error: Not possible to reweight an Obs that contains covobs!')
+1437        if not set(obs[i].names).issubset(weight.names):
+1438            raise ValueError('Error: Ensembles do not fit')
+1439        if len(obs[i].mc_names) > 1 or len(weight.mc_names) > 1:
+1440            raise ValueError('Error: Cannot reweight an Obs that contains multiple ensembles.')
+1441        for name in obs[i].names:
+1442            if not set(obs[i].idl[name]).issubset(weight.idl[name]):
+1443                raise ValueError(f'obs[{i}] has to be defined on a subset of the configs in weight.idl[{name}]!')
+1444        new_samples = []
+1445        w_deltas = {}
+1446        for name in sorted(obs[i].names):
+1447            w_deltas[name] = _reduce_deltas(weight.deltas[name], weight.idl[name], obs[i].idl[name])
+1448            new_samples.append((w_deltas[name] + weight.r_values[name]) * (obs[i].deltas[name] + obs[i].r_values[name]))
+1449        tmp_obs = Obs(new_samples, sorted(obs[i].names), idl=[obs[i].idl[name] for name in sorted(obs[i].names)])
+1450
+1451        if kwargs.get('all_configs'):
+1452            new_weight = weight
+1453        else:
+1454            new_weight = Obs([w_deltas[name] + weight.r_values[name] for name in sorted(obs[i].names)], sorted(obs[i].names), idl=[obs[i].idl[name] for name in sorted(obs[i].names)])
+1455
+1456        result.append(tmp_obs / new_weight)
+1457        result[-1].reweighted = True
+1458
+1459    return result
+1460
+1461
+1462def correlate(obs_a, obs_b):
+1463    """Correlate two observables.
 1464
-1465    if obs_a.reweighted is True:
-1466        warnings.warn("The first observable is already reweighted.", RuntimeWarning)
-1467    if obs_b.reweighted is True:
-1468        warnings.warn("The second observable is already reweighted.", RuntimeWarning)
-1469
-1470    new_samples = []
-1471    new_idl = []
-1472    for name in sorted(obs_a.names):
-1473        new_samples.append((obs_a.deltas[name] + obs_a.r_values[name]) * (obs_b.deltas[name] + obs_b.r_values[name]))
-1474        new_idl.append(obs_a.idl[name])
-1475
-1476    o = Obs(new_samples, sorted(obs_a.names), idl=new_idl)
-1477    o.reweighted = obs_a.reweighted or obs_b.reweighted
-1478    return o
+1465    Parameters
+1466    ----------
+1467    obs_a : Obs
+1468        First observable
+1469    obs_b : Obs
+1470        Second observable
+1471
+1472    Notes
+1473    -----
+1474    Keep in mind to only correlate primary observables which have not been reweighted
+1475    yet. The reweighting has to be applied after correlating the observables.
+1476    Only works if a single ensemble is present in the Obs.
+1477    Currently only works if ensemble content is identical (this is not strictly necessary).
+1478    """
 1479
-1480
-1481def covariance(obs, visualize=False, correlation=False, smooth=None, **kwargs):
-1482    r'''Calculates the error covariance matrix of a set of observables.
-1483
-1484    WARNING: This function should be used with care, especially for observables with support on multiple
-1485             ensembles with differing autocorrelations. See the notes below for details.
-1486
-1487    The gamma method has to be applied first to all observables.
-1488
-1489    Parameters
-1490    ----------
-1491    obs : list or numpy.ndarray
-1492        List or one dimensional array of Obs
-1493    visualize : bool
-1494        If True plots the corresponding normalized correlation matrix (default False).
-1495    correlation : bool
-1496        If True the correlation matrix instead of the error covariance matrix is returned (default False).
-1497    smooth : None or int
-1498        If smooth is an integer 'E' between 2 and the dimension of the matrix minus 1 the eigenvalue
-1499        smoothing procedure of hep-lat/9412087 is applied to the correlation matrix which leaves the
-1500        largest E eigenvalues essentially unchanged and smoothes the smaller eigenvalues to avoid extremely
-1501        small ones.
+1480    if len(obs_a.mc_names) > 1 or len(obs_b.mc_names) > 1:
+1481        raise ValueError('Error: Cannot correlate Obs that contain multiple ensembles.')
+1482    if sorted(obs_a.names) != sorted(obs_b.names):
+1483        raise ValueError(f"Ensembles do not fit {set(sorted(obs_a.names)) ^ set(sorted(obs_b.names))}")
+1484    if len(obs_a.cov_names) or len(obs_b.cov_names):
+1485        raise ValueError('Error: Not possible to correlate Obs that contain covobs!')
+1486    for name in obs_a.names:
+1487        if obs_a.shape[name] != obs_b.shape[name]:
+1488            raise ValueError('Shapes of ensemble', name, 'do not fit')
+1489        if obs_a.idl[name] != obs_b.idl[name]:
+1490            raise ValueError('idl of ensemble', name, 'do not fit')
+1491
+1492    if obs_a.reweighted is True:
+1493        warnings.warn("The first observable is already reweighted.", RuntimeWarning, stacklevel=2)
+1494    if obs_b.reweighted is True:
+1495        warnings.warn("The second observable is already reweighted.", RuntimeWarning, stacklevel=2)
+1496
+1497    new_samples = []
+1498    new_idl = []
+1499    for name in sorted(obs_a.names):
+1500        new_samples.append((obs_a.deltas[name] + obs_a.r_values[name]) * (obs_b.deltas[name] + obs_b.r_values[name]))
+1501        new_idl.append(obs_a.idl[name])
 1502
-1503    Notes
-1504    -----
-1505    The error covariance is defined such that it agrees with the squared standard error for two identical observables
-1506    $$\operatorname{cov}(a,a)=\sum_{s=1}^N\delta_a^s\delta_a^s/N^2=\Gamma_{aa}(0)/N=\operatorname{var}(a)/N=\sigma_a^2$$
-1507    in the absence of autocorrelation.
-1508    The error covariance is estimated by calculating the correlation matrix assuming no autocorrelation and then rescaling the correlation matrix by the full errors including the previous gamma method estimate for the autocorrelation of the observables. The covariance at windowsize 0 is guaranteed to be positive semi-definite
-1509    $$\sum_{i,j}v_i\Gamma_{ij}(0)v_j=\frac{1}{N}\sum_{s=1}^N\sum_{i,j}v_i\delta_i^s\delta_j^s v_j=\frac{1}{N}\sum_{s=1}^N\sum_{i}|v_i\delta_i^s|^2\geq 0\,,$$ for every $v\in\mathbb{R}^M$, while such an identity does not hold for larger windows/lags.
-1510    For observables defined on a single ensemble our approximation is equivalent to assuming that the integrated autocorrelation time of an off-diagonal element is equal to the geometric mean of the integrated autocorrelation times of the corresponding diagonal elements.
-1511    $$\tau_{\mathrm{int}, ij}=\sqrt{\tau_{\mathrm{int}, i}\times \tau_{\mathrm{int}, j}}$$
-1512    This construction ensures that the estimated covariance matrix is positive semi-definite (up to numerical rounding errors).
-1513    '''
-1514
-1515    length = len(obs)
-1516
-1517    max_samples = np.max([o.N for o in obs])
-1518    if max_samples <= length and not [item for sublist in [o.cov_names for o in obs] for item in sublist]:
-1519        warnings.warn(f"The dimension of the covariance matrix ({length}) is larger or equal to the number of samples ({max_samples}). This will result in a rank deficient matrix.", RuntimeWarning)
-1520
-1521    cov = np.zeros((length, length))
-1522    for i in range(length):
-1523        for j in range(i, length):
-1524            cov[i, j] = _covariance_element(obs[i], obs[j])
-1525    cov = cov + cov.T - np.diag(np.diag(cov))
-1526
-1527    corr = np.diag(1 / np.sqrt(np.diag(cov))) @ cov @ np.diag(1 / np.sqrt(np.diag(cov)))
-1528
-1529    if isinstance(smooth, int):
-1530        corr = _smooth_eigenvalues(corr, smooth)
-1531
-1532    if visualize:
-1533        plt.matshow(corr, vmin=-1, vmax=1)
-1534        plt.set_cmap('RdBu')
-1535        plt.colorbar()
-1536        plt.draw()
-1537
-1538    if correlation is True:
-1539        return corr
-1540
-1541    errors = [o.dvalue for o in obs]
-1542    cov = np.diag(errors) @ corr @ np.diag(errors)
+1503    o = Obs(new_samples, sorted(obs_a.names), idl=new_idl)
+1504    o.reweighted = obs_a.reweighted or obs_b.reweighted
+1505    return o
+1506
+1507
+1508def covariance(obs, visualize=False, correlation=False, smooth=None, **kwargs):
+1509    r'''Calculates the error covariance matrix of a set of observables.
+1510
+1511    WARNING: This function should be used with care, especially for observables with support on multiple
+1512             ensembles with differing autocorrelations. See the notes below for details.
+1513
+1514    The gamma method has to be applied first to all observables.
+1515
+1516    Parameters
+1517    ----------
+1518    obs : list or numpy.ndarray
+1519        List or one dimensional array of Obs
+1520    visualize : bool
+1521        If True plots the corresponding normalized correlation matrix (default False).
+1522    correlation : bool
+1523        If True the correlation matrix instead of the error covariance matrix is returned (default False).
+1524    smooth : None or int
+1525        If smooth is an integer 'E' between 2 and the dimension of the matrix minus 1 the eigenvalue
+1526        smoothing procedure of hep-lat/9412087 is applied to the correlation matrix which leaves the
+1527        largest E eigenvalues essentially unchanged and smoothes the smaller eigenvalues to avoid extremely
+1528        small ones.
+1529
+1530    Notes
+1531    -----
+1532    The error covariance is defined such that it agrees with the squared standard error for two identical observables
+1533    $$\operatorname{cov}(a,a)=\sum_{s=1}^N\delta_a^s\delta_a^s/N^2=\Gamma_{aa}(0)/N=\operatorname{var}(a)/N=\sigma_a^2$$
+1534    in the absence of autocorrelation.
+1535    The error covariance is estimated by calculating the correlation matrix assuming no autocorrelation and then rescaling the correlation matrix by the full errors including the previous gamma method estimate for the autocorrelation of the observables. The covariance at windowsize 0 is guaranteed to be positive semi-definite
+1536    $$\sum_{i,j}v_i\Gamma_{ij}(0)v_j=\frac{1}{N}\sum_{s=1}^N\sum_{i,j}v_i\delta_i^s\delta_j^s v_j=\frac{1}{N}\sum_{s=1}^N\sum_{i}|v_i\delta_i^s|^2\geq 0\,,$$ for every $v\in\mathbb{R}^M$, while such an identity does not hold for larger windows/lags.
+1537    For observables defined on a single ensemble our approximation is equivalent to assuming that the integrated autocorrelation time of an off-diagonal element is equal to the geometric mean of the integrated autocorrelation times of the corresponding diagonal elements.
+1538    $$\tau_{\mathrm{int}, ij}=\sqrt{\tau_{\mathrm{int}, i}\times \tau_{\mathrm{int}, j}}$$
+1539    This construction ensures that the estimated covariance matrix is positive semi-definite (up to numerical rounding errors).
+1540    '''
+1541
+1542    length = len(obs)
 1543
-1544    eigenvalues = np.linalg.eigh(cov)[0]
-1545    if not np.all(eigenvalues >= 0):
-1546        warnings.warn("Covariance matrix is not positive semi-definite (Eigenvalues: " + str(eigenvalues) + ")", RuntimeWarning)
+1544    max_samples = np.max([o.N for o in obs])
+1545    if max_samples <= length and not [item for sublist in [o.cov_names for o in obs] for item in sublist]:
+1546        warnings.warn(f"The dimension of the covariance matrix ({length}) is larger or equal to the number of samples ({max_samples}). This will result in a rank deficient matrix.", RuntimeWarning, stacklevel=2)
 1547
-1548    return cov
-1549
-1550
-1551def invert_corr_cov_cholesky(corr, inverrdiag):
-1552    """Constructs a lower triangular matrix `chol` via the Cholesky decomposition of the correlation matrix `corr`
-1553       and then returns the inverse covariance matrix `chol_inv` as a lower triangular matrix by solving `chol * x = inverrdiag`.
-1554
-1555    Parameters
-1556    ----------
-1557    corr : np.ndarray
-1558           correlation matrix
-1559    inverrdiag : np.ndarray
-1560              diagonal matrix, the entries are the inverse errors of the data points considered
-1561    """
-1562
-1563    condn = np.linalg.cond(corr)
-1564    if condn > 0.1 / np.finfo(float).eps:
-1565        raise ValueError(f"Cannot invert correlation matrix as its condition number exceeds machine precision ({condn:1.2e})")
-1566    if condn > 1e13:
-1567        warnings.warn("Correlation matrix may be ill-conditioned, condition number: {%1.2e}" % (condn), RuntimeWarning)
-1568    chol = np.linalg.cholesky(corr)
-1569    chol_inv = scipy.linalg.solve_triangular(chol, inverrdiag, lower=True)
+1548    cov = np.zeros((length, length))
+1549    for i in range(length):
+1550        for j in range(i, length):
+1551            cov[i, j] = _covariance_element(obs[i], obs[j])
+1552    cov = cov + cov.T - np.diag(np.diag(cov))
+1553
+1554    corr = np.diag(1 / np.sqrt(np.diag(cov))) @ cov @ np.diag(1 / np.sqrt(np.diag(cov)))
+1555
+1556    if isinstance(smooth, int):
+1557        corr = _smooth_eigenvalues(corr, smooth)
+1558
+1559    if visualize:
+1560        plt.matshow(corr, vmin=-1, vmax=1)
+1561        plt.set_cmap('RdBu')
+1562        plt.colorbar()
+1563        plt.draw()
+1564
+1565    if correlation is True:
+1566        return corr
+1567
+1568    errors = [o.dvalue for o in obs]
+1569    cov = np.diag(errors) @ corr @ np.diag(errors)
 1570
-1571    return chol_inv
-1572
-1573
-1574def sort_corr(corr, kl, yd):
-1575    """ Reorders a correlation matrix to match the alphabetical order of its underlying y data.
+1571    eigenvalues = np.linalg.eigh(cov)[0]
+1572    if not np.all(eigenvalues >= 0):
+1573        warnings.warn("Covariance matrix is not positive semi-definite (Eigenvalues: " + str(eigenvalues) + ")", RuntimeWarning, stacklevel=2)
+1574
+1575    return cov
 1576
-1577    The ordering of the input correlation matrix `corr` is given by the list of keys `kl`.
-1578    The input dictionary `yd` (with the same keys `kl`) must contain the corresponding y data
-1579    that the correlation matrix is based on.
-1580    This function sorts the list of keys `kl` alphabetically and sorts the matrix `corr`
-1581    according to this alphabetical order such that the sorted matrix `corr_sorted` corresponds
-1582    to the y data `yd` when arranged in an alphabetical order by its keys.
-1583
-1584    Parameters
-1585    ----------
-1586    corr : np.ndarray
-1587        A square correlation matrix constructed using the order of the y data specified by `kl`.
-1588        The dimensions of `corr` should match the total number of y data points in `yd` combined.
-1589    kl : list of str
-1590        A list of keys that denotes the order in which the y data from `yd` was used to build the
-1591        input correlation matrix `corr`.
-1592    yd : dict of list
-1593        A dictionary where each key corresponds to a unique identifier, and its value is a list of
-1594        y data points. The total number of y data points across all keys must match the dimensions
-1595        of `corr`. The lists in the dictionary can be lists of Obs.
-1596
-1597    Returns
-1598    -------
-1599    np.ndarray
-1600        A new, sorted correlation matrix that corresponds to the y data from `yd` when arranged alphabetically by its keys.
-1601
-1602    Example
-1603    -------
-1604    >>> import numpy as np
-1605    >>> import pyerrors as pe
-1606    >>> corr = np.array([[1, 0.2, 0.3], [0.2, 1, 0.4], [0.3, 0.4, 1]])
-1607    >>> kl = ['b', 'a']
-1608    >>> yd = {'a': [1, 2], 'b': [3]}
-1609    >>> sorted_corr = pe.obs.sort_corr(corr, kl, yd)
-1610    >>> print(sorted_corr)
-1611    array([[1. , 0.3, 0.4],
-1612           [0.3, 1. , 0.2],
-1613           [0.4, 0.2, 1. ]])
-1614
-1615    """
-1616    kl_sorted = sorted(kl)
-1617
-1618    posd = {}
-1619    ofs = 0
-1620    for ki, k in enumerate(kl):
-1621        posd[k] = [i + ofs for i in range(len(yd[k]))]
-1622        ofs += len(posd[k])
+1577
+1578def invert_corr_cov_cholesky(corr, inverrdiag):
+1579    """Constructs a lower triangular matrix `chol` via the Cholesky decomposition of the correlation matrix `corr`
+1580       and then returns the inverse covariance matrix `chol_inv` as a lower triangular matrix by solving `chol * x = inverrdiag`.
+1581
+1582    Parameters
+1583    ----------
+1584    corr : np.ndarray
+1585           correlation matrix
+1586    inverrdiag : np.ndarray
+1587              diagonal matrix, the entries are the inverse errors of the data points considered
+1588    """
+1589
+1590    condn = np.linalg.cond(corr)
+1591    if condn > 0.1 / np.finfo(float).eps:
+1592        raise ValueError(f"Cannot invert correlation matrix as its condition number exceeds machine precision ({condn:1.2e})")
+1593    if condn > 1e13:
+1594        warnings.warn(f"Correlation matrix may be ill-conditioned, condition number: {{{condn:1.2e}}}", RuntimeWarning, stacklevel=2)
+1595    chol = np.linalg.cholesky(corr)
+1596    chol_inv = scipy.linalg.solve_triangular(chol, inverrdiag, lower=True)
+1597
+1598    return chol_inv
+1599
+1600
+1601def sort_corr(corr, kl, yd):
+1602    """ Reorders a correlation matrix to match the alphabetical order of its underlying y data.
+1603
+1604    The ordering of the input correlation matrix `corr` is given by the list of keys `kl`.
+1605    The input dictionary `yd` (with the same keys `kl`) must contain the corresponding y data
+1606    that the correlation matrix is based on.
+1607    This function sorts the list of keys `kl` alphabetically and sorts the matrix `corr`
+1608    according to this alphabetical order such that the sorted matrix `corr_sorted` corresponds
+1609    to the y data `yd` when arranged in an alphabetical order by its keys.
+1610
+1611    Parameters
+1612    ----------
+1613    corr : np.ndarray
+1614        A square correlation matrix constructed using the order of the y data specified by `kl`.
+1615        The dimensions of `corr` should match the total number of y data points in `yd` combined.
+1616    kl : list of str
+1617        A list of keys that denotes the order in which the y data from `yd` was used to build the
+1618        input correlation matrix `corr`.
+1619    yd : dict of list
+1620        A dictionary where each key corresponds to a unique identifier, and its value is a list of
+1621        y data points. The total number of y data points across all keys must match the dimensions
+1622        of `corr`. The lists in the dictionary can be lists of Obs.
 1623
-1624    mapping = []
-1625    for k in kl_sorted:
-1626        for i in range(len(yd[k])):
-1627            mapping.append(posd[k][i])
+1624    Returns
+1625    -------
+1626    np.ndarray
+1627        A new, sorted correlation matrix that corresponds to the y data from `yd` when arranged alphabetically by its keys.
 1628
-1629    corr_sorted = np.zeros_like(corr)
-1630    for i in range(corr.shape[0]):
-1631        for j in range(corr.shape[0]):
-1632            corr_sorted[i][j] = corr[mapping[i]][mapping[j]]
-1633
-1634    return corr_sorted
-1635
-1636
-1637def _smooth_eigenvalues(corr, E):
-1638    """Eigenvalue smoothing as described in hep-lat/9412087
-1639
-1640    corr : np.ndarray
-1641        correlation matrix
-1642    E : integer
-1643        Number of eigenvalues to be left substantially unchanged
-1644    """
-1645    if not (2 < E < corr.shape[0] - 1):
-1646        raise ValueError(f"'E' has to be between 2 and the dimension of the correlation matrix minus 1 ({corr.shape[0] - 1}).")
-1647    vals, vec = np.linalg.eigh(corr)
-1648    lambda_min = np.mean(vals[:-E])
-1649    vals[vals < lambda_min] = lambda_min
-1650    vals /= np.mean(vals)
-1651    return vec @ np.diag(vals) @ vec.T
-1652
-1653
-1654def _covariance_element(obs1, obs2):
-1655    """Estimates the covariance of two Obs objects, neglecting autocorrelations."""
-1656
-1657    def calc_gamma(deltas1, deltas2, idx1, idx2, new_idx):
-1658        deltas1 = _reduce_deltas(deltas1, idx1, new_idx)
-1659        deltas2 = _reduce_deltas(deltas2, idx2, new_idx)
-1660        return np.sum(deltas1 * deltas2)
-1661
-1662    if set(obs1.names).isdisjoint(set(obs2.names)):
-1663        return 0.0
-1664
-1665    if not hasattr(obs1, 'e_dvalue') or not hasattr(obs2, 'e_dvalue'):
-1666        raise Exception('The gamma method has to be applied to both Obs first.')
-1667
-1668    dvalue = 0.0
-1669
-1670    for e_name in obs1.mc_names:
-1671
-1672        if e_name not in obs2.mc_names:
-1673            continue
-1674
-1675        idl_d = {}
-1676        for r_name in obs1.e_content[e_name]:
-1677            if r_name not in obs2.e_content[e_name]:
-1678                continue
-1679            idl_d[r_name] = _intersection_idx([obs1.idl[r_name], obs2.idl[r_name]])
+1629    Example
+1630    -------
+1631    >>> import numpy as np
+1632    >>> import pyerrors as pe
+1633    >>> corr = np.array([[1, 0.2, 0.3], [0.2, 1, 0.4], [0.3, 0.4, 1]])
+1634    >>> kl = ['b', 'a']
+1635    >>> yd = {'a': [1, 2], 'b': [3]}
+1636    >>> sorted_corr = pe.obs.sort_corr(corr, kl, yd)
+1637    >>> print(sorted_corr)
+1638    array([[1. , 0.3, 0.4],
+1639           [0.3, 1. , 0.2],
+1640           [0.4, 0.2, 1. ]])
+1641
+1642    """
+1643    kl_sorted = sorted(kl)
+1644
+1645    posd = {}
+1646    ofs = 0
+1647    for _ki, k in enumerate(kl):
+1648        posd[k] = [i + ofs for i in range(len(yd[k]))]
+1649        ofs += len(posd[k])
+1650
+1651    mapping = []
+1652    for k in kl_sorted:
+1653        for i in range(len(yd[k])):
+1654            mapping.append(posd[k][i])
+1655
+1656    corr_sorted = np.zeros_like(corr)
+1657    for i in range(corr.shape[0]):
+1658        for j in range(corr.shape[0]):
+1659            corr_sorted[i][j] = corr[mapping[i]][mapping[j]]
+1660
+1661    return corr_sorted
+1662
+1663
+1664def _smooth_eigenvalues(corr, E):
+1665    """Eigenvalue smoothing as described in hep-lat/9412087
+1666
+1667    corr : np.ndarray
+1668        correlation matrix
+1669    E : integer
+1670        Number of eigenvalues to be left substantially unchanged
+1671    """
+1672    if not (2 < E < corr.shape[0] - 1):
+1673        raise ValueError(f"'E' has to be between 2 and the dimension of the correlation matrix minus 1 ({corr.shape[0] - 1}).")
+1674    vals, vec = np.linalg.eigh(corr)
+1675    lambda_min = np.mean(vals[:-E])
+1676    vals[vals < lambda_min] = lambda_min
+1677    vals /= np.mean(vals)
+1678    return vec @ np.diag(vals) @ vec.T
+1679
 1680
-1681        gamma = 0.0
-1682
-1683        for r_name in obs1.e_content[e_name]:
-1684            if r_name not in obs2.e_content[e_name]:
-1685                continue
-1686            if len(idl_d[r_name]) == 0:
-1687                continue
-1688            gamma += calc_gamma(obs1.deltas[r_name], obs2.deltas[r_name], obs1.idl[r_name], obs2.idl[r_name], idl_d[r_name])
-1689
-1690        if gamma == 0.0:
-1691            continue
-1692
-1693        gamma_div = 0.0
-1694        for r_name in obs1.e_content[e_name]:
-1695            if r_name not in obs2.e_content[e_name]:
-1696                continue
-1697            if len(idl_d[r_name]) == 0:
-1698                continue
-1699            gamma_div += np.sqrt(calc_gamma(obs1.deltas[r_name], obs1.deltas[r_name], obs1.idl[r_name], obs1.idl[r_name], idl_d[r_name]) * calc_gamma(obs2.deltas[r_name], obs2.deltas[r_name], obs2.idl[r_name], obs2.idl[r_name], idl_d[r_name]))
-1700        gamma /= gamma_div
+1681def _covariance_element(obs1, obs2):
+1682    """Estimates the covariance of two Obs objects, neglecting autocorrelations."""
+1683
+1684    def calc_gamma(deltas1, deltas2, idx1, idx2, new_idx):
+1685        deltas1 = _reduce_deltas(deltas1, idx1, new_idx)
+1686        deltas2 = _reduce_deltas(deltas2, idx2, new_idx)
+1687        return np.sum(deltas1 * deltas2)
+1688
+1689    if set(obs1.names).isdisjoint(set(obs2.names)):
+1690        return 0.0
+1691
+1692    if not hasattr(obs1, 'e_dvalue') or not hasattr(obs2, 'e_dvalue'):
+1693        raise Exception('The gamma method has to be applied to both Obs first.')
+1694
+1695    dvalue = 0.0
+1696
+1697    for e_name in obs1.mc_names:
+1698
+1699        if e_name not in obs2.mc_names:
+1700            continue
 1701
-1702        dvalue += gamma
-1703
-1704    for e_name in obs1.cov_names:
-1705
-1706        if e_name not in obs2.cov_names:
-1707            continue
-1708
-1709        dvalue += np.dot(np.transpose(obs1.covobs[e_name].grad), np.dot(obs1.covobs[e_name].cov, obs2.covobs[e_name].grad)).item()
-1710
-1711    return dvalue
-1712
-1713
-1714def import_jackknife(jacks, name, idl=None):
-1715    """Imports jackknife samples and returns an Obs
+1702        idl_d = {}
+1703        for r_name in obs1.e_content[e_name]:
+1704            if r_name not in obs2.e_content[e_name]:
+1705                continue
+1706            idl_d[r_name] = _intersection_idx([obs1.idl[r_name], obs2.idl[r_name]])
+1707
+1708        gamma = 0.0
+1709
+1710        for r_name in obs1.e_content[e_name]:
+1711            if r_name not in obs2.e_content[e_name]:
+1712                continue
+1713            if len(idl_d[r_name]) == 0:
+1714                continue
+1715            gamma += calc_gamma(obs1.deltas[r_name], obs2.deltas[r_name], obs1.idl[r_name], obs2.idl[r_name], idl_d[r_name])
 1716
-1717    Parameters
-1718    ----------
-1719    jacks : numpy.ndarray
-1720        numpy array containing the mean value as zeroth entry and
-1721        the N jackknife samples as first to Nth entry.
-1722    name : str
-1723        name of the ensemble the samples are defined on.
-1724    """
-1725    length = len(jacks) - 1
-1726    prj = (np.ones((length, length)) - (length - 1) * np.identity(length))
-1727    samples = jacks[1:] @ prj
-1728    mean = np.mean(samples)
-1729    new_obs = Obs([samples - mean], [name], idl=idl, means=[mean])
-1730    new_obs._value = jacks[0]
-1731    return new_obs
+1717        if gamma == 0.0:
+1718            continue
+1719
+1720        gamma_div = 0.0
+1721        for r_name in obs1.e_content[e_name]:
+1722            if r_name not in obs2.e_content[e_name]:
+1723                continue
+1724            if len(idl_d[r_name]) == 0:
+1725                continue
+1726            gamma_div += np.sqrt(calc_gamma(obs1.deltas[r_name], obs1.deltas[r_name], obs1.idl[r_name], obs1.idl[r_name], idl_d[r_name]) * calc_gamma(obs2.deltas[r_name], obs2.deltas[r_name], obs2.idl[r_name], obs2.idl[r_name], idl_d[r_name]))
+1727        gamma /= gamma_div
+1728
+1729        dvalue += gamma
+1730
+1731    for e_name in obs1.cov_names:
 1732
-1733
-1734def import_bootstrap(boots, name, random_numbers):
-1735    """Imports bootstrap samples and returns an Obs
-1736
-1737    Parameters
-1738    ----------
-1739    boots : numpy.ndarray
-1740        numpy array containing the mean value as zeroth entry and
-1741        the N bootstrap samples as first to Nth entry.
-1742    name : str
-1743        name of the ensemble the samples are defined on.
-1744    random_numbers : np.ndarray
-1745        Array of shape (samples, length) containing the random numbers to generate the bootstrap samples,
-1746        where samples is the number of bootstrap samples and length is the length of the original Monte Carlo
-1747        chain to be reconstructed.
-1748    """
-1749    samples, length = random_numbers.shape
-1750    if samples != len(boots) - 1:
-1751        raise ValueError("Random numbers do not have the correct shape.")
-1752
-1753    if samples < length:
-1754        raise ValueError("Obs can't be reconstructed if there are fewer bootstrap samples than Monte Carlo data points.")
-1755
-1756    proj = np.vstack([np.bincount(o, minlength=length) for o in random_numbers]) / length
-1757
-1758    samples = scipy.linalg.lstsq(proj, boots[1:])[0]
-1759    ret = Obs([samples], [name])
-1760    ret._value = boots[0]
-1761    return ret
-1762
+1733        if e_name not in obs2.cov_names:
+1734            continue
+1735
+1736        dvalue += np.dot(np.transpose(obs1.covobs[e_name].grad), np.dot(obs1.covobs[e_name].cov, obs2.covobs[e_name].grad)).item()
+1737
+1738    return dvalue
+1739
+1740
+1741def import_jackknife(jacks, name, idl=None):
+1742    """Imports jackknife samples and returns an Obs
+1743
+1744    Parameters
+1745    ----------
+1746    jacks : numpy.ndarray
+1747        numpy array containing the mean value as zeroth entry and
+1748        the N jackknife samples as first to Nth entry.
+1749    name : str
+1750        name of the ensemble the samples are defined on.
+1751    """
+1752    length = len(jacks) - 1
+1753    prj = (np.ones((length, length)) - (length - 1) * np.identity(length))
+1754    samples = jacks[1:] @ prj
+1755    mean = np.mean(samples)
+1756    new_obs = Obs([samples - mean], [name], idl=idl, means=[mean])
+1757    new_obs._value = jacks[0]
+1758    return new_obs
+1759
+1760
+1761def import_bootstrap(boots, name, random_numbers):
+1762    """Imports bootstrap samples and returns an Obs
 1763
-1764def merge_obs(list_of_obs):
-1765    """Combine all observables in list_of_obs into one new observable.
-1766    This allows to merge Obs that have been computed on multiple replica
-1767    of the same ensemble.
-1768    If you like to merge Obs that are based on several ensembles, please
-1769    average them yourself.
-1770
-1771    Parameters
-1772    ----------
-1773    list_of_obs : list
-1774        list of the Obs object to be combined
-1775
-1776    Notes
-1777    -----
-1778    It is not possible to combine obs which are based on the same replicum
-1779    """
-1780    replist = [item for obs in list_of_obs for item in obs.names]
-1781    if (len(replist) == len(set(replist))) is False:
-1782        raise ValueError('list_of_obs contains duplicate replica: %s' % (str(replist)))
-1783    if any([len(o.cov_names) for o in list_of_obs]):
-1784        raise ValueError('Not possible to merge data that contains covobs!')
-1785    new_dict = {}
-1786    idl_dict = {}
-1787    for o in list_of_obs:
-1788        new_dict.update({key: o.deltas.get(key, 0) + o.r_values.get(key, 0)
-1789                        for key in set(o.deltas) | set(o.r_values)})
-1790        idl_dict.update({key: o.idl.get(key, 0) for key in set(o.deltas)})
-1791
-1792    names = sorted(new_dict.keys())
-1793    o = Obs([new_dict[name] for name in names], names, idl=[idl_dict[name] for name in names])
-1794    o.reweighted = np.max([oi.reweighted for oi in list_of_obs])
-1795    return o
-1796
+1764    Parameters
+1765    ----------
+1766    boots : numpy.ndarray
+1767        numpy array containing the mean value as zeroth entry and
+1768        the N bootstrap samples as first to Nth entry.
+1769    name : str
+1770        name of the ensemble the samples are defined on.
+1771    random_numbers : np.ndarray
+1772        Array of shape (samples, length) containing the random numbers to generate the bootstrap samples,
+1773        where samples is the number of bootstrap samples and length is the length of the original Monte Carlo
+1774        chain to be reconstructed.
+1775    """
+1776    samples, length = random_numbers.shape
+1777    if samples != len(boots) - 1:
+1778        raise ValueError("Random numbers do not have the correct shape.")
+1779
+1780    if samples < length:
+1781        raise ValueError("Obs can't be reconstructed if there are fewer bootstrap samples than Monte Carlo data points.")
+1782
+1783    proj = np.vstack([np.bincount(o, minlength=length) for o in random_numbers]) / length
+1784
+1785    samples = scipy.linalg.lstsq(proj, boots[1:])[0]
+1786    ret = Obs([samples], [name])
+1787    ret._value = boots[0]
+1788    return ret
+1789
+1790
+1791def merge_obs(list_of_obs):
+1792    """Combine all observables in list_of_obs into one new observable.
+1793    This allows to merge Obs that have been computed on multiple replica
+1794    of the same ensemble.
+1795    If you like to merge Obs that are based on several ensembles, please
+1796    average them yourself.
 1797
-1798def cov_Obs(means, cov, name, grad=None):
-1799    """Create an Obs based on mean(s) and a covariance matrix
-1800
-1801    Parameters
-1802    ----------
-1803    mean : list of floats or float
-1804        N mean value(s) of the new Obs
-1805    cov : list or array
-1806        2d (NxN) Covariance matrix, 1d diagonal entries or 0d covariance
-1807    name : str
-1808        identifier for the covariance matrix
-1809    grad : list or array
-1810        Gradient of the Covobs wrt. the means belonging to cov.
-1811    """
-1812
-1813    def covobs_to_obs(co):
-1814        """Make an Obs out of a Covobs
-1815
-1816        Parameters
-1817        ----------
-1818        co : Covobs
-1819            Covobs to be embedded into the Obs
-1820        """
-1821        o = Obs([], [], means=[])
-1822        o._value = co.value
-1823        o.names.append(co.name)
-1824        o._covobs[co.name] = co
-1825        o._dvalue = np.sqrt(co.errsq())
-1826        return o
+1798    Parameters
+1799    ----------
+1800    list_of_obs : list
+1801        list of the Obs object to be combined
+1802
+1803    Notes
+1804    -----
+1805    It is not possible to combine obs which are based on the same replicum
+1806    """
+1807    replist = [item for obs in list_of_obs for item in obs.names]
+1808    if (len(replist) == len(set(replist))) is False:
+1809        raise ValueError(f'list_of_obs contains duplicate replica: {replist!s}')
+1810    if any([len(o.cov_names) for o in list_of_obs]):
+1811        raise ValueError('Not possible to merge data that contains covobs!')
+1812    new_dict = {}
+1813    idl_dict = {}
+1814    for o in list_of_obs:
+1815        new_dict.update({key: o.deltas.get(key, 0) + o.r_values.get(key, 0)
+1816                        for key in set(o.deltas) | set(o.r_values)})
+1817        idl_dict.update({key: o.idl.get(key, 0) for key in set(o.deltas)})
+1818
+1819    names = sorted(new_dict.keys())
+1820    o = Obs([new_dict[name] for name in names], names, idl=[idl_dict[name] for name in names])
+1821    o.reweighted = np.max([oi.reweighted for oi in list_of_obs])
+1822    return o
+1823
+1824
+1825def cov_Obs(means, cov, name, grad=None):
+1826    """Create an Obs based on mean(s) and a covariance matrix
 1827
-1828    ol = []
-1829    if isinstance(means, (float, int)):
-1830        means = [means]
-1831
-1832    for i in range(len(means)):
-1833        ol.append(covobs_to_obs(Covobs(means[i], cov, name, pos=i, grad=grad)))
-1834    if ol[0].covobs[name].N != len(means):
-1835        raise ValueError('You have to provide %d mean values!' % (ol[0].N))
-1836    if len(ol) == 1:
-1837        return ol[0]
-1838    return ol
+1828    Parameters
+1829    ----------
+1830    mean : list of floats or float
+1831        N mean value(s) of the new Obs
+1832    cov : list or array
+1833        2d (NxN) Covariance matrix, 1d diagonal entries or 0d covariance
+1834    name : str
+1835        identifier for the covariance matrix
+1836    grad : list or array
+1837        Gradient of the Covobs wrt. the means belonging to cov.
+1838    """
 1839
-1840
-1841def _determine_gap(o, e_content, e_name):
-1842    gaps = []
-1843    for r_name in e_content[e_name]:
-1844        if isinstance(o.idl[r_name], range):
-1845            gaps.append(o.idl[r_name].step)
-1846        else:
-1847            gaps.append(np.min(np.diff(o.idl[r_name])))
-1848
-1849    gap = min(gaps)
-1850    if not np.all([gi % gap == 0 for gi in gaps]):
-1851        raise ValueError(f"Replica for ensemble {e_name} do not have a common spacing.", gaps)
-1852
-1853    return gap
+1840    def covobs_to_obs(co):
+1841        """Make an Obs out of a Covobs
+1842
+1843        Parameters
+1844        ----------
+1845        co : Covobs
+1846            Covobs to be embedded into the Obs
+1847        """
+1848        o = Obs([], [], means=[])
+1849        o._value = co.value
+1850        o.names.append(co.name)
+1851        o._covobs[co.name] = co
+1852        o._dvalue = np.sqrt(co.errsq())
+1853        return o
 1854
-1855
-1856def _check_lists_equal(idl):
-1857    '''
-1858    Use groupby to efficiently check whether all elements of idl are identical.
-1859    Returns True if all elements are equal, otherwise False.
-1860
-1861    Parameters
-1862    ----------
-1863    idl : list of lists, ranges or np.ndarrays
-1864    '''
-1865    g = groupby([np.nditer(el) if isinstance(el, np.ndarray) else el for el in idl])
-1866    if next(g, True) and not next(g, False):
-1867        return True
-1868    return False
+1855    ol = []
+1856    if isinstance(means, (float, int)):
+1857        means = [means]
+1858
+1859    for i in range(len(means)):
+1860        ol.append(covobs_to_obs(Covobs(means[i], cov, name, pos=i, grad=grad)))
+1861    if ol[0].covobs[name].N != len(means):
+1862        raise ValueError(f'You have to provide {ol[0].N} mean values!')
+1863    if len(ol) == 1:
+1864        return ol[0]
+1865    return ol
+1866
+1867
+1868def _determine_gap(o, e_content, e_name):
+1869    gaps = []
+1870    for r_name in e_content[e_name]:
+1871        if isinstance(o.idl[r_name], range):
+1872            gaps.append(o.idl[r_name].step)
+1873        else:
+1874            gaps.append(np.min(np.diff(o.idl[r_name])))
+1875
+1876    gap = min(gaps)
+1877    if not np.all([gi % gap == 0 for gi in gaps]):
+1878        raise ValueError(f"Replica for ensemble {e_name} do not have a common spacing.", gaps)
+1879
+1880    return gap
+1881
+1882
+1883def _check_lists_equal(idl):
+1884    '''
+1885    Use groupby to efficiently check whether all elements of idl are identical.
+1886    Returns True if all elements are equal, otherwise False.
+1887
+1888    Parameters
+1889    ----------
+1890    idl : list of lists, ranges or np.ndarrays
+1891    '''
+1892    g = groupby([np.nditer(el) if isinstance(el, np.ndarray) else el for el in idl])
+1893    if next(g, True) and not next(g, False):
+1894        return True
+1895    return False
 
@@ -2217,904 +2244,926 @@
-
 19class Obs:
- 20    """Class for a general observable.
- 21
- 22    Instances of Obs are the basic objects of a pyerrors error analysis.
- 23    They are initialized with a list which contains arrays of samples for
- 24    different ensembles/replica and another list of same length which contains
- 25    the names of the ensembles/replica. Mathematical operations can be
- 26    performed on instances. The result is another instance of Obs. The error of
- 27    an instance can be computed with the gamma_method. Also contains additional
- 28    methods for output and visualization of the error calculation.
- 29
- 30    Attributes
- 31    ----------
- 32    S_global : float
- 33        Standard value for S (default 2.0)
- 34    S_dict : dict
- 35        Dictionary for S values. If an entry for a given ensemble
- 36        exists this overwrites the standard value for that ensemble.
- 37    tau_exp_global : float
- 38        Standard value for tau_exp (default 0.0)
- 39    tau_exp_dict : dict
- 40        Dictionary for tau_exp values. If an entry for a given ensemble exists
- 41        this overwrites the standard value for that ensemble.
- 42    N_sigma_global : float
- 43        Standard value for N_sigma (default 1.0)
- 44    N_sigma_dict : dict
- 45        Dictionary for N_sigma values. If an entry for a given ensemble exists
- 46        this overwrites the standard value for that ensemble.
- 47    """
- 48    __slots__ = ['names', 'shape', 'r_values', 'deltas', 'N', '_value', '_dvalue',
- 49                 'ddvalue', 'reweighted', 'S', 'tau_exp', 'N_sigma',
- 50                 'e_dvalue', 'e_ddvalue', 'e_tauint', 'e_dtauint',
- 51                 'e_windowsize', 'e_rho', 'e_drho', 'e_n_tauint', 'e_n_dtauint',
- 52                 'idl', 'tag', '_covobs', '__dict__']
- 53
- 54    S_global = 2.0
- 55    S_dict = {}
- 56    tau_exp_global = 0.0
- 57    tau_exp_dict = {}
- 58    N_sigma_global = 1.0
- 59    N_sigma_dict = {}
- 60
- 61    def __init__(self, samples, names, idl=None, **kwargs):
- 62        """ Initialize Obs object.
- 63
- 64        Parameters
- 65        ----------
- 66        samples : list
- 67            list of numpy arrays containing the Monte Carlo samples
- 68        names : list
- 69            list of strings labeling the individual samples
- 70        idl : list, optional
- 71            list of ranges or lists on which the samples are defined
- 72        """
- 73
- 74        if kwargs.get("means") is None and len(samples):
- 75            if len(samples) != len(names):
- 76                raise ValueError('Length of samples and names incompatible.')
- 77            if idl is not None:
- 78                if len(idl) != len(names):
- 79                    raise ValueError('Length of idl incompatible with samples and names.')
- 80            name_length = len(names)
- 81            if name_length > 1:
- 82                if name_length != len(set(names)):
- 83                    raise ValueError('Names are not unique.')
- 84                if not all(isinstance(x, str) for x in names):
- 85                    raise TypeError('All names have to be strings.')
- 86                if len(set([o.split('|')[0] for o in names])) > 1:
- 87                    raise ValueError('Cannot initialize Obs based on multiple ensembles. Please average separate Obs from each ensemble.')
- 88            else:
- 89                if not isinstance(names[0], str):
- 90                    raise TypeError('All names have to be strings.')
- 91            if min(len(x) for x in samples) <= 4:
- 92                raise ValueError('Samples have to have at least 5 entries.')
- 93
- 94        self.names = sorted(names)
- 95        self.shape = {}
- 96        self.r_values = {}
- 97        self.deltas = {}
- 98        self._covobs = {}
- 99
-100        self._value = 0
-101        self.N = 0
-102        self.idl = {}
-103        if idl is not None:
-104            for name, idx in sorted(zip(names, idl)):
-105                if isinstance(idx, range):
-106                    self.idl[name] = idx
-107                elif isinstance(idx, (list, np.ndarray)):
-108                    dc = np.unique(np.diff(idx))
-109                    if np.any(dc < 0):
-110                        raise ValueError("Unsorted idx for idl[%s] at position %s" % (name, ' '.join(['%s' % (pos + 1) for pos in np.where(np.diff(idx) < 0)[0]])))
-111                    elif np.any(dc == 0):
-112                        raise ValueError("Duplicate entries in idx for idl[%s] at position %s" % (name, ' '.join(['%s' % (pos + 1) for pos in np.where(np.diff(idx) == 0)[0]])))
-113                    if len(dc) == 1:
-114                        self.idl[name] = range(idx[0], idx[-1] + dc[0], dc[0])
-115                    else:
-116                        self.idl[name] = list(idx)
-117                else:
-118                    raise TypeError('incompatible type for idl[%s].' % (name))
-119        else:
-120            for name, sample in sorted(zip(names, samples)):
-121                self.idl[name] = range(1, len(sample) + 1)
-122
-123        if kwargs.get("means") is not None:
-124            for name, sample, mean in sorted(zip(names, samples, kwargs.get("means"))):
-125                self.shape[name] = len(self.idl[name])
-126                self.N += self.shape[name]
-127                self.r_values[name] = mean
-128                self.deltas[name] = sample
-129        else:
-130            for name, sample in sorted(zip(names, samples)):
-131                self.shape[name] = len(self.idl[name])
-132                self.N += self.shape[name]
-133                if len(sample) != self.shape[name]:
-134                    raise ValueError('Incompatible samples and idx for %s: %d vs. %d' % (name, len(sample), self.shape[name]))
-135                self.r_values[name] = np.mean(sample)
-136                self.deltas[name] = sample - self.r_values[name]
-137                self._value += self.shape[name] * self.r_values[name]
-138            self._value /= self.N
-139
-140        self._dvalue = 0.0
-141        self.ddvalue = 0.0
-142        self.reweighted = False
-143
-144        self.tag = None
-145
-146    @property
-147    def value(self):
-148        return self._value
-149
-150    @property
-151    def dvalue(self):
-152        return self._dvalue
-153
-154    @property
-155    def e_names(self):
-156        return sorted(set([o.split('|')[0] for o in self.names]))
-157
-158    @property
-159    def cov_names(self):
-160        return sorted(set([o for o in self.covobs.keys()]))
-161
-162    @property
-163    def mc_names(self):
-164        return sorted(set([o.split('|')[0] for o in self.names if o not in self.cov_names]))
-165
-166    @property
-167    def e_content(self):
-168        res = {}
-169        for e, e_name in enumerate(self.e_names):
-170            res[e_name] = sorted(filter(lambda x: x.startswith(e_name + '|'), self.names))
-171            if e_name in self.names:
-172                res[e_name].append(e_name)
-173        return res
+            
 22class Obs:
+ 23    """Class for a general observable.
+ 24
+ 25    Instances of Obs are the basic objects of a pyerrors error analysis.
+ 26    They are initialized with a list which contains arrays of samples for
+ 27    different ensembles/replica and another list of same length which contains
+ 28    the names of the ensembles/replica. Mathematical operations can be
+ 29    performed on instances. The result is another instance of Obs. The error of
+ 30    an instance can be computed with the gamma_method. Also contains additional
+ 31    methods for output and visualization of the error calculation.
+ 32
+ 33    Attributes
+ 34    ----------
+ 35    S_global : float
+ 36        Standard value for S (default 2.0)
+ 37    S_dict : dict
+ 38        Dictionary for S values. If an entry for a given ensemble
+ 39        exists this overwrites the standard value for that ensemble.
+ 40    tau_exp_global : float
+ 41        Standard value for tau_exp (default 0.0)
+ 42    tau_exp_dict : dict
+ 43        Dictionary for tau_exp values. If an entry for a given ensemble exists
+ 44        this overwrites the standard value for that ensemble.
+ 45    N_sigma_global : float
+ 46        Standard value for N_sigma (default 1.0)
+ 47    N_sigma_dict : dict
+ 48        Dictionary for N_sigma values. If an entry for a given ensemble exists
+ 49        this overwrites the standard value for that ensemble.
+ 50    """
+ 51    __slots__ = [
+ 52        'N',
+ 53        'N_sigma',
+ 54        'S',
+ 55        '__dict__',
+ 56        '_covobs',
+ 57        '_dvalue',
+ 58        '_value',
+ 59        'ddvalue',
+ 60        'deltas',
+ 61        'e_ddvalue',
+ 62        'e_drho',
+ 63        'e_dtauint',
+ 64        'e_dvalue',
+ 65        'e_n_dtauint',
+ 66        'e_n_tauint',
+ 67        'e_rho',
+ 68        'e_tauint',
+ 69        'e_windowsize',
+ 70        'idl',
+ 71        'names',
+ 72        'r_values',
+ 73        'reweighted',
+ 74        'shape',
+ 75        'tag',
+ 76        'tau_exp',
+ 77    ]
+ 78
+ 79    S_global = 2.0
+ 80    S_dict: ClassVar[dict] = {}
+ 81    tau_exp_global = 0.0
+ 82    tau_exp_dict: ClassVar[dict] = {}
+ 83    N_sigma_global = 1.0
+ 84    N_sigma_dict: ClassVar[dict] = {}
+ 85
+ 86    def __init__(self, samples, names, idl=None, **kwargs):
+ 87        """ Initialize Obs object.
+ 88
+ 89        Parameters
+ 90        ----------
+ 91        samples : list
+ 92            list of numpy arrays containing the Monte Carlo samples
+ 93        names : list
+ 94            list of strings labeling the individual samples
+ 95        idl : list, optional
+ 96            list of ranges or lists on which the samples are defined
+ 97        """
+ 98
+ 99        if kwargs.get("means") is None and len(samples):
+100            if len(samples) != len(names):
+101                raise ValueError('Length of samples and names incompatible.')
+102            if idl is not None:
+103                if len(idl) != len(names):
+104                    raise ValueError('Length of idl incompatible with samples and names.')
+105            name_length = len(names)
+106            if name_length > 1:
+107                if name_length != len(set(names)):
+108                    raise ValueError('Names are not unique.')
+109                if not all(isinstance(x, str) for x in names):
+110                    raise TypeError('All names have to be strings.')
+111                if len(set([o.split('|')[0] for o in names])) > 1:
+112                    raise ValueError('Cannot initialize Obs based on multiple ensembles. Please average separate Obs from each ensemble.')
+113            else:
+114                if not isinstance(names[0], str):
+115                    raise TypeError('All names have to be strings.')
+116            if min(len(x) for x in samples) <= 4:
+117                raise ValueError('Samples have to have at least 5 entries.')
+118
+119        self.names = sorted(names)
+120        self.shape = {}
+121        self.r_values = {}
+122        self.deltas = {}
+123        self._covobs = {}
+124
+125        self._value = 0
+126        self.N = 0
+127        self.idl = {}
+128        if idl is not None:
+129            for name, idx in sorted(zip(names, idl, strict=True)):
+130                if isinstance(idx, range):
+131                    self.idl[name] = idx
+132                elif isinstance(idx, (list, np.ndarray)):
+133                    dc = np.unique(np.diff(idx))
+134                    if np.any(dc < 0):
+135                        raise ValueError("Unsorted idx for idl[{}] at position {}".format(name, ' '.join(['%s' % (pos + 1) for pos in np.where(np.diff(idx) < 0)[0]])))
+136                    elif np.any(dc == 0):
+137                        raise ValueError("Duplicate entries in idx for idl[{}] at position {}".format(name, ' '.join(['%s' % (pos + 1) for pos in np.where(np.diff(idx) == 0)[0]])))
+138                    if len(dc) == 1:
+139                        self.idl[name] = range(idx[0], idx[-1] + dc[0], dc[0])
+140                    else:
+141                        self.idl[name] = list(idx)
+142                else:
+143                    raise TypeError(f'incompatible type for idl[{name}].')
+144        else:
+145            for name, sample in sorted(zip(names, samples, strict=True)):
+146                self.idl[name] = range(1, len(sample) + 1)
+147
+148        if kwargs.get("means") is not None:
+149            for name, sample, mean in sorted(zip(names, samples, kwargs.get("means"), strict=True)):
+150                self.shape[name] = len(self.idl[name])
+151                self.N += self.shape[name]
+152                self.r_values[name] = mean
+153                self.deltas[name] = sample
+154        else:
+155            for name, sample in sorted(zip(names, samples, strict=True)):
+156                self.shape[name] = len(self.idl[name])
+157                self.N += self.shape[name]
+158                if len(sample) != self.shape[name]:
+159                    raise ValueError(f'Incompatible samples and idx for {name}: {len(sample)} vs. {self.shape[name]}')
+160                self.r_values[name] = np.mean(sample)
+161                self.deltas[name] = sample - self.r_values[name]
+162                self._value += self.shape[name] * self.r_values[name]
+163            self._value /= self.N
+164
+165        self._dvalue = 0.0
+166        self.ddvalue = 0.0
+167        self.reweighted = False
+168
+169        self.tag = None
+170
+171    @property
+172    def value(self):
+173        return self._value
 174
 175    @property
-176    def covobs(self):
-177        return self._covobs
+176    def dvalue(self):
+177        return self._dvalue
 178
-179    def gamma_method(self, **kwargs):
-180        """Estimate the error and related properties of the Obs.
-181
-182        Parameters
-183        ----------
-184        S : float
-185            specifies a custom value for the parameter S (default 2.0).
-186            If set to 0 it is assumed that the data exhibits no
-187            autocorrelation. In this case the error estimates coincides
-188            with the sample standard error.
-189        tau_exp : float
-190            positive value triggers the critical slowing down analysis
-191            (default 0.0).
-192        N_sigma : float
-193            number of standard deviations from zero until the tail is
-194            attached to the autocorrelation function (default 1).
-195        fft : bool
-196            determines whether the fft algorithm is used for the computation
-197            of the autocorrelation function (default True)
-198        """
+179    @property
+180    def e_names(self):
+181        return sorted(set([o.split('|')[0] for o in self.names]))
+182
+183    @property
+184    def cov_names(self):
+185        return sorted(set([o for o in self.covobs.keys()]))
+186
+187    @property
+188    def mc_names(self):
+189        return sorted(set([o.split('|')[0] for o in self.names if o not in self.cov_names]))
+190
+191    @property
+192    def e_content(self):
+193        res = {}
+194        for _e, e_name in enumerate(self.e_names):
+195            res[e_name] = sorted(filter(lambda x: x.startswith(e_name + '|'), self.names))
+196            if e_name in self.names:
+197                res[e_name].append(e_name)
+198        return res
 199
-200        e_content = self.e_content
-201        self.e_dvalue = {}
-202        self.e_ddvalue = {}
-203        self.e_tauint = {}
-204        self.e_dtauint = {}
-205        self.e_windowsize = {}
-206        self.e_n_tauint = {}
-207        self.e_n_dtauint = {}
-208        e_gamma = {}
-209        self.e_rho = {}
-210        self.e_drho = {}
-211        self._dvalue = 0
-212        self.ddvalue = 0
-213
-214        self.S = {}
-215        self.tau_exp = {}
-216        self.N_sigma = {}
-217
-218        if kwargs.get('fft') is False:
-219            fft = False
-220        else:
-221            fft = True
-222
-223        def _parse_kwarg(kwarg_name):
-224            if kwarg_name in kwargs:
-225                tmp = kwargs.get(kwarg_name)
-226                if isinstance(tmp, (int, float)):
-227                    if tmp < 0:
-228                        raise ValueError(kwarg_name + ' has to be larger or equal to 0.')
-229                    for e, e_name in enumerate(self.e_names):
-230                        getattr(self, kwarg_name)[e_name] = tmp
-231                else:
-232                    raise TypeError(kwarg_name + ' is not in proper format.')
-233            else:
-234                for e, e_name in enumerate(self.e_names):
-235                    if e_name in getattr(Obs, kwarg_name + '_dict'):
-236                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_dict')[e_name]
-237                    else:
-238                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_global')
-239
-240        _parse_kwarg('S')
-241        _parse_kwarg('tau_exp')
-242        _parse_kwarg('N_sigma')
-243
-244        for e, e_name in enumerate(self.mc_names):
-245            gapsize = _determine_gap(self, e_content, e_name)
-246
-247            r_length = []
-248            for r_name in e_content[e_name]:
-249                if isinstance(self.idl[r_name], range):
-250                    r_length.append(len(self.idl[r_name]) * self.idl[r_name].step // gapsize)
-251                else:
-252                    r_length.append((self.idl[r_name][-1] - self.idl[r_name][0] + 1) // gapsize)
-253
-254            e_N = np.sum([self.shape[r_name] for r_name in e_content[e_name]])
-255            w_max = max(r_length) // 2
-256            e_gamma[e_name] = np.zeros(w_max)
-257            self.e_rho[e_name] = np.zeros(w_max)
-258            self.e_drho[e_name] = np.zeros(w_max)
-259
-260            for r_name in e_content[e_name]:
-261                e_gamma[e_name] += self._calc_gamma(self.deltas[r_name], self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
-262
-263            gamma_div = np.zeros(w_max)
-264            for r_name in e_content[e_name]:
-265                gamma_div += self._calc_gamma(np.ones((self.shape[r_name])), self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
-266            gamma_div[gamma_div < 1] = 1.0
-267            e_gamma[e_name] /= gamma_div[:w_max]
+200    @property
+201    def covobs(self):
+202        return self._covobs
+203
+204    def gamma_method(self, **kwargs):
+205        """Estimate the error and related properties of the Obs.
+206
+207        Parameters
+208        ----------
+209        S : float
+210            specifies a custom value for the parameter S (default 2.0).
+211            If set to 0 it is assumed that the data exhibits no
+212            autocorrelation. In this case the error estimates coincides
+213            with the sample standard error.
+214        tau_exp : float
+215            positive value triggers the critical slowing down analysis
+216            (default 0.0).
+217        N_sigma : float
+218            number of standard deviations from zero until the tail is
+219            attached to the autocorrelation function (default 1).
+220        fft : bool
+221            determines whether the fft algorithm is used for the computation
+222            of the autocorrelation function (default True)
+223        """
+224
+225        e_content = self.e_content
+226        self.e_dvalue = {}
+227        self.e_ddvalue = {}
+228        self.e_tauint = {}
+229        self.e_dtauint = {}
+230        self.e_windowsize = {}
+231        self.e_n_tauint = {}
+232        self.e_n_dtauint = {}
+233        e_gamma = {}
+234        self.e_rho = {}
+235        self.e_drho = {}
+236        self._dvalue = 0
+237        self.ddvalue = 0
+238
+239        self.S = {}
+240        self.tau_exp = {}
+241        self.N_sigma = {}
+242
+243        if kwargs.get('fft') is False:
+244            fft = False
+245        else:
+246            fft = True
+247
+248        def _parse_kwarg(kwarg_name):
+249            if kwarg_name in kwargs:
+250                tmp = kwargs.get(kwarg_name)
+251                if isinstance(tmp, (int, float)):
+252                    if tmp < 0:
+253                        raise ValueError(kwarg_name + ' has to be larger or equal to 0.')
+254                    for _e, e_name in enumerate(self.e_names):
+255                        getattr(self, kwarg_name)[e_name] = tmp
+256                else:
+257                    raise TypeError(kwarg_name + ' is not in proper format.')
+258            else:
+259                for _e, e_name in enumerate(self.e_names):
+260                    if e_name in getattr(Obs, kwarg_name + '_dict'):
+261                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_dict')[e_name]
+262                    else:
+263                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_global')
+264
+265        _parse_kwarg('S')
+266        _parse_kwarg('tau_exp')
+267        _parse_kwarg('N_sigma')
 268
-269            if np.abs(e_gamma[e_name][0]) < 10 * np.finfo(float).tiny:  # Prevent division by zero
-270                self.e_tauint[e_name] = 0.5
-271                self.e_dtauint[e_name] = 0.0
-272                self.e_dvalue[e_name] = 0.0
-273                self.e_ddvalue[e_name] = 0.0
-274                self.e_windowsize[e_name] = 0
-275                continue
-276
-277            self.e_rho[e_name] = e_gamma[e_name][:w_max] / e_gamma[e_name][0]
-278            self.e_n_tauint[e_name] = np.cumsum(np.concatenate(([0.5], self.e_rho[e_name][1:])))
-279            # Make sure no entry of tauint is smaller than 0.5
-280            self.e_n_tauint[e_name][self.e_n_tauint[e_name] <= 0.5] = 0.5 + np.finfo(np.float64).eps
-281            # hep-lat/0306017 eq. (42)
-282            self.e_n_dtauint[e_name] = self.e_n_tauint[e_name] * 2 * np.sqrt(np.abs(np.arange(w_max) + 0.5 - self.e_n_tauint[e_name]) / e_N)
-283            self.e_n_dtauint[e_name][0] = 0.0
+269        for _e, e_name in enumerate(self.mc_names):
+270            gapsize = _determine_gap(self, e_content, e_name)
+271
+272            r_length = []
+273            for r_name in e_content[e_name]:
+274                if isinstance(self.idl[r_name], range):
+275                    r_length.append(len(self.idl[r_name]) * self.idl[r_name].step // gapsize)
+276                else:
+277                    r_length.append((self.idl[r_name][-1] - self.idl[r_name][0] + 1) // gapsize)
+278
+279            e_N = np.sum([self.shape[r_name] for r_name in e_content[e_name]])
+280            w_max = max(r_length) // 2
+281            e_gamma[e_name] = np.zeros(w_max)
+282            self.e_rho[e_name] = np.zeros(w_max)
+283            self.e_drho[e_name] = np.zeros(w_max)
 284
-285            def _compute_drho(i):
-286                tmp = (self.e_rho[e_name][i + 1:w_max]
-287                       + np.concatenate([self.e_rho[e_name][i - 1:None if i - (w_max - 1) // 2 <= 0 else (2 * i - (2 * w_max) // 2):-1],
-288                                         self.e_rho[e_name][1:max(1, w_max - 2 * i)]])
-289                       - 2 * self.e_rho[e_name][i] * self.e_rho[e_name][1:w_max - i])
-290                self.e_drho[e_name][i] = np.sqrt(np.sum(tmp ** 2) / e_N)
-291
-292            if self.tau_exp[e_name] > 0:
-293                _compute_drho(1)
-294                texp = self.tau_exp[e_name]
-295                # Critical slowing down analysis
-296                if w_max // 2 <= 1:
-297                    raise ValueError("Need at least 8 samples for tau_exp error analysis")
-298                for n in range(1, w_max // 2):
-299                    _compute_drho(n + 1)
-300                    if (self.e_rho[e_name][n] - self.N_sigma[e_name] * self.e_drho[e_name][n]) < 0 or n >= w_max // 2 - 2:
-301                        # Bias correction hep-lat/0306017 eq. (49) included
-302                        self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N) + texp * np.abs(self.e_rho[e_name][n + 1])  # The absolute makes sure, that the tail contribution is always positive
-303                        self.e_dtauint[e_name] = np.sqrt(self.e_n_dtauint[e_name][n] ** 2 + texp ** 2 * self.e_drho[e_name][n + 1] ** 2)
-304                        # Error of tau_exp neglected so far, missing term: self.e_rho[e_name][n + 1] ** 2 * d_tau_exp ** 2
-305                        self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
-306                        self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
-307                        self.e_windowsize[e_name] = n
-308                        break
-309            else:
-310                if self.S[e_name] == 0.0:
-311                    self.e_tauint[e_name] = 0.5
-312                    self.e_dtauint[e_name] = 0.0
-313                    self.e_dvalue[e_name] = np.sqrt(e_gamma[e_name][0] / (e_N - 1))
-314                    self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt(0.5 / e_N)
-315                    self.e_windowsize[e_name] = 0
-316                else:
-317                    # Standard automatic windowing procedure
-318                    tau = self.S[e_name] / np.log((2 * self.e_n_tauint[e_name][1:] + 1) / (2 * self.e_n_tauint[e_name][1:] - 1))
-319                    g_w = np.exp(- np.arange(1, len(tau) + 1) / tau) - tau / np.sqrt(np.arange(1, len(tau) + 1) * e_N)
-320                    for n in range(1, w_max):
-321                        if g_w[n - 1] < 0 or n >= w_max - 1:
-322                            _compute_drho(n)
-323                            self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N)  # Bias correction hep-lat/0306017 eq. (49)
-324                            self.e_dtauint[e_name] = self.e_n_dtauint[e_name][n]
-325                            self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
-326                            self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
-327                            self.e_windowsize[e_name] = n
-328                            break
-329
-330            self._dvalue += self.e_dvalue[e_name] ** 2
-331            self.ddvalue += (self.e_dvalue[e_name] * self.e_ddvalue[e_name]) ** 2
-332
-333        for e_name in self.cov_names:
-334            self.e_dvalue[e_name] = np.sqrt(self.covobs[e_name].errsq())
-335            self.e_ddvalue[e_name] = 0
-336            self._dvalue += self.e_dvalue[e_name]**2
-337
-338        self._dvalue = np.sqrt(self._dvalue)
-339        if self._dvalue == 0.0:
-340            self.ddvalue = 0.0
-341        else:
-342            self.ddvalue = np.sqrt(self.ddvalue) / self._dvalue
-343        return
-344
-345    gm = gamma_method
-346
-347    def _calc_gamma(self, deltas, idx, shape, w_max, fft, gapsize):
-348        """Calculate Gamma_{AA} from the deltas, which are defined on idx.
-349           idx is assumed to be a contiguous range (possibly with a stepsize != 1)
-350
-351        Parameters
-352        ----------
-353        deltas : list
-354            List of fluctuations
-355        idx : list
-356            List or range of configurations on which the deltas are defined.
-357        shape : int
-358            Number of configurations in idx.
-359        w_max : int
-360            Upper bound for the summation window.
-361        fft : bool
-362            determines whether the fft algorithm is used for the computation
-363            of the autocorrelation function.
-364        gapsize : int
-365            The target distance between two configurations. If longer distances
-366            are found in idx, the data is expanded.
-367        """
-368        gamma = np.zeros(w_max)
-369        deltas = _expand_deltas(deltas, idx, shape, gapsize)
-370        new_shape = len(deltas)
-371        if fft:
-372            max_gamma = min(new_shape, w_max)
-373            # The padding for the fft has to be even
-374            padding = new_shape + max_gamma + (new_shape + max_gamma) % 2
-375            gamma[:max_gamma] += np.fft.irfft(np.abs(np.fft.rfft(deltas, padding)) ** 2)[:max_gamma]
-376        else:
-377            for n in range(w_max):
-378                if new_shape - n >= 0:
-379                    gamma[n] += deltas[0:new_shape - n].dot(deltas[n:new_shape])
-380
-381        return gamma
-382
-383    def details(self, ens_content=True):
-384        """Output detailed properties of the Obs.
-385
-386        Parameters
-387        ----------
-388        ens_content : bool
-389            print details about the ensembles and replica if true.
-390        """
-391        if self.tag is not None:
-392            print("Description:", self.tag)
-393        if not hasattr(self, 'e_dvalue'):
-394            print('Result\t %3.8e' % (self.value))
-395        else:
-396            if self.value == 0.0:
-397                percentage = np.nan
-398            else:
-399                percentage = np.abs(self._dvalue / self.value) * 100
-400            print('Result\t %3.8e +/- %3.8e +/- %3.8e (%3.3f%%)' % (self.value, self._dvalue, self.ddvalue, percentage))
-401            if len(self.e_names) > 1:
-402                print(' Ensemble errors:')
-403            e_content = self.e_content
-404            for e_name in self.mc_names:
-405                gap = _determine_gap(self, e_content, e_name)
-406
-407                if len(self.e_names) > 1:
-408                    print('', e_name, '\t %3.6e +/- %3.6e' % (self.e_dvalue[e_name], self.e_ddvalue[e_name]))
-409                tau_string = " \N{GREEK SMALL LETTER TAU}_int\t " + _format_uncertainty(self.e_tauint[e_name], self.e_dtauint[e_name])
-410                tau_string += f" in units of {gap} config"
-411                if gap > 1:
-412                    tau_string += "s"
-413                if self.tau_exp[e_name] > 0:
-414                    tau_string = f"{tau_string: <45}" + '\t(\N{GREEK SMALL LETTER TAU}_exp=%3.2f, N_\N{GREEK SMALL LETTER SIGMA}=%1.0i)' % (self.tau_exp[e_name], self.N_sigma[e_name])
-415                else:
-416                    tau_string = f"{tau_string: <45}" + '\t(S=%3.2f)' % (self.S[e_name])
-417                print(tau_string)
-418            for e_name in self.cov_names:
-419                print('', e_name, '\t %3.8e' % (self.e_dvalue[e_name]))
-420        if ens_content is True:
-421            if len(self.e_names) == 1:
-422                print(self.N, 'samples in', len(self.e_names), 'ensemble:')
+285            for r_name in e_content[e_name]:
+286                e_gamma[e_name] += self._calc_gamma(self.deltas[r_name], self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
+287
+288            gamma_div = np.zeros(w_max)
+289            for r_name in e_content[e_name]:
+290                gamma_div += self._calc_gamma(np.ones(self.shape[r_name]), self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
+291            gamma_div[gamma_div < 1] = 1.0
+292            e_gamma[e_name] /= gamma_div[:w_max]
+293
+294            if np.abs(e_gamma[e_name][0]) < 10 * np.finfo(float).tiny:  # Prevent division by zero
+295                self.e_tauint[e_name] = 0.5
+296                self.e_dtauint[e_name] = 0.0
+297                self.e_dvalue[e_name] = 0.0
+298                self.e_ddvalue[e_name] = 0.0
+299                self.e_windowsize[e_name] = 0
+300                continue
+301
+302            self.e_rho[e_name] = e_gamma[e_name][:w_max] / e_gamma[e_name][0]
+303            self.e_n_tauint[e_name] = np.cumsum(np.concatenate(([0.5], self.e_rho[e_name][1:])))
+304            # Make sure no entry of tauint is smaller than 0.5
+305            self.e_n_tauint[e_name][self.e_n_tauint[e_name] <= 0.5] = 0.5 + np.finfo(np.float64).eps
+306            # hep-lat/0306017 eq. (42)
+307            self.e_n_dtauint[e_name] = self.e_n_tauint[e_name] * 2 * np.sqrt(np.abs(np.arange(w_max) + 0.5 - self.e_n_tauint[e_name]) / e_N)
+308            self.e_n_dtauint[e_name][0] = 0.0
+309
+310            def _compute_drho(i, e_name=e_name, w_max=w_max, e_N=e_N):
+311                tmp = (self.e_rho[e_name][i + 1:w_max]
+312                       + np.concatenate([self.e_rho[e_name][i - 1:None if i - (w_max - 1) // 2 <= 0 else (2 * i - (2 * w_max) // 2):-1],
+313                                         self.e_rho[e_name][1:max(1, w_max - 2 * i)]])
+314                       - 2 * self.e_rho[e_name][i] * self.e_rho[e_name][1:w_max - i])
+315                self.e_drho[e_name][i] = np.sqrt(np.sum(tmp ** 2) / e_N)
+316
+317            if self.tau_exp[e_name] > 0:
+318                _compute_drho(1)
+319                texp = self.tau_exp[e_name]
+320                # Critical slowing down analysis
+321                if w_max // 2 <= 1:
+322                    raise ValueError("Need at least 8 samples for tau_exp error analysis")
+323                for n in range(1, w_max // 2):
+324                    _compute_drho(n + 1)
+325                    if (self.e_rho[e_name][n] - self.N_sigma[e_name] * self.e_drho[e_name][n]) < 0 or n >= w_max // 2 - 2:
+326                        # Bias correction hep-lat/0306017 eq. (49) included
+327                        self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N) + texp * np.abs(self.e_rho[e_name][n + 1])  # The absolute makes sure, that the tail contribution is always positive
+328                        self.e_dtauint[e_name] = np.sqrt(self.e_n_dtauint[e_name][n] ** 2 + texp ** 2 * self.e_drho[e_name][n + 1] ** 2)
+329                        # Error of tau_exp neglected so far, missing term: self.e_rho[e_name][n + 1] ** 2 * d_tau_exp ** 2
+330                        self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
+331                        self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
+332                        self.e_windowsize[e_name] = n
+333                        break
+334            else:
+335                if self.S[e_name] == 0.0:
+336                    self.e_tauint[e_name] = 0.5
+337                    self.e_dtauint[e_name] = 0.0
+338                    self.e_dvalue[e_name] = np.sqrt(e_gamma[e_name][0] / (e_N - 1))
+339                    self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt(0.5 / e_N)
+340                    self.e_windowsize[e_name] = 0
+341                else:
+342                    # Standard automatic windowing procedure
+343                    tau = self.S[e_name] / np.log((2 * self.e_n_tauint[e_name][1:] + 1) / (2 * self.e_n_tauint[e_name][1:] - 1))
+344                    g_w = np.exp(- np.arange(1, len(tau) + 1) / tau) - tau / np.sqrt(np.arange(1, len(tau) + 1) * e_N)
+345                    for n in range(1, w_max):
+346                        if g_w[n - 1] < 0 or n >= w_max - 1:
+347                            _compute_drho(n)
+348                            self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N)  # Bias correction hep-lat/0306017 eq. (49)
+349                            self.e_dtauint[e_name] = self.e_n_dtauint[e_name][n]
+350                            self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
+351                            self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
+352                            self.e_windowsize[e_name] = n
+353                            break
+354
+355            self._dvalue += self.e_dvalue[e_name] ** 2
+356            self.ddvalue += (self.e_dvalue[e_name] * self.e_ddvalue[e_name]) ** 2
+357
+358        for e_name in self.cov_names:
+359            self.e_dvalue[e_name] = np.sqrt(self.covobs[e_name].errsq())
+360            self.e_ddvalue[e_name] = 0
+361            self._dvalue += self.e_dvalue[e_name]**2
+362
+363        self._dvalue = np.sqrt(self._dvalue)
+364        if self._dvalue == 0.0:
+365            self.ddvalue = 0.0
+366        else:
+367            self.ddvalue = np.sqrt(self.ddvalue) / self._dvalue
+368        return
+369
+370    gm = gamma_method
+371
+372    def _calc_gamma(self, deltas, idx, shape, w_max, fft, gapsize):
+373        """Calculate Gamma_{AA} from the deltas, which are defined on idx.
+374           idx is assumed to be a contiguous range (possibly with a stepsize != 1)
+375
+376        Parameters
+377        ----------
+378        deltas : list
+379            List of fluctuations
+380        idx : list
+381            List or range of configurations on which the deltas are defined.
+382        shape : int
+383            Number of configurations in idx.
+384        w_max : int
+385            Upper bound for the summation window.
+386        fft : bool
+387            determines whether the fft algorithm is used for the computation
+388            of the autocorrelation function.
+389        gapsize : int
+390            The target distance between two configurations. If longer distances
+391            are found in idx, the data is expanded.
+392        """
+393        gamma = np.zeros(w_max)
+394        deltas = _expand_deltas(deltas, idx, shape, gapsize)
+395        new_shape = len(deltas)
+396        if fft:
+397            max_gamma = min(new_shape, w_max)
+398            # The padding for the fft has to be even
+399            padding = new_shape + max_gamma + (new_shape + max_gamma) % 2
+400            gamma[:max_gamma] += np.fft.irfft(np.abs(np.fft.rfft(deltas, padding)) ** 2)[:max_gamma]
+401        else:
+402            for n in range(w_max):
+403                if new_shape - n >= 0:
+404                    gamma[n] += deltas[0:new_shape - n].dot(deltas[n:new_shape])
+405
+406        return gamma
+407
+408    def details(self, ens_content=True):
+409        """Output detailed properties of the Obs.
+410
+411        Parameters
+412        ----------
+413        ens_content : bool
+414            print details about the ensembles and replica if true.
+415        """
+416        if self.tag is not None:
+417            print("Description:", self.tag)
+418        if not hasattr(self, 'e_dvalue'):
+419            print(f'Result\t {self.value:3.8e}')
+420        else:
+421            if self.value == 0.0:
+422                percentage = np.nan
 423            else:
-424                print(self.N, 'samples in', len(self.e_names), 'ensembles:')
-425            my_string_list = []
-426            for key, value in sorted(self.e_content.items()):
-427                if key not in self.covobs:
-428                    my_string = '  ' + "\u00B7 Ensemble '" + key + "' "
-429                    if len(value) == 1:
-430                        my_string += f': {self.shape[value[0]]} configurations'
-431                        if isinstance(self.idl[value[0]], range):
-432                            my_string += f' (from {self.idl[value[0]].start} to {self.idl[value[0]][-1]}' + int(self.idl[value[0]].step != 1) * f' in steps of {self.idl[value[0]].step}' + ')'
-433                        else:
-434                            my_string += f' (irregular range from {self.idl[value[0]][0]} to {self.idl[value[0]][-1]})'
-435                    else:
-436                        sublist = []
-437                        for v in value:
-438                            my_substring = '    ' + "\u00B7 Replicum '" + v[len(key) + 1:] + "' "
-439                            my_substring += f': {self.shape[v]} configurations'
-440                            if isinstance(self.idl[v], range):
-441                                my_substring += f' (from {self.idl[v].start} to {self.idl[v][-1]}' + int(self.idl[v].step != 1) * f' in steps of {self.idl[v].step}' + ')'
-442                            else:
-443                                my_substring += f' (irregular range from {self.idl[v][0]} to {self.idl[v][-1]})'
-444                            sublist.append(my_substring)
-445
-446                        my_string += '\n' + '\n'.join(sublist)
-447                else:
-448                    my_string = '  ' + "\u00B7 Covobs   '" + key + "' "
-449                my_string_list.append(my_string)
-450            print('\n'.join(my_string_list))
-451
-452    def reweight(self, weight):
-453        """Reweight the obs with given rewighting factors.
-454
-455        Parameters
-456        ----------
-457        weight : Obs
-458            Reweighting factor. An Observable that has to be defined on a superset of the
-459            configurations in obs[i].idl for all i.
-460        all_configs : bool
-461            if True, the reweighted observables are normalized by the average of
-462            the reweighting factor on all configurations in weight.idl and not
-463            on the configurations in obs[i].idl. Default False.
-464        """
-465        return reweight(weight, [self])[0]
-466
-467    def is_zero_within_error(self, sigma=1):
-468        """Checks whether the observable is zero within 'sigma' standard errors.
-469
-470        Parameters
-471        ----------
-472        sigma : int
-473            Number of standard errors used for the check.
-474
-475        Works only properly when the gamma method was run.
-476        """
-477        return self.is_zero() or np.abs(self.value) <= sigma * self._dvalue
-478
-479    def is_zero(self, atol=1e-10):
-480        """Checks whether the observable is zero within a given tolerance.
-481
-482        Parameters
-483        ----------
-484        atol : float
-485            Absolute tolerance (for details see numpy documentation).
-486        """
-487        return np.isclose(0.0, self.value, 1e-14, atol) and all(np.allclose(0.0, delta, 1e-14, atol) for delta in self.deltas.values()) and all(np.allclose(0.0, delta.errsq(), 1e-14, atol) for delta in self.covobs.values())
-488
-489    def plot_tauint(self, save=None):
-490        """Plot integrated autocorrelation time for each ensemble.
+424                percentage = np.abs(self._dvalue / self.value) * 100
+425            print(f'Result\t {self.value:3.8e} +/- {self._dvalue:3.8e} +/- {self.ddvalue:3.8e} ({percentage:3.3f}%)')
+426            if len(self.e_names) > 1:
+427                print(' Ensemble errors:')
+428            e_content = self.e_content
+429            for e_name in self.mc_names:
+430                gap = _determine_gap(self, e_content, e_name)
+431
+432                if len(self.e_names) > 1:
+433                    print('', e_name, f'\t {self.e_dvalue[e_name]:3.6e} +/- {self.e_ddvalue[e_name]:3.6e}')
+434                tau_string = " \N{GREEK SMALL LETTER TAU}_int\t " + _format_uncertainty(self.e_tauint[e_name], self.e_dtauint[e_name])
+435                tau_string += f" in units of {gap} config"
+436                if gap > 1:
+437                    tau_string += "s"
+438                if self.tau_exp[e_name] > 0:
+439                    tau_string = f"{tau_string: <45}" + f'\t(\N{GREEK SMALL LETTER TAU}_exp={self.tau_exp[e_name]:3.2f}, N_\N{GREEK SMALL LETTER SIGMA}={self.N_sigma[e_name]:g})'
+440                else:
+441                    tau_string = f"{tau_string: <45}" + f'\t(S={self.S[e_name]:3.2f})'
+442                print(tau_string)
+443            for e_name in self.cov_names:
+444                print('', e_name, f'\t {self.e_dvalue[e_name]:3.8e}')
+445        if ens_content is True:
+446            if len(self.e_names) == 1:
+447                print(self.N, 'samples in', len(self.e_names), 'ensemble:')
+448            else:
+449                print(self.N, 'samples in', len(self.e_names), 'ensembles:')
+450            my_string_list = []
+451            for key, value in sorted(self.e_content.items()):
+452                if key not in self.covobs:
+453                    my_string = '  ' + "\u00B7 Ensemble '" + key + "' "
+454                    if len(value) == 1:
+455                        my_string += f': {self.shape[value[0]]} configurations'
+456                        if isinstance(self.idl[value[0]], range):
+457                            my_string += f' (from {self.idl[value[0]].start} to {self.idl[value[0]][-1]}' + int(self.idl[value[0]].step != 1) * f' in steps of {self.idl[value[0]].step}' + ')'
+458                        else:
+459                            my_string += f' (irregular range from {self.idl[value[0]][0]} to {self.idl[value[0]][-1]})'
+460                    else:
+461                        sublist = []
+462                        for v in value:
+463                            my_substring = '    ' + "\u00B7 Replicum '" + v[len(key) + 1:] + "' "
+464                            my_substring += f': {self.shape[v]} configurations'
+465                            if isinstance(self.idl[v], range):
+466                                my_substring += f' (from {self.idl[v].start} to {self.idl[v][-1]}' + int(self.idl[v].step != 1) * f' in steps of {self.idl[v].step}' + ')'
+467                            else:
+468                                my_substring += f' (irregular range from {self.idl[v][0]} to {self.idl[v][-1]})'
+469                            sublist.append(my_substring)
+470
+471                        my_string += '\n' + '\n'.join(sublist)
+472                else:
+473                    my_string = '  ' + "\u00B7 Covobs   '" + key + "' "
+474                my_string_list.append(my_string)
+475            print('\n'.join(my_string_list))
+476
+477    def reweight(self, weight):
+478        """Reweight the obs with given rewighting factors.
+479
+480        Parameters
+481        ----------
+482        weight : Obs
+483            Reweighting factor. An Observable that has to be defined on a superset of the
+484            configurations in obs[i].idl for all i.
+485        all_configs : bool
+486            if True, the reweighted observables are normalized by the average of
+487            the reweighting factor on all configurations in weight.idl and not
+488            on the configurations in obs[i].idl. Default False.
+489        """
+490        return reweight(weight, [self])[0]
 491
-492        Parameters
-493        ----------
-494        save : str
-495            saves the figure to a file named 'save' if.
-496        """
-497        if not hasattr(self, 'e_dvalue'):
-498            raise Exception('Run the gamma method first.')
+492    def is_zero_within_error(self, sigma=1):
+493        """Checks whether the observable is zero within 'sigma' standard errors.
+494
+495        Parameters
+496        ----------
+497        sigma : int
+498            Number of standard errors used for the check.
 499
-500        for e, e_name in enumerate(self.mc_names):
-501            fig = plt.figure()
-502            plt.xlabel(r'$W$')
-503            plt.ylabel(r'$\tau_\mathrm{int}$')
-504            length = int(len(self.e_n_tauint[e_name]))
-505            if self.tau_exp[e_name] > 0:
-506                base = self.e_n_tauint[e_name][self.e_windowsize[e_name]]
-507                x_help = np.arange(2 * self.tau_exp[e_name])
-508                y_help = (x_help + 1) * np.abs(self.e_rho[e_name][self.e_windowsize[e_name] + 1]) * (1 - x_help / (2 * (2 * self.tau_exp[e_name] - 1))) + base
-509                x_arr = np.arange(self.e_windowsize[e_name] + 1, self.e_windowsize[e_name] + 1 + 2 * self.tau_exp[e_name])
-510                plt.plot(x_arr, y_help, 'C' + str(e), linewidth=1, ls='--', marker=',')
-511                plt.errorbar([self.e_windowsize[e_name] + 2 * self.tau_exp[e_name]], [self.e_tauint[e_name]],
-512                             yerr=[self.e_dtauint[e_name]], fmt='C' + str(e), linewidth=1, capsize=2, marker='o', mfc=plt.rcParams['axes.facecolor'])
-513                xmax = self.e_windowsize[e_name] + 2 * self.tau_exp[e_name] + 1.5
-514                label = e_name + r', $\tau_\mathrm{exp}$=' + str(np.around(self.tau_exp[e_name], decimals=2))
-515            else:
-516                label = e_name + ', S=' + str(np.around(self.S[e_name], decimals=2))
-517                xmax = max(10.5, 2 * self.e_windowsize[e_name] - 0.5)
-518
-519            plt.errorbar(np.arange(length)[:int(xmax) + 1], self.e_n_tauint[e_name][:int(xmax) + 1], yerr=self.e_n_dtauint[e_name][:int(xmax) + 1], linewidth=1, capsize=2, label=label)
-520            plt.axvline(x=self.e_windowsize[e_name], color='C' + str(e), alpha=0.5, marker=',', ls='--')
-521            plt.legend()
-522            plt.xlim(-0.5, xmax)
-523            ylim = plt.ylim()
-524            plt.ylim(bottom=0.0, top=max(1.0, ylim[1]))
-525            plt.draw()
-526            if save:
-527                fig.savefig(save + "_" + str(e))
-528
-529    def plot_rho(self, save=None):
-530        """Plot normalized autocorrelation function time for each ensemble.
-531
-532        Parameters
-533        ----------
-534        save : str
-535            saves the figure to a file named 'save' if.
-536        """
-537        if not hasattr(self, 'e_dvalue'):
-538            raise Exception('Run the gamma method first.')
-539        for e, e_name in enumerate(self.mc_names):
-540            fig = plt.figure()
-541            plt.xlabel('W')
-542            plt.ylabel('rho')
-543            length = int(len(self.e_drho[e_name]))
-544            plt.errorbar(np.arange(length), self.e_rho[e_name][:length], yerr=self.e_drho[e_name][:], linewidth=1, capsize=2)
-545            plt.axvline(x=self.e_windowsize[e_name], color='r', alpha=0.25, ls='--', marker=',')
-546            if self.tau_exp[e_name] > 0:
-547                plt.plot([self.e_windowsize[e_name] + 1, self.e_windowsize[e_name] + 1 + 2 * self.tau_exp[e_name]],
-548                         [self.e_rho[e_name][self.e_windowsize[e_name] + 1], 0], 'k-', lw=1)
-549                xmax = self.e_windowsize[e_name] + 2 * self.tau_exp[e_name] + 1.5
-550                plt.title('Rho ' + e_name + r', tau\_exp=' + str(np.around(self.tau_exp[e_name], decimals=2)))
-551            else:
-552                xmax = max(10.5, 2 * self.e_windowsize[e_name] - 0.5)
-553                plt.title('Rho ' + e_name + ', S=' + str(np.around(self.S[e_name], decimals=2)))
-554            plt.plot([-0.5, xmax], [0, 0], 'k--', lw=1)
-555            plt.xlim(-0.5, xmax)
-556            plt.draw()
-557            if save:
-558                fig.savefig(save + "_" + str(e))
-559
-560    def plot_rep_dist(self):
-561        """Plot replica distribution for each ensemble with more than one replicum."""
+500        Works only properly when the gamma method was run.
+501        """
+502        return self.is_zero() or np.abs(self.value) <= sigma * self._dvalue
+503
+504    def is_zero(self, atol=1e-10):
+505        """Checks whether the observable is zero within a given tolerance.
+506
+507        Parameters
+508        ----------
+509        atol : float
+510            Absolute tolerance (for details see numpy documentation).
+511        """
+512        return np.isclose(0.0, self.value, 1e-14, atol) and all(np.allclose(0.0, delta, 1e-14, atol) for delta in self.deltas.values()) and all(np.allclose(0.0, delta.errsq(), 1e-14, atol) for delta in self.covobs.values())
+513
+514    def plot_tauint(self, save=None):
+515        """Plot integrated autocorrelation time for each ensemble.
+516
+517        Parameters
+518        ----------
+519        save : str
+520            saves the figure to a file named 'save' if.
+521        """
+522        if not hasattr(self, 'e_dvalue'):
+523            raise Exception('Run the gamma method first.')
+524
+525        for e, e_name in enumerate(self.mc_names):
+526            fig = plt.figure()
+527            plt.xlabel(r'$W$')
+528            plt.ylabel(r'$\tau_\mathrm{int}$')
+529            length = len(self.e_n_tauint[e_name])
+530            if self.tau_exp[e_name] > 0:
+531                base = self.e_n_tauint[e_name][self.e_windowsize[e_name]]
+532                x_help = np.arange(2 * self.tau_exp[e_name])
+533                y_help = (x_help + 1) * np.abs(self.e_rho[e_name][self.e_windowsize[e_name] + 1]) * (1 - x_help / (2 * (2 * self.tau_exp[e_name] - 1))) + base
+534                x_arr = np.arange(self.e_windowsize[e_name] + 1, self.e_windowsize[e_name] + 1 + 2 * self.tau_exp[e_name])
+535                plt.plot(x_arr, y_help, 'C' + str(e), linewidth=1, ls='--', marker=',')
+536                plt.errorbar([self.e_windowsize[e_name] + 2 * self.tau_exp[e_name]], [self.e_tauint[e_name]],
+537                             yerr=[self.e_dtauint[e_name]], fmt='C' + str(e), linewidth=1, capsize=2, marker='o', mfc=plt.rcParams['axes.facecolor'])
+538                xmax = self.e_windowsize[e_name] + 2 * self.tau_exp[e_name] + 1.5
+539                label = e_name + r', $\tau_\mathrm{exp}$=' + str(np.around(self.tau_exp[e_name], decimals=2))
+540            else:
+541                label = e_name + ', S=' + str(np.around(self.S[e_name], decimals=2))
+542                xmax = max(10.5, 2 * self.e_windowsize[e_name] - 0.5)
+543
+544            plt.errorbar(np.arange(length)[:int(xmax) + 1], self.e_n_tauint[e_name][:int(xmax) + 1], yerr=self.e_n_dtauint[e_name][:int(xmax) + 1], linewidth=1, capsize=2, label=label)
+545            plt.axvline(x=self.e_windowsize[e_name], color='C' + str(e), alpha=0.5, marker=',', ls='--')
+546            plt.legend()
+547            plt.xlim(-0.5, xmax)
+548            ylim = plt.ylim()
+549            plt.ylim(bottom=0.0, top=max(1.0, ylim[1]))
+550            plt.draw()
+551            if save:
+552                fig.savefig(save + "_" + str(e))
+553
+554    def plot_rho(self, save=None):
+555        """Plot normalized autocorrelation function time for each ensemble.
+556
+557        Parameters
+558        ----------
+559        save : str
+560            saves the figure to a file named 'save' if.
+561        """
 562        if not hasattr(self, 'e_dvalue'):
 563            raise Exception('Run the gamma method first.')
 564        for e, e_name in enumerate(self.mc_names):
-565            if len(self.e_content[e_name]) == 1:
-566                print('No replica distribution for a single replicum (', e_name, ')')
-567                continue
-568            r_length = []
-569            sub_r_mean = 0
-570            for r, r_name in enumerate(self.e_content[e_name]):
-571                r_length.append(len(self.deltas[r_name]))
-572                sub_r_mean += self.shape[r_name] * self.r_values[r_name]
-573            e_N = np.sum(r_length)
-574            sub_r_mean /= e_N
-575            arr = np.zeros(len(self.e_content[e_name]))
-576            for r, r_name in enumerate(self.e_content[e_name]):
-577                arr[r] = (self.r_values[r_name] - sub_r_mean) / (self.e_dvalue[e_name] * np.sqrt(e_N / self.shape[r_name] - 1))
-578            plt.hist(arr, rwidth=0.8, bins=len(self.e_content[e_name]))
-579            plt.title('Replica distribution' + e_name + ' (mean=0, var=1)')
-580            plt.draw()
-581
-582    def plot_history(self, expand=True):
-583        """Plot derived Monte Carlo history for each ensemble
+565            fig = plt.figure()
+566            plt.xlabel('W')
+567            plt.ylabel('rho')
+568            length = len(self.e_drho[e_name])
+569            plt.errorbar(np.arange(length), self.e_rho[e_name][:length], yerr=self.e_drho[e_name][:], linewidth=1, capsize=2)
+570            plt.axvline(x=self.e_windowsize[e_name], color='r', alpha=0.25, ls='--', marker=',')
+571            if self.tau_exp[e_name] > 0:
+572                plt.plot([self.e_windowsize[e_name] + 1, self.e_windowsize[e_name] + 1 + 2 * self.tau_exp[e_name]],
+573                         [self.e_rho[e_name][self.e_windowsize[e_name] + 1], 0], 'k-', lw=1)
+574                xmax = self.e_windowsize[e_name] + 2 * self.tau_exp[e_name] + 1.5
+575                plt.title('Rho ' + e_name + r', tau\_exp=' + str(np.around(self.tau_exp[e_name], decimals=2)))
+576            else:
+577                xmax = max(10.5, 2 * self.e_windowsize[e_name] - 0.5)
+578                plt.title('Rho ' + e_name + ', S=' + str(np.around(self.S[e_name], decimals=2)))
+579            plt.plot([-0.5, xmax], [0, 0], 'k--', lw=1)
+580            plt.xlim(-0.5, xmax)
+581            plt.draw()
+582            if save:
+583                fig.savefig(save + "_" + str(e))
 584
-585        Parameters
-586        ----------
-587        expand : bool
-588            show expanded history for irregular Monte Carlo chains (default: True).
-589        """
-590        for e, e_name in enumerate(self.mc_names):
-591            plt.figure()
-592            r_length = []
-593            tmp = []
-594            tmp_expanded = []
-595            for r, r_name in enumerate(self.e_content[e_name]):
-596                tmp.append(self.deltas[r_name] + self.r_values[r_name])
-597                if expand:
-598                    tmp_expanded.append(_expand_deltas(self.deltas[r_name], list(self.idl[r_name]), self.shape[r_name], 1) + self.r_values[r_name])
-599                    r_length.append(len(tmp_expanded[-1]))
-600                else:
-601                    r_length.append(len(tmp[-1]))
-602            e_N = np.sum(r_length)
-603            x = np.arange(e_N)
-604            y_test = np.concatenate(tmp, axis=0)
-605            if expand:
-606                y = np.concatenate(tmp_expanded, axis=0)
-607            else:
-608                y = y_test
-609            plt.errorbar(x, y, fmt='.', markersize=3)
-610            plt.xlim(-0.5, e_N - 0.5)
-611            plt.title(e_name + f'\nskew: {skew(y_test):.3f} (p={skewtest(y_test).pvalue:.3f}), kurtosis: {kurtosis(y_test):.3f} (p={kurtosistest(y_test).pvalue:.3f})')
-612            plt.draw()
-613
-614    def plot_piechart(self, save=None):
-615        """Plot piechart which shows the fractional contribution of each
-616        ensemble to the error and returns a dictionary containing the fractions.
-617
-618        Parameters
-619        ----------
-620        save : str
-621            saves the figure to a file named 'save' if.
-622        """
-623        if not hasattr(self, 'e_dvalue'):
-624            raise Exception('Run the gamma method first.')
-625        if np.isclose(0.0, self._dvalue, atol=1e-15):
-626            raise ValueError('Error is 0.0')
-627        labels = self.e_names
-628        sizes = [self.e_dvalue[name] ** 2 for name in labels] / self._dvalue ** 2
-629        fig1, ax1 = plt.subplots()
-630        ax1.pie(sizes, labels=labels, startangle=90, normalize=True)
-631        ax1.axis('equal')
-632        plt.draw()
-633        if save:
-634            fig1.savefig(save)
-635
-636        return dict(zip(labels, sizes))
-637
-638    def dump(self, filename, datatype="json.gz", description="", **kwargs):
-639        """Dump the Obs to a file 'name' of chosen format.
-640
-641        Parameters
-642        ----------
-643        filename : str
-644            name of the file to be saved.
-645        datatype : str
-646            Format of the exported file. Supported formats include
-647            "json.gz" and "pickle"
-648        description : str
-649            Description for output file, only relevant for json.gz format.
-650        path : str
-651            specifies a custom path for the file (default '.')
-652        """
-653        if 'path' in kwargs:
-654            file_name = kwargs.get('path') + '/' + filename
-655        else:
-656            file_name = filename
-657
-658        if datatype == "json.gz":
-659            from .input.json import dump_to_json
-660            dump_to_json([self], file_name, description=description)
-661        elif datatype == "pickle":
-662            with open(file_name + '.p', 'wb') as fb:
-663                pickle.dump(self, fb)
-664        else:
-665            raise TypeError("Unknown datatype " + str(datatype))
-666
-667    def export_jackknife(self):
-668        """Export jackknife samples from the Obs
-669
-670        Returns
-671        -------
-672        numpy.ndarray
-673            Returns a numpy array of length N + 1 where N is the number of samples
-674            for the given ensemble and replicum. The zeroth entry of the array contains
-675            the mean value of the Obs, entries 1 to N contain the N jackknife samples
-676            derived from the Obs. The current implementation only works for observables
-677            defined on exactly one ensemble and replicum. The derived jackknife samples
-678            should agree with samples from a full jackknife analysis up to O(1/N).
-679        """
-680
-681        if len(self.names) != 1:
-682            raise ValueError("'export_jackknife' is only implemented for Obs defined on one ensemble and replicum.")
-683
-684        name = self.names[0]
-685        full_data = self.deltas[name] + self.r_values[name]
-686        n = full_data.size
-687        mean = self.value
-688        tmp_jacks = np.zeros(n + 1)
-689        tmp_jacks[0] = mean
-690        tmp_jacks[1:] = (n * mean - full_data) / (n - 1)
-691        return tmp_jacks
-692
-693    def export_bootstrap(self, samples=500, random_numbers=None, save_rng=None):
-694        """Export bootstrap samples from the Obs
-695
-696        Parameters
-697        ----------
-698        samples : int
-699            Number of bootstrap samples to generate.
-700        random_numbers : np.ndarray
-701            Array of shape (samples, length) containing the random numbers to generate the bootstrap samples.
-702            If not provided the bootstrap samples are generated bashed on the md5 hash of the enesmble name.
-703        save_rng : str
-704            Save the random numbers to a file if a path is specified.
+585    def plot_rep_dist(self):
+586        """Plot replica distribution for each ensemble with more than one replicum."""
+587        if not hasattr(self, 'e_dvalue'):
+588            raise Exception('Run the gamma method first.')
+589        for _e, e_name in enumerate(self.mc_names):
+590            if len(self.e_content[e_name]) == 1:
+591                print('No replica distribution for a single replicum (', e_name, ')')
+592                continue
+593            r_length = []
+594            sub_r_mean = 0
+595            for r_name in self.e_content[e_name]:
+596                r_length.append(len(self.deltas[r_name]))
+597                sub_r_mean += self.shape[r_name] * self.r_values[r_name]
+598            e_N = np.sum(r_length)
+599            sub_r_mean /= e_N
+600            arr = np.zeros(len(self.e_content[e_name]))
+601            for r, r_name in enumerate(self.e_content[e_name]):
+602                arr[r] = (self.r_values[r_name] - sub_r_mean) / (self.e_dvalue[e_name] * np.sqrt(e_N / self.shape[r_name] - 1))
+603            plt.hist(arr, rwidth=0.8, bins=len(self.e_content[e_name]))
+604            plt.title('Replica distribution' + e_name + ' (mean=0, var=1)')
+605            plt.draw()
+606
+607    def plot_history(self, expand=True):
+608        """Plot derived Monte Carlo history for each ensemble
+609
+610        Parameters
+611        ----------
+612        expand : bool
+613            show expanded history for irregular Monte Carlo chains (default: True).
+614        """
+615        for _e, e_name in enumerate(self.mc_names):
+616            plt.figure()
+617            r_length = []
+618            tmp = []
+619            tmp_expanded = []
+620            for _r, r_name in enumerate(self.e_content[e_name]):
+621                tmp.append(self.deltas[r_name] + self.r_values[r_name])
+622                if expand:
+623                    tmp_expanded.append(_expand_deltas(self.deltas[r_name], list(self.idl[r_name]), self.shape[r_name], 1) + self.r_values[r_name])
+624                    r_length.append(len(tmp_expanded[-1]))
+625                else:
+626                    r_length.append(len(tmp[-1]))
+627            e_N = np.sum(r_length)
+628            x = np.arange(e_N)
+629            y_test = np.concatenate(tmp, axis=0)
+630            if expand:
+631                y = np.concatenate(tmp_expanded, axis=0)
+632            else:
+633                y = y_test
+634            plt.errorbar(x, y, fmt='.', markersize=3)
+635            plt.xlim(-0.5, e_N - 0.5)
+636            plt.title(e_name + f'\nskew: {skew(y_test):.3f} (p={skewtest(y_test).pvalue:.3f}), kurtosis: {kurtosis(y_test):.3f} (p={kurtosistest(y_test).pvalue:.3f})')
+637            plt.draw()
+638
+639    def plot_piechart(self, save=None):
+640        """Plot piechart which shows the fractional contribution of each
+641        ensemble to the error and returns a dictionary containing the fractions.
+642
+643        Parameters
+644        ----------
+645        save : str
+646            saves the figure to a file named 'save' if.
+647        """
+648        if not hasattr(self, 'e_dvalue'):
+649            raise Exception('Run the gamma method first.')
+650        if np.isclose(0.0, self._dvalue, atol=1e-15):
+651            raise ValueError('Error is 0.0')
+652        labels = self.e_names
+653        sizes = [self.e_dvalue[name] ** 2 for name in labels] / self._dvalue ** 2
+654        fig1, ax1 = plt.subplots()
+655        ax1.pie(sizes, labels=labels, startangle=90, normalize=True)
+656        ax1.axis('equal')
+657        plt.draw()
+658        if save:
+659            fig1.savefig(save)
+660
+661        return dict(zip(labels, sizes, strict=True))
+662
+663    def dump(self, filename, datatype="json.gz", description="", **kwargs):
+664        """Dump the Obs to a file 'name' of chosen format.
+665
+666        Parameters
+667        ----------
+668        filename : str
+669            name of the file to be saved.
+670        datatype : str
+671            Format of the exported file. Supported formats include
+672            "json.gz" and "pickle"
+673        description : str
+674            Description for output file, only relevant for json.gz format.
+675        path : str
+676            specifies a custom path for the file (default '.')
+677        """
+678        if 'path' in kwargs:
+679            file_name = kwargs.get('path') + '/' + filename
+680        else:
+681            file_name = filename
+682
+683        if datatype == "json.gz":
+684            from .input.json import dump_to_json
+685            dump_to_json([self], file_name, description=description)
+686        elif datatype == "pickle":
+687            with open(file_name + '.p', 'wb') as fb:
+688                pickle.dump(self, fb)
+689        else:
+690            raise TypeError("Unknown datatype " + str(datatype))
+691
+692    def export_jackknife(self):
+693        """Export jackknife samples from the Obs
+694
+695        Returns
+696        -------
+697        numpy.ndarray
+698            Returns a numpy array of length N + 1 where N is the number of samples
+699            for the given ensemble and replicum. The zeroth entry of the array contains
+700            the mean value of the Obs, entries 1 to N contain the N jackknife samples
+701            derived from the Obs. The current implementation only works for observables
+702            defined on exactly one ensemble and replicum. The derived jackknife samples
+703            should agree with samples from a full jackknife analysis up to O(1/N).
+704        """
 705
-706        Returns
-707        -------
-708        numpy.ndarray
-709            Returns a numpy array of length N + 1 where N is the number of samples
-710            for the given ensemble and replicum. The zeroth entry of the array contains
-711            the mean value of the Obs, entries 1 to N contain the N import_bootstrap samples
-712            derived from the Obs. The current implementation only works for observables
-713            defined on exactly one ensemble and replicum. The derived bootstrap samples
-714            should agree with samples from a full bootstrap analysis up to O(1/N).
-715        """
-716        if len(self.names) != 1:
-717            raise ValueError("'export_boostrap' is only implemented for Obs defined on one ensemble and replicum.")
-718
-719        name = self.names[0]
-720        length = self.N
-721
-722        if random_numbers is None:
-723            seed = int(hashlib.md5(name.encode()).hexdigest(), 16) & 0xFFFFFFFF
-724            rng = np.random.default_rng(seed)
-725            random_numbers = rng.integers(0, length, size=(samples, length))
-726
-727        if save_rng is not None:
-728            np.savetxt(save_rng, random_numbers, fmt='%i')
-729
-730        proj = np.vstack([np.bincount(o, minlength=length) for o in random_numbers]) / length
-731        ret = np.zeros(samples + 1)
-732        ret[0] = self.value
-733        ret[1:] = proj @ (self.deltas[name] + self.r_values[name])
-734        return ret
-735
-736    def __float__(self):
-737        return float(self.value)
-738
-739    def __repr__(self):
-740        return 'Obs[' + str(self) + ']'
-741
-742    def __str__(self):
-743        return _format_uncertainty(self.value, self._dvalue)
-744
-745    def __format__(self, format_type):
-746        if format_type == "":
-747            significance = 2
-748        else:
-749            significance = int(float(format_type.replace("+", "").replace("-", "")))
-750        my_str = _format_uncertainty(self.value, self._dvalue,
-751                                     significance=significance)
-752        for char in ["+", " "]:
-753            if format_type.startswith(char):
-754                if my_str[0] != "-":
-755                    my_str = char + my_str
-756        return my_str
-757
-758    def __hash__(self):
-759        hash_tuple = (np.array([self.value]).astype(np.float32).data.tobytes(),)
-760        hash_tuple += tuple([o.astype(np.float32).data.tobytes() for o in self.deltas.values()])
-761        hash_tuple += tuple([np.array([o.errsq()]).astype(np.float32).data.tobytes() for o in self.covobs.values()])
-762        hash_tuple += tuple([o.encode() for o in self.names])
-763        m = hashlib.md5()
-764        [m.update(o) for o in hash_tuple]
-765        return int(m.hexdigest(), 16) & 0xFFFFFFFF
+706        if len(self.names) != 1:
+707            raise ValueError("'export_jackknife' is only implemented for Obs defined on one ensemble and replicum.")
+708
+709        name = self.names[0]
+710        full_data = self.deltas[name] + self.r_values[name]
+711        n = full_data.size
+712        mean = self.value
+713        tmp_jacks = np.zeros(n + 1)
+714        tmp_jacks[0] = mean
+715        tmp_jacks[1:] = (n * mean - full_data) / (n - 1)
+716        return tmp_jacks
+717
+718    def export_bootstrap(self, samples=500, random_numbers=None, save_rng=None):
+719        """Export bootstrap samples from the Obs
+720
+721        Parameters
+722        ----------
+723        samples : int
+724            Number of bootstrap samples to generate.
+725        random_numbers : np.ndarray
+726            Array of shape (samples, length) containing the random numbers to generate the bootstrap samples.
+727            If not provided the bootstrap samples are generated bashed on the md5 hash of the enesmble name.
+728        save_rng : str
+729            Save the random numbers to a file if a path is specified.
+730
+731        Returns
+732        -------
+733        numpy.ndarray
+734            Returns a numpy array of length N + 1 where N is the number of samples
+735            for the given ensemble and replicum. The zeroth entry of the array contains
+736            the mean value of the Obs, entries 1 to N contain the N import_bootstrap samples
+737            derived from the Obs. The current implementation only works for observables
+738            defined on exactly one ensemble and replicum. The derived bootstrap samples
+739            should agree with samples from a full bootstrap analysis up to O(1/N).
+740        """
+741        if len(self.names) != 1:
+742            raise ValueError("'export_boostrap' is only implemented for Obs defined on one ensemble and replicum.")
+743
+744        name = self.names[0]
+745        length = self.N
+746
+747        if random_numbers is None:
+748            seed = int(hashlib.md5(name.encode()).hexdigest(), 16) & 0xFFFFFFFF
+749            rng = np.random.default_rng(seed)
+750            random_numbers = rng.integers(0, length, size=(samples, length))
+751
+752        if save_rng is not None:
+753            np.savetxt(save_rng, random_numbers, fmt='%i')
+754
+755        proj = np.vstack([np.bincount(o, minlength=length) for o in random_numbers]) / length
+756        ret = np.zeros(samples + 1)
+757        ret[0] = self.value
+758        ret[1:] = proj @ (self.deltas[name] + self.r_values[name])
+759        return ret
+760
+761    def __float__(self):
+762        return float(self.value)
+763
+764    def __repr__(self):
+765        return 'Obs[' + str(self) + ']'
 766
-767    # Overload comparisons
-768    def __lt__(self, other):
-769        return self.value < other
-770
-771    def __le__(self, other):
-772        return self.value <= other
-773
-774    def __gt__(self, other):
-775        return self.value > other
-776
-777    def __ge__(self, other):
-778        return self.value >= other
-779
-780    def __eq__(self, other):
-781        if other is None:
-782            return False
-783        return (self - other).is_zero()
-784
-785    # Overload math operations
-786    def __add__(self, y):
-787        if isinstance(y, Obs):
-788            return derived_observable(lambda x, **kwargs: x[0] + x[1], [self, y], man_grad=[1, 1])
-789        else:
-790            if isinstance(y, np.ndarray):
-791                return np.array([self + o for o in y])
-792            elif isinstance(y, complex):
-793                return CObs(self, 0) + y
-794            elif y.__class__.__name__ in ['Corr', 'CObs']:
-795                return NotImplemented
-796            else:
-797                return derived_observable(lambda x, **kwargs: x[0] + y, [self], man_grad=[1])
+767    def __str__(self):
+768        return _format_uncertainty(self.value, self._dvalue)
+769
+770    def __format__(self, format_type):
+771        if format_type == "":
+772            significance = 2
+773        else:
+774            significance = int(float(format_type.replace("+", "").replace("-", "")))
+775        my_str = _format_uncertainty(self.value, self._dvalue,
+776                                     significance=significance)
+777        for char in ["+", " "]:
+778            if format_type.startswith(char):
+779                if my_str[0] != "-":
+780                    my_str = char + my_str
+781        return my_str
+782
+783    def __hash__(self):
+784        hash_tuple = (np.array([self.value]).astype(np.float32).data.tobytes(),)
+785        hash_tuple += tuple([o.astype(np.float32).data.tobytes() for o in self.deltas.values()])
+786        hash_tuple += tuple([np.array([o.errsq()]).astype(np.float32).data.tobytes() for o in self.covobs.values()])
+787        hash_tuple += tuple([o.encode() for o in self.names])
+788        m = hashlib.md5()
+789        [m.update(o) for o in hash_tuple]
+790        return int(m.hexdigest(), 16) & 0xFFFFFFFF
+791
+792    # Overload comparisons
+793    def __lt__(self, other):
+794        return self.value < other
+795
+796    def __le__(self, other):
+797        return self.value <= other
 798
-799    def __radd__(self, y):
-800        return self + y
+799    def __gt__(self, other):
+800        return self.value > other
 801
-802    def __mul__(self, y):
-803        if isinstance(y, Obs):
-804            return derived_observable(lambda x, **kwargs: x[0] * x[1], [self, y], man_grad=[y.value, self.value])
-805        else:
-806            if isinstance(y, np.ndarray):
-807                return np.array([self * o for o in y])
-808            elif isinstance(y, complex):
-809                return CObs(self * y.real, self * y.imag)
-810            elif y.__class__.__name__ in ['Corr', 'CObs']:
-811                return NotImplemented
-812            else:
-813                return derived_observable(lambda x, **kwargs: x[0] * y, [self], man_grad=[y])
-814
-815    def __rmul__(self, y):
-816        return self * y
-817
-818    def __sub__(self, y):
-819        if isinstance(y, Obs):
-820            return derived_observable(lambda x, **kwargs: x[0] - x[1], [self, y], man_grad=[1, -1])
-821        else:
-822            if isinstance(y, np.ndarray):
-823                return np.array([self - o for o in y])
-824            elif y.__class__.__name__ in ['Corr', 'CObs']:
-825                return NotImplemented
-826            else:
-827                return derived_observable(lambda x, **kwargs: x[0] - y, [self], man_grad=[1])
-828
-829    def __rsub__(self, y):
-830        return -1 * (self - y)
-831
-832    def __pos__(self):
-833        return self
-834
-835    def __neg__(self):
-836        return -1 * self
-837
-838    def __truediv__(self, y):
-839        if isinstance(y, Obs):
-840            return derived_observable(lambda x, **kwargs: x[0] / x[1], [self, y], man_grad=[1 / y.value, - self.value / y.value ** 2])
-841        else:
-842            if isinstance(y, np.ndarray):
-843                return np.array([self / o for o in y])
-844            elif y.__class__.__name__ in ['Corr', 'CObs']:
-845                return NotImplemented
-846            else:
-847                return derived_observable(lambda x, **kwargs: x[0] / y, [self], man_grad=[1 / y])
-848
-849    def __rtruediv__(self, y):
-850        if isinstance(y, Obs):
-851            return derived_observable(lambda x, **kwargs: x[0] / x[1], [y, self], man_grad=[1 / self.value, - y.value / self.value ** 2])
-852        else:
-853            if isinstance(y, np.ndarray):
-854                return np.array([o / self for o in y])
-855            elif y.__class__.__name__ in ['Corr', 'CObs']:
-856                return NotImplemented
-857            else:
-858                return derived_observable(lambda x, **kwargs: y / x[0], [self], man_grad=[-y / self.value ** 2])
+802    def __ge__(self, other):
+803        return self.value >= other
+804
+805    def __eq__(self, other):
+806        if other is None:
+807            return False
+808        return (self - other).is_zero()
+809
+810    # Overload math operations
+811    def __add__(self, y):
+812        if isinstance(y, Obs):
+813            return derived_observable(lambda x, **kwargs: x[0] + x[1], [self, y], man_grad=[1, 1])
+814        else:
+815            if isinstance(y, np.ndarray):
+816                return np.array([self + o for o in y])
+817            elif isinstance(y, complex):
+818                return CObs(self, 0) + y
+819            elif y.__class__.__name__ in ['Corr', 'CObs']:
+820                return NotImplemented
+821            else:
+822                return derived_observable(lambda x, **kwargs: x[0] + y, [self], man_grad=[1])
+823
+824    def __radd__(self, y):
+825        return self + y
+826
+827    def __mul__(self, y):
+828        if isinstance(y, Obs):
+829            return derived_observable(lambda x, **kwargs: x[0] * x[1], [self, y], man_grad=[y.value, self.value])
+830        else:
+831            if isinstance(y, np.ndarray):
+832                return np.array([self * o for o in y])
+833            elif isinstance(y, complex):
+834                return CObs(self * y.real, self * y.imag)
+835            elif y.__class__.__name__ in ['Corr', 'CObs']:
+836                return NotImplemented
+837            else:
+838                return derived_observable(lambda x, **kwargs: x[0] * y, [self], man_grad=[y])
+839
+840    def __rmul__(self, y):
+841        return self * y
+842
+843    def __sub__(self, y):
+844        if isinstance(y, Obs):
+845            return derived_observable(lambda x, **kwargs: x[0] - x[1], [self, y], man_grad=[1, -1])
+846        else:
+847            if isinstance(y, np.ndarray):
+848                return np.array([self - o for o in y])
+849            elif y.__class__.__name__ in ['Corr', 'CObs']:
+850                return NotImplemented
+851            else:
+852                return derived_observable(lambda x, **kwargs: x[0] - y, [self], man_grad=[1])
+853
+854    def __rsub__(self, y):
+855        return -1 * (self - y)
+856
+857    def __pos__(self):
+858        return self
 859
-860    def __pow__(self, y):
-861        if isinstance(y, Obs):
-862            return derived_observable(lambda x, **kwargs: x[0] ** x[1], [self, y], man_grad=[y.value * self.value ** (y.value - 1), self.value ** y.value * np.log(self.value)])
-863        else:
-864            return derived_observable(lambda x, **kwargs: x[0] ** y, [self], man_grad=[y * self.value ** (y - 1)])
-865
-866    def __rpow__(self, y):
-867        return derived_observable(lambda x, **kwargs: y ** x[0], [self], man_grad=[y ** self.value * np.log(y)])
-868
-869    def __abs__(self):
-870        return derived_observable(lambda x: anp.abs(x[0]), [self])
-871
-872    # Overload numpy functions
-873    def sqrt(self):
-874        return derived_observable(lambda x, **kwargs: np.sqrt(x[0]), [self], man_grad=[1 / 2 / np.sqrt(self.value)])
-875
-876    def log(self):
-877        return derived_observable(lambda x, **kwargs: np.log(x[0]), [self], man_grad=[1 / self.value])
-878
-879    def exp(self):
-880        return derived_observable(lambda x, **kwargs: np.exp(x[0]), [self], man_grad=[np.exp(self.value)])
-881
-882    def sin(self):
-883        return derived_observable(lambda x, **kwargs: np.sin(x[0]), [self], man_grad=[np.cos(self.value)])
+860    def __neg__(self):
+861        return -1 * self
+862
+863    def __truediv__(self, y):
+864        if isinstance(y, Obs):
+865            return derived_observable(lambda x, **kwargs: x[0] / x[1], [self, y], man_grad=[1 / y.value, - self.value / y.value ** 2])
+866        else:
+867            if isinstance(y, np.ndarray):
+868                return np.array([self / o for o in y])
+869            elif y.__class__.__name__ in ['Corr', 'CObs']:
+870                return NotImplemented
+871            else:
+872                return derived_observable(lambda x, **kwargs: x[0] / y, [self], man_grad=[1 / y])
+873
+874    def __rtruediv__(self, y):
+875        if isinstance(y, Obs):
+876            return derived_observable(lambda x, **kwargs: x[0] / x[1], [y, self], man_grad=[1 / self.value, - y.value / self.value ** 2])
+877        else:
+878            if isinstance(y, np.ndarray):
+879                return np.array([o / self for o in y])
+880            elif y.__class__.__name__ in ['Corr', 'CObs']:
+881                return NotImplemented
+882            else:
+883                return derived_observable(lambda x, **kwargs: y / x[0], [self], man_grad=[-y / self.value ** 2])
 884
-885    def cos(self):
-886        return derived_observable(lambda x, **kwargs: np.cos(x[0]), [self], man_grad=[-np.sin(self.value)])
-887
-888    def tan(self):
-889        return derived_observable(lambda x, **kwargs: np.tan(x[0]), [self], man_grad=[1 / np.cos(self.value) ** 2])
+885    def __pow__(self, y):
+886        if isinstance(y, Obs):
+887            return derived_observable(lambda x, **kwargs: x[0] ** x[1], [self, y], man_grad=[y.value * self.value ** (y.value - 1), self.value ** y.value * np.log(self.value)])
+888        else:
+889            return derived_observable(lambda x, **kwargs: x[0] ** y, [self], man_grad=[y * self.value ** (y - 1)])
 890
-891    def arcsin(self):
-892        return derived_observable(lambda x: anp.arcsin(x[0]), [self])
+891    def __rpow__(self, y):
+892        return derived_observable(lambda x, **kwargs: y ** x[0], [self], man_grad=[y ** self.value * np.log(y)])
 893
-894    def arccos(self):
-895        return derived_observable(lambda x: anp.arccos(x[0]), [self])
+894    def __abs__(self):
+895        return derived_observable(lambda x: anp.abs(x[0]), [self])
 896
-897    def arctan(self):
-898        return derived_observable(lambda x: anp.arctan(x[0]), [self])
-899
-900    def sinh(self):
-901        return derived_observable(lambda x, **kwargs: np.sinh(x[0]), [self], man_grad=[np.cosh(self.value)])
-902
-903    def cosh(self):
-904        return derived_observable(lambda x, **kwargs: np.cosh(x[0]), [self], man_grad=[np.sinh(self.value)])
-905
-906    def tanh(self):
-907        return derived_observable(lambda x, **kwargs: np.tanh(x[0]), [self], man_grad=[1 / np.cosh(self.value) ** 2])
-908
-909    def arcsinh(self):
-910        return derived_observable(lambda x: anp.arcsinh(x[0]), [self])
-911
-912    def arccosh(self):
-913        return derived_observable(lambda x: anp.arccosh(x[0]), [self])
-914
-915    def arctanh(self):
-916        return derived_observable(lambda x: anp.arctanh(x[0]), [self])
+897    # Overload numpy functions
+898    def sqrt(self):
+899        return derived_observable(lambda x, **kwargs: np.sqrt(x[0]), [self], man_grad=[1 / 2 / np.sqrt(self.value)])
+900
+901    def log(self):
+902        return derived_observable(lambda x, **kwargs: np.log(x[0]), [self], man_grad=[1 / self.value])
+903
+904    def exp(self):
+905        return derived_observable(lambda x, **kwargs: np.exp(x[0]), [self], man_grad=[np.exp(self.value)])
+906
+907    def sin(self):
+908        return derived_observable(lambda x, **kwargs: np.sin(x[0]), [self], man_grad=[np.cos(self.value)])
+909
+910    def cos(self):
+911        return derived_observable(lambda x, **kwargs: np.cos(x[0]), [self], man_grad=[-np.sin(self.value)])
+912
+913    def tan(self):
+914        return derived_observable(lambda x, **kwargs: np.tan(x[0]), [self], man_grad=[1 / np.cos(self.value) ** 2])
+915
+916    def arcsin(self):
+917        return derived_observable(lambda x: anp.arcsin(x[0]), [self])
+918
+919    def arccos(self):
+920        return derived_observable(lambda x: anp.arccos(x[0]), [self])
+921
+922    def arctan(self):
+923        return derived_observable(lambda x: anp.arctan(x[0]), [self])
+924
+925    def sinh(self):
+926        return derived_observable(lambda x, **kwargs: np.sinh(x[0]), [self], man_grad=[np.cosh(self.value)])
+927
+928    def cosh(self):
+929        return derived_observable(lambda x, **kwargs: np.cosh(x[0]), [self], man_grad=[np.sinh(self.value)])
+930
+931    def tanh(self):
+932        return derived_observable(lambda x, **kwargs: np.tanh(x[0]), [self], man_grad=[1 / np.cosh(self.value) ** 2])
+933
+934    def arcsinh(self):
+935        return derived_observable(lambda x: anp.arcsinh(x[0]), [self])
+936
+937    def arccosh(self):
+938        return derived_observable(lambda x: anp.arccosh(x[0]), [self])
+939
+940    def arctanh(self):
+941        return derived_observable(lambda x: anp.arctanh(x[0]), [self])
 
@@ -3160,90 +3209,90 @@ this overwrites the standard value for that ensemble.
-
 61    def __init__(self, samples, names, idl=None, **kwargs):
- 62        """ Initialize Obs object.
- 63
- 64        Parameters
- 65        ----------
- 66        samples : list
- 67            list of numpy arrays containing the Monte Carlo samples
- 68        names : list
- 69            list of strings labeling the individual samples
- 70        idl : list, optional
- 71            list of ranges or lists on which the samples are defined
- 72        """
- 73
- 74        if kwargs.get("means") is None and len(samples):
- 75            if len(samples) != len(names):
- 76                raise ValueError('Length of samples and names incompatible.')
- 77            if idl is not None:
- 78                if len(idl) != len(names):
- 79                    raise ValueError('Length of idl incompatible with samples and names.')
- 80            name_length = len(names)
- 81            if name_length > 1:
- 82                if name_length != len(set(names)):
- 83                    raise ValueError('Names are not unique.')
- 84                if not all(isinstance(x, str) for x in names):
- 85                    raise TypeError('All names have to be strings.')
- 86                if len(set([o.split('|')[0] for o in names])) > 1:
- 87                    raise ValueError('Cannot initialize Obs based on multiple ensembles. Please average separate Obs from each ensemble.')
- 88            else:
- 89                if not isinstance(names[0], str):
- 90                    raise TypeError('All names have to be strings.')
- 91            if min(len(x) for x in samples) <= 4:
- 92                raise ValueError('Samples have to have at least 5 entries.')
- 93
- 94        self.names = sorted(names)
- 95        self.shape = {}
- 96        self.r_values = {}
- 97        self.deltas = {}
- 98        self._covobs = {}
- 99
-100        self._value = 0
-101        self.N = 0
-102        self.idl = {}
-103        if idl is not None:
-104            for name, idx in sorted(zip(names, idl)):
-105                if isinstance(idx, range):
-106                    self.idl[name] = idx
-107                elif isinstance(idx, (list, np.ndarray)):
-108                    dc = np.unique(np.diff(idx))
-109                    if np.any(dc < 0):
-110                        raise ValueError("Unsorted idx for idl[%s] at position %s" % (name, ' '.join(['%s' % (pos + 1) for pos in np.where(np.diff(idx) < 0)[0]])))
-111                    elif np.any(dc == 0):
-112                        raise ValueError("Duplicate entries in idx for idl[%s] at position %s" % (name, ' '.join(['%s' % (pos + 1) for pos in np.where(np.diff(idx) == 0)[0]])))
-113                    if len(dc) == 1:
-114                        self.idl[name] = range(idx[0], idx[-1] + dc[0], dc[0])
-115                    else:
-116                        self.idl[name] = list(idx)
-117                else:
-118                    raise TypeError('incompatible type for idl[%s].' % (name))
-119        else:
-120            for name, sample in sorted(zip(names, samples)):
-121                self.idl[name] = range(1, len(sample) + 1)
-122
-123        if kwargs.get("means") is not None:
-124            for name, sample, mean in sorted(zip(names, samples, kwargs.get("means"))):
-125                self.shape[name] = len(self.idl[name])
-126                self.N += self.shape[name]
-127                self.r_values[name] = mean
-128                self.deltas[name] = sample
-129        else:
-130            for name, sample in sorted(zip(names, samples)):
-131                self.shape[name] = len(self.idl[name])
-132                self.N += self.shape[name]
-133                if len(sample) != self.shape[name]:
-134                    raise ValueError('Incompatible samples and idx for %s: %d vs. %d' % (name, len(sample), self.shape[name]))
-135                self.r_values[name] = np.mean(sample)
-136                self.deltas[name] = sample - self.r_values[name]
-137                self._value += self.shape[name] * self.r_values[name]
-138            self._value /= self.N
-139
-140        self._dvalue = 0.0
-141        self.ddvalue = 0.0
-142        self.reweighted = False
-143
-144        self.tag = None
+            
 86    def __init__(self, samples, names, idl=None, **kwargs):
+ 87        """ Initialize Obs object.
+ 88
+ 89        Parameters
+ 90        ----------
+ 91        samples : list
+ 92            list of numpy arrays containing the Monte Carlo samples
+ 93        names : list
+ 94            list of strings labeling the individual samples
+ 95        idl : list, optional
+ 96            list of ranges or lists on which the samples are defined
+ 97        """
+ 98
+ 99        if kwargs.get("means") is None and len(samples):
+100            if len(samples) != len(names):
+101                raise ValueError('Length of samples and names incompatible.')
+102            if idl is not None:
+103                if len(idl) != len(names):
+104                    raise ValueError('Length of idl incompatible with samples and names.')
+105            name_length = len(names)
+106            if name_length > 1:
+107                if name_length != len(set(names)):
+108                    raise ValueError('Names are not unique.')
+109                if not all(isinstance(x, str) for x in names):
+110                    raise TypeError('All names have to be strings.')
+111                if len(set([o.split('|')[0] for o in names])) > 1:
+112                    raise ValueError('Cannot initialize Obs based on multiple ensembles. Please average separate Obs from each ensemble.')
+113            else:
+114                if not isinstance(names[0], str):
+115                    raise TypeError('All names have to be strings.')
+116            if min(len(x) for x in samples) <= 4:
+117                raise ValueError('Samples have to have at least 5 entries.')
+118
+119        self.names = sorted(names)
+120        self.shape = {}
+121        self.r_values = {}
+122        self.deltas = {}
+123        self._covobs = {}
+124
+125        self._value = 0
+126        self.N = 0
+127        self.idl = {}
+128        if idl is not None:
+129            for name, idx in sorted(zip(names, idl, strict=True)):
+130                if isinstance(idx, range):
+131                    self.idl[name] = idx
+132                elif isinstance(idx, (list, np.ndarray)):
+133                    dc = np.unique(np.diff(idx))
+134                    if np.any(dc < 0):
+135                        raise ValueError("Unsorted idx for idl[{}] at position {}".format(name, ' '.join(['%s' % (pos + 1) for pos in np.where(np.diff(idx) < 0)[0]])))
+136                    elif np.any(dc == 0):
+137                        raise ValueError("Duplicate entries in idx for idl[{}] at position {}".format(name, ' '.join(['%s' % (pos + 1) for pos in np.where(np.diff(idx) == 0)[0]])))
+138                    if len(dc) == 1:
+139                        self.idl[name] = range(idx[0], idx[-1] + dc[0], dc[0])
+140                    else:
+141                        self.idl[name] = list(idx)
+142                else:
+143                    raise TypeError(f'incompatible type for idl[{name}].')
+144        else:
+145            for name, sample in sorted(zip(names, samples, strict=True)):
+146                self.idl[name] = range(1, len(sample) + 1)
+147
+148        if kwargs.get("means") is not None:
+149            for name, sample, mean in sorted(zip(names, samples, kwargs.get("means"), strict=True)):
+150                self.shape[name] = len(self.idl[name])
+151                self.N += self.shape[name]
+152                self.r_values[name] = mean
+153                self.deltas[name] = sample
+154        else:
+155            for name, sample in sorted(zip(names, samples, strict=True)):
+156                self.shape[name] = len(self.idl[name])
+157                self.N += self.shape[name]
+158                if len(sample) != self.shape[name]:
+159                    raise ValueError(f'Incompatible samples and idx for {name}: {len(sample)} vs. {self.shape[name]}')
+160                self.r_values[name] = np.mean(sample)
+161                self.deltas[name] = sample - self.r_values[name]
+162                self._value += self.shape[name] * self.r_values[name]
+163            self._value /= self.N
+164
+165        self._dvalue = 0.0
+166        self.ddvalue = 0.0
+167        self.reweighted = False
+168
+169        self.tag = None
 
@@ -3277,7 +3326,7 @@ list of ranges or lists on which the samples are defined
- S_dict = + S_dict: ClassVar[dict] = {} @@ -3301,7 +3350,7 @@ list of ranges or lists on which the samples are defined
- tau_exp_dict = + tau_exp_dict: ClassVar[dict] = {} @@ -3325,7 +3374,7 @@ list of ranges or lists on which the samples are defined
- N_sigma_dict = + N_sigma_dict: ClassVar[dict] = {} @@ -3443,9 +3492,9 @@ list of ranges or lists on which the samples are defined
-
146    @property
-147    def value(self):
-148        return self._value
+            
171    @property
+172    def value(self):
+173        return self._value
 
@@ -3461,9 +3510,9 @@ list of ranges or lists on which the samples are defined
-
150    @property
-151    def dvalue(self):
-152        return self._dvalue
+            
175    @property
+176    def dvalue(self):
+177        return self._dvalue
 
@@ -3479,9 +3528,9 @@ list of ranges or lists on which the samples are defined
-
154    @property
-155    def e_names(self):
-156        return sorted(set([o.split('|')[0] for o in self.names]))
+            
179    @property
+180    def e_names(self):
+181        return sorted(set([o.split('|')[0] for o in self.names]))
 
@@ -3497,9 +3546,9 @@ list of ranges or lists on which the samples are defined
-
158    @property
-159    def cov_names(self):
-160        return sorted(set([o for o in self.covobs.keys()]))
+            
183    @property
+184    def cov_names(self):
+185        return sorted(set([o for o in self.covobs.keys()]))
 
@@ -3515,9 +3564,9 @@ list of ranges or lists on which the samples are defined
-
162    @property
-163    def mc_names(self):
-164        return sorted(set([o.split('|')[0] for o in self.names if o not in self.cov_names]))
+            
187    @property
+188    def mc_names(self):
+189        return sorted(set([o.split('|')[0] for o in self.names if o not in self.cov_names]))
 
@@ -3533,14 +3582,14 @@ list of ranges or lists on which the samples are defined
-
166    @property
-167    def e_content(self):
-168        res = {}
-169        for e, e_name in enumerate(self.e_names):
-170            res[e_name] = sorted(filter(lambda x: x.startswith(e_name + '|'), self.names))
-171            if e_name in self.names:
-172                res[e_name].append(e_name)
-173        return res
+            
191    @property
+192    def e_content(self):
+193        res = {}
+194        for _e, e_name in enumerate(self.e_names):
+195            res[e_name] = sorted(filter(lambda x: x.startswith(e_name + '|'), self.names))
+196            if e_name in self.names:
+197                res[e_name].append(e_name)
+198        return res
 
@@ -3556,9 +3605,9 @@ list of ranges or lists on which the samples are defined
-
175    @property
-176    def covobs(self):
-177        return self._covobs
+            
200    @property
+201    def covobs(self):
+202        return self._covobs
 
@@ -3576,171 +3625,171 @@ list of ranges or lists on which the samples are defined
-
179    def gamma_method(self, **kwargs):
-180        """Estimate the error and related properties of the Obs.
-181
-182        Parameters
-183        ----------
-184        S : float
-185            specifies a custom value for the parameter S (default 2.0).
-186            If set to 0 it is assumed that the data exhibits no
-187            autocorrelation. In this case the error estimates coincides
-188            with the sample standard error.
-189        tau_exp : float
-190            positive value triggers the critical slowing down analysis
-191            (default 0.0).
-192        N_sigma : float
-193            number of standard deviations from zero until the tail is
-194            attached to the autocorrelation function (default 1).
-195        fft : bool
-196            determines whether the fft algorithm is used for the computation
-197            of the autocorrelation function (default True)
-198        """
-199
-200        e_content = self.e_content
-201        self.e_dvalue = {}
-202        self.e_ddvalue = {}
-203        self.e_tauint = {}
-204        self.e_dtauint = {}
-205        self.e_windowsize = {}
-206        self.e_n_tauint = {}
-207        self.e_n_dtauint = {}
-208        e_gamma = {}
-209        self.e_rho = {}
-210        self.e_drho = {}
-211        self._dvalue = 0
-212        self.ddvalue = 0
-213
-214        self.S = {}
-215        self.tau_exp = {}
-216        self.N_sigma = {}
-217
-218        if kwargs.get('fft') is False:
-219            fft = False
-220        else:
-221            fft = True
-222
-223        def _parse_kwarg(kwarg_name):
-224            if kwarg_name in kwargs:
-225                tmp = kwargs.get(kwarg_name)
-226                if isinstance(tmp, (int, float)):
-227                    if tmp < 0:
-228                        raise ValueError(kwarg_name + ' has to be larger or equal to 0.')
-229                    for e, e_name in enumerate(self.e_names):
-230                        getattr(self, kwarg_name)[e_name] = tmp
-231                else:
-232                    raise TypeError(kwarg_name + ' is not in proper format.')
-233            else:
-234                for e, e_name in enumerate(self.e_names):
-235                    if e_name in getattr(Obs, kwarg_name + '_dict'):
-236                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_dict')[e_name]
-237                    else:
-238                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_global')
-239
-240        _parse_kwarg('S')
-241        _parse_kwarg('tau_exp')
-242        _parse_kwarg('N_sigma')
-243
-244        for e, e_name in enumerate(self.mc_names):
-245            gapsize = _determine_gap(self, e_content, e_name)
-246
-247            r_length = []
-248            for r_name in e_content[e_name]:
-249                if isinstance(self.idl[r_name], range):
-250                    r_length.append(len(self.idl[r_name]) * self.idl[r_name].step // gapsize)
-251                else:
-252                    r_length.append((self.idl[r_name][-1] - self.idl[r_name][0] + 1) // gapsize)
-253
-254            e_N = np.sum([self.shape[r_name] for r_name in e_content[e_name]])
-255            w_max = max(r_length) // 2
-256            e_gamma[e_name] = np.zeros(w_max)
-257            self.e_rho[e_name] = np.zeros(w_max)
-258            self.e_drho[e_name] = np.zeros(w_max)
-259
-260            for r_name in e_content[e_name]:
-261                e_gamma[e_name] += self._calc_gamma(self.deltas[r_name], self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
-262
-263            gamma_div = np.zeros(w_max)
-264            for r_name in e_content[e_name]:
-265                gamma_div += self._calc_gamma(np.ones((self.shape[r_name])), self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
-266            gamma_div[gamma_div < 1] = 1.0
-267            e_gamma[e_name] /= gamma_div[:w_max]
+            
204    def gamma_method(self, **kwargs):
+205        """Estimate the error and related properties of the Obs.
+206
+207        Parameters
+208        ----------
+209        S : float
+210            specifies a custom value for the parameter S (default 2.0).
+211            If set to 0 it is assumed that the data exhibits no
+212            autocorrelation. In this case the error estimates coincides
+213            with the sample standard error.
+214        tau_exp : float
+215            positive value triggers the critical slowing down analysis
+216            (default 0.0).
+217        N_sigma : float
+218            number of standard deviations from zero until the tail is
+219            attached to the autocorrelation function (default 1).
+220        fft : bool
+221            determines whether the fft algorithm is used for the computation
+222            of the autocorrelation function (default True)
+223        """
+224
+225        e_content = self.e_content
+226        self.e_dvalue = {}
+227        self.e_ddvalue = {}
+228        self.e_tauint = {}
+229        self.e_dtauint = {}
+230        self.e_windowsize = {}
+231        self.e_n_tauint = {}
+232        self.e_n_dtauint = {}
+233        e_gamma = {}
+234        self.e_rho = {}
+235        self.e_drho = {}
+236        self._dvalue = 0
+237        self.ddvalue = 0
+238
+239        self.S = {}
+240        self.tau_exp = {}
+241        self.N_sigma = {}
+242
+243        if kwargs.get('fft') is False:
+244            fft = False
+245        else:
+246            fft = True
+247
+248        def _parse_kwarg(kwarg_name):
+249            if kwarg_name in kwargs:
+250                tmp = kwargs.get(kwarg_name)
+251                if isinstance(tmp, (int, float)):
+252                    if tmp < 0:
+253                        raise ValueError(kwarg_name + ' has to be larger or equal to 0.')
+254                    for _e, e_name in enumerate(self.e_names):
+255                        getattr(self, kwarg_name)[e_name] = tmp
+256                else:
+257                    raise TypeError(kwarg_name + ' is not in proper format.')
+258            else:
+259                for _e, e_name in enumerate(self.e_names):
+260                    if e_name in getattr(Obs, kwarg_name + '_dict'):
+261                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_dict')[e_name]
+262                    else:
+263                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_global')
+264
+265        _parse_kwarg('S')
+266        _parse_kwarg('tau_exp')
+267        _parse_kwarg('N_sigma')
 268
-269            if np.abs(e_gamma[e_name][0]) < 10 * np.finfo(float).tiny:  # Prevent division by zero
-270                self.e_tauint[e_name] = 0.5
-271                self.e_dtauint[e_name] = 0.0
-272                self.e_dvalue[e_name] = 0.0
-273                self.e_ddvalue[e_name] = 0.0
-274                self.e_windowsize[e_name] = 0
-275                continue
-276
-277            self.e_rho[e_name] = e_gamma[e_name][:w_max] / e_gamma[e_name][0]
-278            self.e_n_tauint[e_name] = np.cumsum(np.concatenate(([0.5], self.e_rho[e_name][1:])))
-279            # Make sure no entry of tauint is smaller than 0.5
-280            self.e_n_tauint[e_name][self.e_n_tauint[e_name] <= 0.5] = 0.5 + np.finfo(np.float64).eps
-281            # hep-lat/0306017 eq. (42)
-282            self.e_n_dtauint[e_name] = self.e_n_tauint[e_name] * 2 * np.sqrt(np.abs(np.arange(w_max) + 0.5 - self.e_n_tauint[e_name]) / e_N)
-283            self.e_n_dtauint[e_name][0] = 0.0
+269        for _e, e_name in enumerate(self.mc_names):
+270            gapsize = _determine_gap(self, e_content, e_name)
+271
+272            r_length = []
+273            for r_name in e_content[e_name]:
+274                if isinstance(self.idl[r_name], range):
+275                    r_length.append(len(self.idl[r_name]) * self.idl[r_name].step // gapsize)
+276                else:
+277                    r_length.append((self.idl[r_name][-1] - self.idl[r_name][0] + 1) // gapsize)
+278
+279            e_N = np.sum([self.shape[r_name] for r_name in e_content[e_name]])
+280            w_max = max(r_length) // 2
+281            e_gamma[e_name] = np.zeros(w_max)
+282            self.e_rho[e_name] = np.zeros(w_max)
+283            self.e_drho[e_name] = np.zeros(w_max)
 284
-285            def _compute_drho(i):
-286                tmp = (self.e_rho[e_name][i + 1:w_max]
-287                       + np.concatenate([self.e_rho[e_name][i - 1:None if i - (w_max - 1) // 2 <= 0 else (2 * i - (2 * w_max) // 2):-1],
-288                                         self.e_rho[e_name][1:max(1, w_max - 2 * i)]])
-289                       - 2 * self.e_rho[e_name][i] * self.e_rho[e_name][1:w_max - i])
-290                self.e_drho[e_name][i] = np.sqrt(np.sum(tmp ** 2) / e_N)
-291
-292            if self.tau_exp[e_name] > 0:
-293                _compute_drho(1)
-294                texp = self.tau_exp[e_name]
-295                # Critical slowing down analysis
-296                if w_max // 2 <= 1:
-297                    raise ValueError("Need at least 8 samples for tau_exp error analysis")
-298                for n in range(1, w_max // 2):
-299                    _compute_drho(n + 1)
-300                    if (self.e_rho[e_name][n] - self.N_sigma[e_name] * self.e_drho[e_name][n]) < 0 or n >= w_max // 2 - 2:
-301                        # Bias correction hep-lat/0306017 eq. (49) included
-302                        self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N) + texp * np.abs(self.e_rho[e_name][n + 1])  # The absolute makes sure, that the tail contribution is always positive
-303                        self.e_dtauint[e_name] = np.sqrt(self.e_n_dtauint[e_name][n] ** 2 + texp ** 2 * self.e_drho[e_name][n + 1] ** 2)
-304                        # Error of tau_exp neglected so far, missing term: self.e_rho[e_name][n + 1] ** 2 * d_tau_exp ** 2
-305                        self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
-306                        self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
-307                        self.e_windowsize[e_name] = n
-308                        break
-309            else:
-310                if self.S[e_name] == 0.0:
-311                    self.e_tauint[e_name] = 0.5
-312                    self.e_dtauint[e_name] = 0.0
-313                    self.e_dvalue[e_name] = np.sqrt(e_gamma[e_name][0] / (e_N - 1))
-314                    self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt(0.5 / e_N)
-315                    self.e_windowsize[e_name] = 0
-316                else:
-317                    # Standard automatic windowing procedure
-318                    tau = self.S[e_name] / np.log((2 * self.e_n_tauint[e_name][1:] + 1) / (2 * self.e_n_tauint[e_name][1:] - 1))
-319                    g_w = np.exp(- np.arange(1, len(tau) + 1) / tau) - tau / np.sqrt(np.arange(1, len(tau) + 1) * e_N)
-320                    for n in range(1, w_max):
-321                        if g_w[n - 1] < 0 or n >= w_max - 1:
-322                            _compute_drho(n)
-323                            self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N)  # Bias correction hep-lat/0306017 eq. (49)
-324                            self.e_dtauint[e_name] = self.e_n_dtauint[e_name][n]
-325                            self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
-326                            self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
-327                            self.e_windowsize[e_name] = n
-328                            break
-329
-330            self._dvalue += self.e_dvalue[e_name] ** 2
-331            self.ddvalue += (self.e_dvalue[e_name] * self.e_ddvalue[e_name]) ** 2
-332
-333        for e_name in self.cov_names:
-334            self.e_dvalue[e_name] = np.sqrt(self.covobs[e_name].errsq())
-335            self.e_ddvalue[e_name] = 0
-336            self._dvalue += self.e_dvalue[e_name]**2
-337
-338        self._dvalue = np.sqrt(self._dvalue)
-339        if self._dvalue == 0.0:
-340            self.ddvalue = 0.0
-341        else:
-342            self.ddvalue = np.sqrt(self.ddvalue) / self._dvalue
-343        return
+285            for r_name in e_content[e_name]:
+286                e_gamma[e_name] += self._calc_gamma(self.deltas[r_name], self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
+287
+288            gamma_div = np.zeros(w_max)
+289            for r_name in e_content[e_name]:
+290                gamma_div += self._calc_gamma(np.ones(self.shape[r_name]), self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
+291            gamma_div[gamma_div < 1] = 1.0
+292            e_gamma[e_name] /= gamma_div[:w_max]
+293
+294            if np.abs(e_gamma[e_name][0]) < 10 * np.finfo(float).tiny:  # Prevent division by zero
+295                self.e_tauint[e_name] = 0.5
+296                self.e_dtauint[e_name] = 0.0
+297                self.e_dvalue[e_name] = 0.0
+298                self.e_ddvalue[e_name] = 0.0
+299                self.e_windowsize[e_name] = 0
+300                continue
+301
+302            self.e_rho[e_name] = e_gamma[e_name][:w_max] / e_gamma[e_name][0]
+303            self.e_n_tauint[e_name] = np.cumsum(np.concatenate(([0.5], self.e_rho[e_name][1:])))
+304            # Make sure no entry of tauint is smaller than 0.5
+305            self.e_n_tauint[e_name][self.e_n_tauint[e_name] <= 0.5] = 0.5 + np.finfo(np.float64).eps
+306            # hep-lat/0306017 eq. (42)
+307            self.e_n_dtauint[e_name] = self.e_n_tauint[e_name] * 2 * np.sqrt(np.abs(np.arange(w_max) + 0.5 - self.e_n_tauint[e_name]) / e_N)
+308            self.e_n_dtauint[e_name][0] = 0.0
+309
+310            def _compute_drho(i, e_name=e_name, w_max=w_max, e_N=e_N):
+311                tmp = (self.e_rho[e_name][i + 1:w_max]
+312                       + np.concatenate([self.e_rho[e_name][i - 1:None if i - (w_max - 1) // 2 <= 0 else (2 * i - (2 * w_max) // 2):-1],
+313                                         self.e_rho[e_name][1:max(1, w_max - 2 * i)]])
+314                       - 2 * self.e_rho[e_name][i] * self.e_rho[e_name][1:w_max - i])
+315                self.e_drho[e_name][i] = np.sqrt(np.sum(tmp ** 2) / e_N)
+316
+317            if self.tau_exp[e_name] > 0:
+318                _compute_drho(1)
+319                texp = self.tau_exp[e_name]
+320                # Critical slowing down analysis
+321                if w_max // 2 <= 1:
+322                    raise ValueError("Need at least 8 samples for tau_exp error analysis")
+323                for n in range(1, w_max // 2):
+324                    _compute_drho(n + 1)
+325                    if (self.e_rho[e_name][n] - self.N_sigma[e_name] * self.e_drho[e_name][n]) < 0 or n >= w_max // 2 - 2:
+326                        # Bias correction hep-lat/0306017 eq. (49) included
+327                        self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N) + texp * np.abs(self.e_rho[e_name][n + 1])  # The absolute makes sure, that the tail contribution is always positive
+328                        self.e_dtauint[e_name] = np.sqrt(self.e_n_dtauint[e_name][n] ** 2 + texp ** 2 * self.e_drho[e_name][n + 1] ** 2)
+329                        # Error of tau_exp neglected so far, missing term: self.e_rho[e_name][n + 1] ** 2 * d_tau_exp ** 2
+330                        self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
+331                        self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
+332                        self.e_windowsize[e_name] = n
+333                        break
+334            else:
+335                if self.S[e_name] == 0.0:
+336                    self.e_tauint[e_name] = 0.5
+337                    self.e_dtauint[e_name] = 0.0
+338                    self.e_dvalue[e_name] = np.sqrt(e_gamma[e_name][0] / (e_N - 1))
+339                    self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt(0.5 / e_N)
+340                    self.e_windowsize[e_name] = 0
+341                else:
+342                    # Standard automatic windowing procedure
+343                    tau = self.S[e_name] / np.log((2 * self.e_n_tauint[e_name][1:] + 1) / (2 * self.e_n_tauint[e_name][1:] - 1))
+344                    g_w = np.exp(- np.arange(1, len(tau) + 1) / tau) - tau / np.sqrt(np.arange(1, len(tau) + 1) * e_N)
+345                    for n in range(1, w_max):
+346                        if g_w[n - 1] < 0 or n >= w_max - 1:
+347                            _compute_drho(n)
+348                            self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N)  # Bias correction hep-lat/0306017 eq. (49)
+349                            self.e_dtauint[e_name] = self.e_n_dtauint[e_name][n]
+350                            self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
+351                            self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
+352                            self.e_windowsize[e_name] = n
+353                            break
+354
+355            self._dvalue += self.e_dvalue[e_name] ** 2
+356            self.ddvalue += (self.e_dvalue[e_name] * self.e_ddvalue[e_name]) ** 2
+357
+358        for e_name in self.cov_names:
+359            self.e_dvalue[e_name] = np.sqrt(self.covobs[e_name].errsq())
+360            self.e_ddvalue[e_name] = 0
+361            self._dvalue += self.e_dvalue[e_name]**2
+362
+363        self._dvalue = np.sqrt(self._dvalue)
+364        if self._dvalue == 0.0:
+365            self.ddvalue = 0.0
+366        else:
+367            self.ddvalue = np.sqrt(self.ddvalue) / self._dvalue
+368        return
 
@@ -3779,171 +3828,171 @@ of the autocorrelation function (default True)
-
179    def gamma_method(self, **kwargs):
-180        """Estimate the error and related properties of the Obs.
-181
-182        Parameters
-183        ----------
-184        S : float
-185            specifies a custom value for the parameter S (default 2.0).
-186            If set to 0 it is assumed that the data exhibits no
-187            autocorrelation. In this case the error estimates coincides
-188            with the sample standard error.
-189        tau_exp : float
-190            positive value triggers the critical slowing down analysis
-191            (default 0.0).
-192        N_sigma : float
-193            number of standard deviations from zero until the tail is
-194            attached to the autocorrelation function (default 1).
-195        fft : bool
-196            determines whether the fft algorithm is used for the computation
-197            of the autocorrelation function (default True)
-198        """
-199
-200        e_content = self.e_content
-201        self.e_dvalue = {}
-202        self.e_ddvalue = {}
-203        self.e_tauint = {}
-204        self.e_dtauint = {}
-205        self.e_windowsize = {}
-206        self.e_n_tauint = {}
-207        self.e_n_dtauint = {}
-208        e_gamma = {}
-209        self.e_rho = {}
-210        self.e_drho = {}
-211        self._dvalue = 0
-212        self.ddvalue = 0
-213
-214        self.S = {}
-215        self.tau_exp = {}
-216        self.N_sigma = {}
-217
-218        if kwargs.get('fft') is False:
-219            fft = False
-220        else:
-221            fft = True
-222
-223        def _parse_kwarg(kwarg_name):
-224            if kwarg_name in kwargs:
-225                tmp = kwargs.get(kwarg_name)
-226                if isinstance(tmp, (int, float)):
-227                    if tmp < 0:
-228                        raise ValueError(kwarg_name + ' has to be larger or equal to 0.')
-229                    for e, e_name in enumerate(self.e_names):
-230                        getattr(self, kwarg_name)[e_name] = tmp
-231                else:
-232                    raise TypeError(kwarg_name + ' is not in proper format.')
-233            else:
-234                for e, e_name in enumerate(self.e_names):
-235                    if e_name in getattr(Obs, kwarg_name + '_dict'):
-236                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_dict')[e_name]
-237                    else:
-238                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_global')
-239
-240        _parse_kwarg('S')
-241        _parse_kwarg('tau_exp')
-242        _parse_kwarg('N_sigma')
-243
-244        for e, e_name in enumerate(self.mc_names):
-245            gapsize = _determine_gap(self, e_content, e_name)
-246
-247            r_length = []
-248            for r_name in e_content[e_name]:
-249                if isinstance(self.idl[r_name], range):
-250                    r_length.append(len(self.idl[r_name]) * self.idl[r_name].step // gapsize)
-251                else:
-252                    r_length.append((self.idl[r_name][-1] - self.idl[r_name][0] + 1) // gapsize)
-253
-254            e_N = np.sum([self.shape[r_name] for r_name in e_content[e_name]])
-255            w_max = max(r_length) // 2
-256            e_gamma[e_name] = np.zeros(w_max)
-257            self.e_rho[e_name] = np.zeros(w_max)
-258            self.e_drho[e_name] = np.zeros(w_max)
-259
-260            for r_name in e_content[e_name]:
-261                e_gamma[e_name] += self._calc_gamma(self.deltas[r_name], self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
-262
-263            gamma_div = np.zeros(w_max)
-264            for r_name in e_content[e_name]:
-265                gamma_div += self._calc_gamma(np.ones((self.shape[r_name])), self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
-266            gamma_div[gamma_div < 1] = 1.0
-267            e_gamma[e_name] /= gamma_div[:w_max]
+            
204    def gamma_method(self, **kwargs):
+205        """Estimate the error and related properties of the Obs.
+206
+207        Parameters
+208        ----------
+209        S : float
+210            specifies a custom value for the parameter S (default 2.0).
+211            If set to 0 it is assumed that the data exhibits no
+212            autocorrelation. In this case the error estimates coincides
+213            with the sample standard error.
+214        tau_exp : float
+215            positive value triggers the critical slowing down analysis
+216            (default 0.0).
+217        N_sigma : float
+218            number of standard deviations from zero until the tail is
+219            attached to the autocorrelation function (default 1).
+220        fft : bool
+221            determines whether the fft algorithm is used for the computation
+222            of the autocorrelation function (default True)
+223        """
+224
+225        e_content = self.e_content
+226        self.e_dvalue = {}
+227        self.e_ddvalue = {}
+228        self.e_tauint = {}
+229        self.e_dtauint = {}
+230        self.e_windowsize = {}
+231        self.e_n_tauint = {}
+232        self.e_n_dtauint = {}
+233        e_gamma = {}
+234        self.e_rho = {}
+235        self.e_drho = {}
+236        self._dvalue = 0
+237        self.ddvalue = 0
+238
+239        self.S = {}
+240        self.tau_exp = {}
+241        self.N_sigma = {}
+242
+243        if kwargs.get('fft') is False:
+244            fft = False
+245        else:
+246            fft = True
+247
+248        def _parse_kwarg(kwarg_name):
+249            if kwarg_name in kwargs:
+250                tmp = kwargs.get(kwarg_name)
+251                if isinstance(tmp, (int, float)):
+252                    if tmp < 0:
+253                        raise ValueError(kwarg_name + ' has to be larger or equal to 0.')
+254                    for _e, e_name in enumerate(self.e_names):
+255                        getattr(self, kwarg_name)[e_name] = tmp
+256                else:
+257                    raise TypeError(kwarg_name + ' is not in proper format.')
+258            else:
+259                for _e, e_name in enumerate(self.e_names):
+260                    if e_name in getattr(Obs, kwarg_name + '_dict'):
+261                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_dict')[e_name]
+262                    else:
+263                        getattr(self, kwarg_name)[e_name] = getattr(Obs, kwarg_name + '_global')
+264
+265        _parse_kwarg('S')
+266        _parse_kwarg('tau_exp')
+267        _parse_kwarg('N_sigma')
 268
-269            if np.abs(e_gamma[e_name][0]) < 10 * np.finfo(float).tiny:  # Prevent division by zero
-270                self.e_tauint[e_name] = 0.5
-271                self.e_dtauint[e_name] = 0.0
-272                self.e_dvalue[e_name] = 0.0
-273                self.e_ddvalue[e_name] = 0.0
-274                self.e_windowsize[e_name] = 0
-275                continue
-276
-277            self.e_rho[e_name] = e_gamma[e_name][:w_max] / e_gamma[e_name][0]
-278            self.e_n_tauint[e_name] = np.cumsum(np.concatenate(([0.5], self.e_rho[e_name][1:])))
-279            # Make sure no entry of tauint is smaller than 0.5
-280            self.e_n_tauint[e_name][self.e_n_tauint[e_name] <= 0.5] = 0.5 + np.finfo(np.float64).eps
-281            # hep-lat/0306017 eq. (42)
-282            self.e_n_dtauint[e_name] = self.e_n_tauint[e_name] * 2 * np.sqrt(np.abs(np.arange(w_max) + 0.5 - self.e_n_tauint[e_name]) / e_N)
-283            self.e_n_dtauint[e_name][0] = 0.0
+269        for _e, e_name in enumerate(self.mc_names):
+270            gapsize = _determine_gap(self, e_content, e_name)
+271
+272            r_length = []
+273            for r_name in e_content[e_name]:
+274                if isinstance(self.idl[r_name], range):
+275                    r_length.append(len(self.idl[r_name]) * self.idl[r_name].step // gapsize)
+276                else:
+277                    r_length.append((self.idl[r_name][-1] - self.idl[r_name][0] + 1) // gapsize)
+278
+279            e_N = np.sum([self.shape[r_name] for r_name in e_content[e_name]])
+280            w_max = max(r_length) // 2
+281            e_gamma[e_name] = np.zeros(w_max)
+282            self.e_rho[e_name] = np.zeros(w_max)
+283            self.e_drho[e_name] = np.zeros(w_max)
 284
-285            def _compute_drho(i):
-286                tmp = (self.e_rho[e_name][i + 1:w_max]
-287                       + np.concatenate([self.e_rho[e_name][i - 1:None if i - (w_max - 1) // 2 <= 0 else (2 * i - (2 * w_max) // 2):-1],
-288                                         self.e_rho[e_name][1:max(1, w_max - 2 * i)]])
-289                       - 2 * self.e_rho[e_name][i] * self.e_rho[e_name][1:w_max - i])
-290                self.e_drho[e_name][i] = np.sqrt(np.sum(tmp ** 2) / e_N)
-291
-292            if self.tau_exp[e_name] > 0:
-293                _compute_drho(1)
-294                texp = self.tau_exp[e_name]
-295                # Critical slowing down analysis
-296                if w_max // 2 <= 1:
-297                    raise ValueError("Need at least 8 samples for tau_exp error analysis")
-298                for n in range(1, w_max // 2):
-299                    _compute_drho(n + 1)
-300                    if (self.e_rho[e_name][n] - self.N_sigma[e_name] * self.e_drho[e_name][n]) < 0 or n >= w_max // 2 - 2:
-301                        # Bias correction hep-lat/0306017 eq. (49) included
-302                        self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N) + texp * np.abs(self.e_rho[e_name][n + 1])  # The absolute makes sure, that the tail contribution is always positive
-303                        self.e_dtauint[e_name] = np.sqrt(self.e_n_dtauint[e_name][n] ** 2 + texp ** 2 * self.e_drho[e_name][n + 1] ** 2)
-304                        # Error of tau_exp neglected so far, missing term: self.e_rho[e_name][n + 1] ** 2 * d_tau_exp ** 2
-305                        self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
-306                        self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
-307                        self.e_windowsize[e_name] = n
-308                        break
-309            else:
-310                if self.S[e_name] == 0.0:
-311                    self.e_tauint[e_name] = 0.5
-312                    self.e_dtauint[e_name] = 0.0
-313                    self.e_dvalue[e_name] = np.sqrt(e_gamma[e_name][0] / (e_N - 1))
-314                    self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt(0.5 / e_N)
-315                    self.e_windowsize[e_name] = 0
-316                else:
-317                    # Standard automatic windowing procedure
-318                    tau = self.S[e_name] / np.log((2 * self.e_n_tauint[e_name][1:] + 1) / (2 * self.e_n_tauint[e_name][1:] - 1))
-319                    g_w = np.exp(- np.arange(1, len(tau) + 1) / tau) - tau / np.sqrt(np.arange(1, len(tau) + 1) * e_N)
-320                    for n in range(1, w_max):
-321                        if g_w[n - 1] < 0 or n >= w_max - 1:
-322                            _compute_drho(n)
-323                            self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N)  # Bias correction hep-lat/0306017 eq. (49)
-324                            self.e_dtauint[e_name] = self.e_n_dtauint[e_name][n]
-325                            self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
-326                            self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
-327                            self.e_windowsize[e_name] = n
-328                            break
-329
-330            self._dvalue += self.e_dvalue[e_name] ** 2
-331            self.ddvalue += (self.e_dvalue[e_name] * self.e_ddvalue[e_name]) ** 2
-332
-333        for e_name in self.cov_names:
-334            self.e_dvalue[e_name] = np.sqrt(self.covobs[e_name].errsq())
-335            self.e_ddvalue[e_name] = 0
-336            self._dvalue += self.e_dvalue[e_name]**2
-337
-338        self._dvalue = np.sqrt(self._dvalue)
-339        if self._dvalue == 0.0:
-340            self.ddvalue = 0.0
-341        else:
-342            self.ddvalue = np.sqrt(self.ddvalue) / self._dvalue
-343        return
+285            for r_name in e_content[e_name]:
+286                e_gamma[e_name] += self._calc_gamma(self.deltas[r_name], self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
+287
+288            gamma_div = np.zeros(w_max)
+289            for r_name in e_content[e_name]:
+290                gamma_div += self._calc_gamma(np.ones(self.shape[r_name]), self.idl[r_name], self.shape[r_name], w_max, fft, gapsize)
+291            gamma_div[gamma_div < 1] = 1.0
+292            e_gamma[e_name] /= gamma_div[:w_max]
+293
+294            if np.abs(e_gamma[e_name][0]) < 10 * np.finfo(float).tiny:  # Prevent division by zero
+295                self.e_tauint[e_name] = 0.5
+296                self.e_dtauint[e_name] = 0.0
+297                self.e_dvalue[e_name] = 0.0
+298                self.e_ddvalue[e_name] = 0.0
+299                self.e_windowsize[e_name] = 0
+300                continue
+301
+302            self.e_rho[e_name] = e_gamma[e_name][:w_max] / e_gamma[e_name][0]
+303            self.e_n_tauint[e_name] = np.cumsum(np.concatenate(([0.5], self.e_rho[e_name][1:])))
+304            # Make sure no entry of tauint is smaller than 0.5
+305            self.e_n_tauint[e_name][self.e_n_tauint[e_name] <= 0.5] = 0.5 + np.finfo(np.float64).eps
+306            # hep-lat/0306017 eq. (42)
+307            self.e_n_dtauint[e_name] = self.e_n_tauint[e_name] * 2 * np.sqrt(np.abs(np.arange(w_max) + 0.5 - self.e_n_tauint[e_name]) / e_N)
+308            self.e_n_dtauint[e_name][0] = 0.0
+309
+310            def _compute_drho(i, e_name=e_name, w_max=w_max, e_N=e_N):
+311                tmp = (self.e_rho[e_name][i + 1:w_max]
+312                       + np.concatenate([self.e_rho[e_name][i - 1:None if i - (w_max - 1) // 2 <= 0 else (2 * i - (2 * w_max) // 2):-1],
+313                                         self.e_rho[e_name][1:max(1, w_max - 2 * i)]])
+314                       - 2 * self.e_rho[e_name][i] * self.e_rho[e_name][1:w_max - i])
+315                self.e_drho[e_name][i] = np.sqrt(np.sum(tmp ** 2) / e_N)
+316
+317            if self.tau_exp[e_name] > 0:
+318                _compute_drho(1)
+319                texp = self.tau_exp[e_name]
+320                # Critical slowing down analysis
+321                if w_max // 2 <= 1:
+322                    raise ValueError("Need at least 8 samples for tau_exp error analysis")
+323                for n in range(1, w_max // 2):
+324                    _compute_drho(n + 1)
+325                    if (self.e_rho[e_name][n] - self.N_sigma[e_name] * self.e_drho[e_name][n]) < 0 or n >= w_max // 2 - 2:
+326                        # Bias correction hep-lat/0306017 eq. (49) included
+327                        self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N) + texp * np.abs(self.e_rho[e_name][n + 1])  # The absolute makes sure, that the tail contribution is always positive
+328                        self.e_dtauint[e_name] = np.sqrt(self.e_n_dtauint[e_name][n] ** 2 + texp ** 2 * self.e_drho[e_name][n + 1] ** 2)
+329                        # Error of tau_exp neglected so far, missing term: self.e_rho[e_name][n + 1] ** 2 * d_tau_exp ** 2
+330                        self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
+331                        self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
+332                        self.e_windowsize[e_name] = n
+333                        break
+334            else:
+335                if self.S[e_name] == 0.0:
+336                    self.e_tauint[e_name] = 0.5
+337                    self.e_dtauint[e_name] = 0.0
+338                    self.e_dvalue[e_name] = np.sqrt(e_gamma[e_name][0] / (e_N - 1))
+339                    self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt(0.5 / e_N)
+340                    self.e_windowsize[e_name] = 0
+341                else:
+342                    # Standard automatic windowing procedure
+343                    tau = self.S[e_name] / np.log((2 * self.e_n_tauint[e_name][1:] + 1) / (2 * self.e_n_tauint[e_name][1:] - 1))
+344                    g_w = np.exp(- np.arange(1, len(tau) + 1) / tau) - tau / np.sqrt(np.arange(1, len(tau) + 1) * e_N)
+345                    for n in range(1, w_max):
+346                        if g_w[n - 1] < 0 or n >= w_max - 1:
+347                            _compute_drho(n)
+348                            self.e_tauint[e_name] = self.e_n_tauint[e_name][n] * (1 + (2 * n + 1) / e_N) / (1 + 1 / e_N)  # Bias correction hep-lat/0306017 eq. (49)
+349                            self.e_dtauint[e_name] = self.e_n_dtauint[e_name][n]
+350                            self.e_dvalue[e_name] = np.sqrt(2 * self.e_tauint[e_name] * e_gamma[e_name][0] * (1 + 1 / e_N) / e_N)
+351                            self.e_ddvalue[e_name] = self.e_dvalue[e_name] * np.sqrt((n + 0.5) / e_N)
+352                            self.e_windowsize[e_name] = n
+353                            break
+354
+355            self._dvalue += self.e_dvalue[e_name] ** 2
+356            self.ddvalue += (self.e_dvalue[e_name] * self.e_ddvalue[e_name]) ** 2
+357
+358        for e_name in self.cov_names:
+359            self.e_dvalue[e_name] = np.sqrt(self.covobs[e_name].errsq())
+360            self.e_ddvalue[e_name] = 0
+361            self._dvalue += self.e_dvalue[e_name]**2
+362
+363        self._dvalue = np.sqrt(self._dvalue)
+364        if self._dvalue == 0.0:
+365            self.ddvalue = 0.0
+366        else:
+367            self.ddvalue = np.sqrt(self.ddvalue) / self._dvalue
+368        return
 
@@ -3982,74 +4031,74 @@ of the autocorrelation function (default True)
-
383    def details(self, ens_content=True):
-384        """Output detailed properties of the Obs.
-385
-386        Parameters
-387        ----------
-388        ens_content : bool
-389            print details about the ensembles and replica if true.
-390        """
-391        if self.tag is not None:
-392            print("Description:", self.tag)
-393        if not hasattr(self, 'e_dvalue'):
-394            print('Result\t %3.8e' % (self.value))
-395        else:
-396            if self.value == 0.0:
-397                percentage = np.nan
-398            else:
-399                percentage = np.abs(self._dvalue / self.value) * 100
-400            print('Result\t %3.8e +/- %3.8e +/- %3.8e (%3.3f%%)' % (self.value, self._dvalue, self.ddvalue, percentage))
-401            if len(self.e_names) > 1:
-402                print(' Ensemble errors:')
-403            e_content = self.e_content
-404            for e_name in self.mc_names:
-405                gap = _determine_gap(self, e_content, e_name)
-406
-407                if len(self.e_names) > 1:
-408                    print('', e_name, '\t %3.6e +/- %3.6e' % (self.e_dvalue[e_name], self.e_ddvalue[e_name]))
-409                tau_string = " \N{GREEK SMALL LETTER TAU}_int\t " + _format_uncertainty(self.e_tauint[e_name], self.e_dtauint[e_name])
-410                tau_string += f" in units of {gap} config"
-411                if gap > 1:
-412                    tau_string += "s"
-413                if self.tau_exp[e_name] > 0:
-414                    tau_string = f"{tau_string: <45}" + '\t(\N{GREEK SMALL LETTER TAU}_exp=%3.2f, N_\N{GREEK SMALL LETTER SIGMA}=%1.0i)' % (self.tau_exp[e_name], self.N_sigma[e_name])
-415                else:
-416                    tau_string = f"{tau_string: <45}" + '\t(S=%3.2f)' % (self.S[e_name])
-417                print(tau_string)
-418            for e_name in self.cov_names:
-419                print('', e_name, '\t %3.8e' % (self.e_dvalue[e_name]))
-420        if ens_content is True:
-421            if len(self.e_names) == 1:
-422                print(self.N, 'samples in', len(self.e_names), 'ensemble:')
+            
408    def details(self, ens_content=True):
+409        """Output detailed properties of the Obs.
+410
+411        Parameters
+412        ----------
+413        ens_content : bool
+414            print details about the ensembles and replica if true.
+415        """
+416        if self.tag is not None:
+417            print("Description:", self.tag)
+418        if not hasattr(self, 'e_dvalue'):
+419            print(f'Result\t {self.value:3.8e}')
+420        else:
+421            if self.value == 0.0:
+422                percentage = np.nan
 423            else:
-424                print(self.N, 'samples in', len(self.e_names), 'ensembles:')
-425            my_string_list = []
-426            for key, value in sorted(self.e_content.items()):
-427                if key not in self.covobs:
-428                    my_string = '  ' + "\u00B7 Ensemble '" + key + "' "
-429                    if len(value) == 1:
-430                        my_string += f': {self.shape[value[0]]} configurations'
-431                        if isinstance(self.idl[value[0]], range):
-432                            my_string += f' (from {self.idl[value[0]].start} to {self.idl[value[0]][-1]}' + int(self.idl[value[0]].step != 1) * f' in steps of {self.idl[value[0]].step}' + ')'
-433                        else:
-434                            my_string += f' (irregular range from {self.idl[value[0]][0]} to {self.idl[value[0]][-1]})'
-435                    else:
-436                        sublist = []
-437                        for v in value:
-438                            my_substring = '    ' + "\u00B7 Replicum '" + v[len(key) + 1:] + "' "
-439                            my_substring += f': {self.shape[v]} configurations'
-440                            if isinstance(self.idl[v], range):
-441                                my_substring += f' (from {self.idl[v].start} to {self.idl[v][-1]}' + int(self.idl[v].step != 1) * f' in steps of {self.idl[v].step}' + ')'
-442                            else:
-443                                my_substring += f' (irregular range from {self.idl[v][0]} to {self.idl[v][-1]})'
-444                            sublist.append(my_substring)
-445
-446                        my_string += '\n' + '\n'.join(sublist)
-447                else:
-448                    my_string = '  ' + "\u00B7 Covobs   '" + key + "' "
-449                my_string_list.append(my_string)
-450            print('\n'.join(my_string_list))
+424                percentage = np.abs(self._dvalue / self.value) * 100
+425            print(f'Result\t {self.value:3.8e} +/- {self._dvalue:3.8e} +/- {self.ddvalue:3.8e} ({percentage:3.3f}%)')
+426            if len(self.e_names) > 1:
+427                print(' Ensemble errors:')
+428            e_content = self.e_content
+429            for e_name in self.mc_names:
+430                gap = _determine_gap(self, e_content, e_name)
+431
+432                if len(self.e_names) > 1:
+433                    print('', e_name, f'\t {self.e_dvalue[e_name]:3.6e} +/- {self.e_ddvalue[e_name]:3.6e}')
+434                tau_string = " \N{GREEK SMALL LETTER TAU}_int\t " + _format_uncertainty(self.e_tauint[e_name], self.e_dtauint[e_name])
+435                tau_string += f" in units of {gap} config"
+436                if gap > 1:
+437                    tau_string += "s"
+438                if self.tau_exp[e_name] > 0:
+439                    tau_string = f"{tau_string: <45}" + f'\t(\N{GREEK SMALL LETTER TAU}_exp={self.tau_exp[e_name]:3.2f}, N_\N{GREEK SMALL LETTER SIGMA}={self.N_sigma[e_name]:g})'
+440                else:
+441                    tau_string = f"{tau_string: <45}" + f'\t(S={self.S[e_name]:3.2f})'
+442                print(tau_string)
+443            for e_name in self.cov_names:
+444                print('', e_name, f'\t {self.e_dvalue[e_name]:3.8e}')
+445        if ens_content is True:
+446            if len(self.e_names) == 1:
+447                print(self.N, 'samples in', len(self.e_names), 'ensemble:')
+448            else:
+449                print(self.N, 'samples in', len(self.e_names), 'ensembles:')
+450            my_string_list = []
+451            for key, value in sorted(self.e_content.items()):
+452                if key not in self.covobs:
+453                    my_string = '  ' + "\u00B7 Ensemble '" + key + "' "
+454                    if len(value) == 1:
+455                        my_string += f': {self.shape[value[0]]} configurations'
+456                        if isinstance(self.idl[value[0]], range):
+457                            my_string += f' (from {self.idl[value[0]].start} to {self.idl[value[0]][-1]}' + int(self.idl[value[0]].step != 1) * f' in steps of {self.idl[value[0]].step}' + ')'
+458                        else:
+459                            my_string += f' (irregular range from {self.idl[value[0]][0]} to {self.idl[value[0]][-1]})'
+460                    else:
+461                        sublist = []
+462                        for v in value:
+463                            my_substring = '    ' + "\u00B7 Replicum '" + v[len(key) + 1:] + "' "
+464                            my_substring += f': {self.shape[v]} configurations'
+465                            if isinstance(self.idl[v], range):
+466                                my_substring += f' (from {self.idl[v].start} to {self.idl[v][-1]}' + int(self.idl[v].step != 1) * f' in steps of {self.idl[v].step}' + ')'
+467                            else:
+468                                my_substring += f' (irregular range from {self.idl[v][0]} to {self.idl[v][-1]})'
+469                            sublist.append(my_substring)
+470
+471                        my_string += '\n' + '\n'.join(sublist)
+472                else:
+473                    my_string = '  ' + "\u00B7 Covobs   '" + key + "' "
+474                my_string_list.append(my_string)
+475            print('\n'.join(my_string_list))
 
@@ -4076,20 +4125,20 @@ print details about the ensembles and replica if true.
-
452    def reweight(self, weight):
-453        """Reweight the obs with given rewighting factors.
-454
-455        Parameters
-456        ----------
-457        weight : Obs
-458            Reweighting factor. An Observable that has to be defined on a superset of the
-459            configurations in obs[i].idl for all i.
-460        all_configs : bool
-461            if True, the reweighted observables are normalized by the average of
-462            the reweighting factor on all configurations in weight.idl and not
-463            on the configurations in obs[i].idl. Default False.
-464        """
-465        return reweight(weight, [self])[0]
+            
477    def reweight(self, weight):
+478        """Reweight the obs with given rewighting factors.
+479
+480        Parameters
+481        ----------
+482        weight : Obs
+483            Reweighting factor. An Observable that has to be defined on a superset of the
+484            configurations in obs[i].idl for all i.
+485        all_configs : bool
+486            if True, the reweighted observables are normalized by the average of
+487            the reweighting factor on all configurations in weight.idl and not
+488            on the configurations in obs[i].idl. Default False.
+489        """
+490        return reweight(weight, [self])[0]
 
@@ -4121,17 +4170,17 @@ on the configurations in obs[i].idl. Default False.
-
467    def is_zero_within_error(self, sigma=1):
-468        """Checks whether the observable is zero within 'sigma' standard errors.
-469
-470        Parameters
-471        ----------
-472        sigma : int
-473            Number of standard errors used for the check.
-474
-475        Works only properly when the gamma method was run.
-476        """
-477        return self.is_zero() or np.abs(self.value) <= sigma * self._dvalue
+            
492    def is_zero_within_error(self, sigma=1):
+493        """Checks whether the observable is zero within 'sigma' standard errors.
+494
+495        Parameters
+496        ----------
+497        sigma : int
+498            Number of standard errors used for the check.
+499
+500        Works only properly when the gamma method was run.
+501        """
+502        return self.is_zero() or np.abs(self.value) <= sigma * self._dvalue
 
@@ -4159,15 +4208,15 @@ Number of standard errors used for the check.
-
479    def is_zero(self, atol=1e-10):
-480        """Checks whether the observable is zero within a given tolerance.
-481
-482        Parameters
-483        ----------
-484        atol : float
-485            Absolute tolerance (for details see numpy documentation).
-486        """
-487        return np.isclose(0.0, self.value, 1e-14, atol) and all(np.allclose(0.0, delta, 1e-14, atol) for delta in self.deltas.values()) and all(np.allclose(0.0, delta.errsq(), 1e-14, atol) for delta in self.covobs.values())
+            
504    def is_zero(self, atol=1e-10):
+505        """Checks whether the observable is zero within a given tolerance.
+506
+507        Parameters
+508        ----------
+509        atol : float
+510            Absolute tolerance (for details see numpy documentation).
+511        """
+512        return np.isclose(0.0, self.value, 1e-14, atol) and all(np.allclose(0.0, delta, 1e-14, atol) for delta in self.deltas.values()) and all(np.allclose(0.0, delta.errsq(), 1e-14, atol) for delta in self.covobs.values())
 
@@ -4194,45 +4243,45 @@ Absolute tolerance (for details see numpy documentation).
-
489    def plot_tauint(self, save=None):
-490        """Plot integrated autocorrelation time for each ensemble.
-491
-492        Parameters
-493        ----------
-494        save : str
-495            saves the figure to a file named 'save' if.
-496        """
-497        if not hasattr(self, 'e_dvalue'):
-498            raise Exception('Run the gamma method first.')
-499
-500        for e, e_name in enumerate(self.mc_names):
-501            fig = plt.figure()
-502            plt.xlabel(r'$W$')
-503            plt.ylabel(r'$\tau_\mathrm{int}$')
-504            length = int(len(self.e_n_tauint[e_name]))
-505            if self.tau_exp[e_name] > 0:
-506                base = self.e_n_tauint[e_name][self.e_windowsize[e_name]]
-507                x_help = np.arange(2 * self.tau_exp[e_name])
-508                y_help = (x_help + 1) * np.abs(self.e_rho[e_name][self.e_windowsize[e_name] + 1]) * (1 - x_help / (2 * (2 * self.tau_exp[e_name] - 1))) + base
-509                x_arr = np.arange(self.e_windowsize[e_name] + 1, self.e_windowsize[e_name] + 1 + 2 * self.tau_exp[e_name])
-510                plt.plot(x_arr, y_help, 'C' + str(e), linewidth=1, ls='--', marker=',')
-511                plt.errorbar([self.e_windowsize[e_name] + 2 * self.tau_exp[e_name]], [self.e_tauint[e_name]],
-512                             yerr=[self.e_dtauint[e_name]], fmt='C' + str(e), linewidth=1, capsize=2, marker='o', mfc=plt.rcParams['axes.facecolor'])
-513                xmax = self.e_windowsize[e_name] + 2 * self.tau_exp[e_name] + 1.5
-514                label = e_name + r', $\tau_\mathrm{exp}$=' + str(np.around(self.tau_exp[e_name], decimals=2))
-515            else:
-516                label = e_name + ', S=' + str(np.around(self.S[e_name], decimals=2))
-517                xmax = max(10.5, 2 * self.e_windowsize[e_name] - 0.5)
-518
-519            plt.errorbar(np.arange(length)[:int(xmax) + 1], self.e_n_tauint[e_name][:int(xmax) + 1], yerr=self.e_n_dtauint[e_name][:int(xmax) + 1], linewidth=1, capsize=2, label=label)
-520            plt.axvline(x=self.e_windowsize[e_name], color='C' + str(e), alpha=0.5, marker=',', ls='--')
-521            plt.legend()
-522            plt.xlim(-0.5, xmax)
-523            ylim = plt.ylim()
-524            plt.ylim(bottom=0.0, top=max(1.0, ylim[1]))
-525            plt.draw()
-526            if save:
-527                fig.savefig(save + "_" + str(e))
+            
514    def plot_tauint(self, save=None):
+515        """Plot integrated autocorrelation time for each ensemble.
+516
+517        Parameters
+518        ----------
+519        save : str
+520            saves the figure to a file named 'save' if.
+521        """
+522        if not hasattr(self, 'e_dvalue'):
+523            raise Exception('Run the gamma method first.')
+524
+525        for e, e_name in enumerate(self.mc_names):
+526            fig = plt.figure()
+527            plt.xlabel(r'$W$')
+528            plt.ylabel(r'$\tau_\mathrm{int}$')
+529            length = len(self.e_n_tauint[e_name])
+530            if self.tau_exp[e_name] > 0:
+531                base = self.e_n_tauint[e_name][self.e_windowsize[e_name]]
+532                x_help = np.arange(2 * self.tau_exp[e_name])
+533                y_help = (x_help + 1) * np.abs(self.e_rho[e_name][self.e_windowsize[e_name] + 1]) * (1 - x_help / (2 * (2 * self.tau_exp[e_name] - 1))) + base
+534                x_arr = np.arange(self.e_windowsize[e_name] + 1, self.e_windowsize[e_name] + 1 + 2 * self.tau_exp[e_name])
+535                plt.plot(x_arr, y_help, 'C' + str(e), linewidth=1, ls='--', marker=',')
+536                plt.errorbar([self.e_windowsize[e_name] + 2 * self.tau_exp[e_name]], [self.e_tauint[e_name]],
+537                             yerr=[self.e_dtauint[e_name]], fmt='C' + str(e), linewidth=1, capsize=2, marker='o', mfc=plt.rcParams['axes.facecolor'])
+538                xmax = self.e_windowsize[e_name] + 2 * self.tau_exp[e_name] + 1.5
+539                label = e_name + r', $\tau_\mathrm{exp}$=' + str(np.around(self.tau_exp[e_name], decimals=2))
+540            else:
+541                label = e_name + ', S=' + str(np.around(self.S[e_name], decimals=2))
+542                xmax = max(10.5, 2 * self.e_windowsize[e_name] - 0.5)
+543
+544            plt.errorbar(np.arange(length)[:int(xmax) + 1], self.e_n_tauint[e_name][:int(xmax) + 1], yerr=self.e_n_dtauint[e_name][:int(xmax) + 1], linewidth=1, capsize=2, label=label)
+545            plt.axvline(x=self.e_windowsize[e_name], color='C' + str(e), alpha=0.5, marker=',', ls='--')
+546            plt.legend()
+547            plt.xlim(-0.5, xmax)
+548            ylim = plt.ylim()
+549            plt.ylim(bottom=0.0, top=max(1.0, ylim[1]))
+550            plt.draw()
+551            if save:
+552                fig.savefig(save + "_" + str(e))
 
@@ -4259,36 +4308,36 @@ saves the figure to a file named 'save' if.
-
529    def plot_rho(self, save=None):
-530        """Plot normalized autocorrelation function time for each ensemble.
-531
-532        Parameters
-533        ----------
-534        save : str
-535            saves the figure to a file named 'save' if.
-536        """
-537        if not hasattr(self, 'e_dvalue'):
-538            raise Exception('Run the gamma method first.')
-539        for e, e_name in enumerate(self.mc_names):
-540            fig = plt.figure()
-541            plt.xlabel('W')
-542            plt.ylabel('rho')
-543            length = int(len(self.e_drho[e_name]))
-544            plt.errorbar(np.arange(length), self.e_rho[e_name][:length], yerr=self.e_drho[e_name][:], linewidth=1, capsize=2)
-545            plt.axvline(x=self.e_windowsize[e_name], color='r', alpha=0.25, ls='--', marker=',')
-546            if self.tau_exp[e_name] > 0:
-547                plt.plot([self.e_windowsize[e_name] + 1, self.e_windowsize[e_name] + 1 + 2 * self.tau_exp[e_name]],
-548                         [self.e_rho[e_name][self.e_windowsize[e_name] + 1], 0], 'k-', lw=1)
-549                xmax = self.e_windowsize[e_name] + 2 * self.tau_exp[e_name] + 1.5
-550                plt.title('Rho ' + e_name + r', tau\_exp=' + str(np.around(self.tau_exp[e_name], decimals=2)))
-551            else:
-552                xmax = max(10.5, 2 * self.e_windowsize[e_name] - 0.5)
-553                plt.title('Rho ' + e_name + ', S=' + str(np.around(self.S[e_name], decimals=2)))
-554            plt.plot([-0.5, xmax], [0, 0], 'k--', lw=1)
-555            plt.xlim(-0.5, xmax)
-556            plt.draw()
-557            if save:
-558                fig.savefig(save + "_" + str(e))
+            
554    def plot_rho(self, save=None):
+555        """Plot normalized autocorrelation function time for each ensemble.
+556
+557        Parameters
+558        ----------
+559        save : str
+560            saves the figure to a file named 'save' if.
+561        """
+562        if not hasattr(self, 'e_dvalue'):
+563            raise Exception('Run the gamma method first.')
+564        for e, e_name in enumerate(self.mc_names):
+565            fig = plt.figure()
+566            plt.xlabel('W')
+567            plt.ylabel('rho')
+568            length = len(self.e_drho[e_name])
+569            plt.errorbar(np.arange(length), self.e_rho[e_name][:length], yerr=self.e_drho[e_name][:], linewidth=1, capsize=2)
+570            plt.axvline(x=self.e_windowsize[e_name], color='r', alpha=0.25, ls='--', marker=',')
+571            if self.tau_exp[e_name] > 0:
+572                plt.plot([self.e_windowsize[e_name] + 1, self.e_windowsize[e_name] + 1 + 2 * self.tau_exp[e_name]],
+573                         [self.e_rho[e_name][self.e_windowsize[e_name] + 1], 0], 'k-', lw=1)
+574                xmax = self.e_windowsize[e_name] + 2 * self.tau_exp[e_name] + 1.5
+575                plt.title('Rho ' + e_name + r', tau\_exp=' + str(np.around(self.tau_exp[e_name], decimals=2)))
+576            else:
+577                xmax = max(10.5, 2 * self.e_windowsize[e_name] - 0.5)
+578                plt.title('Rho ' + e_name + ', S=' + str(np.around(self.S[e_name], decimals=2)))
+579            plt.plot([-0.5, xmax], [0, 0], 'k--', lw=1)
+580            plt.xlim(-0.5, xmax)
+581            plt.draw()
+582            if save:
+583                fig.savefig(save + "_" + str(e))
 
@@ -4315,27 +4364,27 @@ saves the figure to a file named 'save' if.
-
560    def plot_rep_dist(self):
-561        """Plot replica distribution for each ensemble with more than one replicum."""
-562        if not hasattr(self, 'e_dvalue'):
-563            raise Exception('Run the gamma method first.')
-564        for e, e_name in enumerate(self.mc_names):
-565            if len(self.e_content[e_name]) == 1:
-566                print('No replica distribution for a single replicum (', e_name, ')')
-567                continue
-568            r_length = []
-569            sub_r_mean = 0
-570            for r, r_name in enumerate(self.e_content[e_name]):
-571                r_length.append(len(self.deltas[r_name]))
-572                sub_r_mean += self.shape[r_name] * self.r_values[r_name]
-573            e_N = np.sum(r_length)
-574            sub_r_mean /= e_N
-575            arr = np.zeros(len(self.e_content[e_name]))
-576            for r, r_name in enumerate(self.e_content[e_name]):
-577                arr[r] = (self.r_values[r_name] - sub_r_mean) / (self.e_dvalue[e_name] * np.sqrt(e_N / self.shape[r_name] - 1))
-578            plt.hist(arr, rwidth=0.8, bins=len(self.e_content[e_name]))
-579            plt.title('Replica distribution' + e_name + ' (mean=0, var=1)')
-580            plt.draw()
+            
585    def plot_rep_dist(self):
+586        """Plot replica distribution for each ensemble with more than one replicum."""
+587        if not hasattr(self, 'e_dvalue'):
+588            raise Exception('Run the gamma method first.')
+589        for _e, e_name in enumerate(self.mc_names):
+590            if len(self.e_content[e_name]) == 1:
+591                print('No replica distribution for a single replicum (', e_name, ')')
+592                continue
+593            r_length = []
+594            sub_r_mean = 0
+595            for r_name in self.e_content[e_name]:
+596                r_length.append(len(self.deltas[r_name]))
+597                sub_r_mean += self.shape[r_name] * self.r_values[r_name]
+598            e_N = np.sum(r_length)
+599            sub_r_mean /= e_N
+600            arr = np.zeros(len(self.e_content[e_name]))
+601            for r, r_name in enumerate(self.e_content[e_name]):
+602                arr[r] = (self.r_values[r_name] - sub_r_mean) / (self.e_dvalue[e_name] * np.sqrt(e_N / self.shape[r_name] - 1))
+603            plt.hist(arr, rwidth=0.8, bins=len(self.e_content[e_name]))
+604            plt.title('Replica distribution' + e_name + ' (mean=0, var=1)')
+605            plt.draw()
 
@@ -4355,37 +4404,37 @@ saves the figure to a file named 'save' if.
-
582    def plot_history(self, expand=True):
-583        """Plot derived Monte Carlo history for each ensemble
-584
-585        Parameters
-586        ----------
-587        expand : bool
-588            show expanded history for irregular Monte Carlo chains (default: True).
-589        """
-590        for e, e_name in enumerate(self.mc_names):
-591            plt.figure()
-592            r_length = []
-593            tmp = []
-594            tmp_expanded = []
-595            for r, r_name in enumerate(self.e_content[e_name]):
-596                tmp.append(self.deltas[r_name] + self.r_values[r_name])
-597                if expand:
-598                    tmp_expanded.append(_expand_deltas(self.deltas[r_name], list(self.idl[r_name]), self.shape[r_name], 1) + self.r_values[r_name])
-599                    r_length.append(len(tmp_expanded[-1]))
-600                else:
-601                    r_length.append(len(tmp[-1]))
-602            e_N = np.sum(r_length)
-603            x = np.arange(e_N)
-604            y_test = np.concatenate(tmp, axis=0)
-605            if expand:
-606                y = np.concatenate(tmp_expanded, axis=0)
-607            else:
-608                y = y_test
-609            plt.errorbar(x, y, fmt='.', markersize=3)
-610            plt.xlim(-0.5, e_N - 0.5)
-611            plt.title(e_name + f'\nskew: {skew(y_test):.3f} (p={skewtest(y_test).pvalue:.3f}), kurtosis: {kurtosis(y_test):.3f} (p={kurtosistest(y_test).pvalue:.3f})')
-612            plt.draw()
+            
607    def plot_history(self, expand=True):
+608        """Plot derived Monte Carlo history for each ensemble
+609
+610        Parameters
+611        ----------
+612        expand : bool
+613            show expanded history for irregular Monte Carlo chains (default: True).
+614        """
+615        for _e, e_name in enumerate(self.mc_names):
+616            plt.figure()
+617            r_length = []
+618            tmp = []
+619            tmp_expanded = []
+620            for _r, r_name in enumerate(self.e_content[e_name]):
+621                tmp.append(self.deltas[r_name] + self.r_values[r_name])
+622                if expand:
+623                    tmp_expanded.append(_expand_deltas(self.deltas[r_name], list(self.idl[r_name]), self.shape[r_name], 1) + self.r_values[r_name])
+624                    r_length.append(len(tmp_expanded[-1]))
+625                else:
+626                    r_length.append(len(tmp[-1]))
+627            e_N = np.sum(r_length)
+628            x = np.arange(e_N)
+629            y_test = np.concatenate(tmp, axis=0)
+630            if expand:
+631                y = np.concatenate(tmp_expanded, axis=0)
+632            else:
+633                y = y_test
+634            plt.errorbar(x, y, fmt='.', markersize=3)
+635            plt.xlim(-0.5, e_N - 0.5)
+636            plt.title(e_name + f'\nskew: {skew(y_test):.3f} (p={skewtest(y_test).pvalue:.3f}), kurtosis: {kurtosis(y_test):.3f} (p={kurtosistest(y_test).pvalue:.3f})')
+637            plt.draw()
 
@@ -4412,29 +4461,29 @@ show expanded history for irregular Monte Carlo chains (default: True).
-
614    def plot_piechart(self, save=None):
-615        """Plot piechart which shows the fractional contribution of each
-616        ensemble to the error and returns a dictionary containing the fractions.
-617
-618        Parameters
-619        ----------
-620        save : str
-621            saves the figure to a file named 'save' if.
-622        """
-623        if not hasattr(self, 'e_dvalue'):
-624            raise Exception('Run the gamma method first.')
-625        if np.isclose(0.0, self._dvalue, atol=1e-15):
-626            raise ValueError('Error is 0.0')
-627        labels = self.e_names
-628        sizes = [self.e_dvalue[name] ** 2 for name in labels] / self._dvalue ** 2
-629        fig1, ax1 = plt.subplots()
-630        ax1.pie(sizes, labels=labels, startangle=90, normalize=True)
-631        ax1.axis('equal')
-632        plt.draw()
-633        if save:
-634            fig1.savefig(save)
-635
-636        return dict(zip(labels, sizes))
+            
639    def plot_piechart(self, save=None):
+640        """Plot piechart which shows the fractional contribution of each
+641        ensemble to the error and returns a dictionary containing the fractions.
+642
+643        Parameters
+644        ----------
+645        save : str
+646            saves the figure to a file named 'save' if.
+647        """
+648        if not hasattr(self, 'e_dvalue'):
+649            raise Exception('Run the gamma method first.')
+650        if np.isclose(0.0, self._dvalue, atol=1e-15):
+651            raise ValueError('Error is 0.0')
+652        labels = self.e_names
+653        sizes = [self.e_dvalue[name] ** 2 for name in labels] / self._dvalue ** 2
+654        fig1, ax1 = plt.subplots()
+655        ax1.pie(sizes, labels=labels, startangle=90, normalize=True)
+656        ax1.axis('equal')
+657        plt.draw()
+658        if save:
+659            fig1.savefig(save)
+660
+661        return dict(zip(labels, sizes, strict=True))
 
@@ -4462,34 +4511,34 @@ saves the figure to a file named 'save' if.
-
638    def dump(self, filename, datatype="json.gz", description="", **kwargs):
-639        """Dump the Obs to a file 'name' of chosen format.
-640
-641        Parameters
-642        ----------
-643        filename : str
-644            name of the file to be saved.
-645        datatype : str
-646            Format of the exported file. Supported formats include
-647            "json.gz" and "pickle"
-648        description : str
-649            Description for output file, only relevant for json.gz format.
-650        path : str
-651            specifies a custom path for the file (default '.')
-652        """
-653        if 'path' in kwargs:
-654            file_name = kwargs.get('path') + '/' + filename
-655        else:
-656            file_name = filename
-657
-658        if datatype == "json.gz":
-659            from .input.json import dump_to_json
-660            dump_to_json([self], file_name, description=description)
-661        elif datatype == "pickle":
-662            with open(file_name + '.p', 'wb') as fb:
-663                pickle.dump(self, fb)
-664        else:
-665            raise TypeError("Unknown datatype " + str(datatype))
+            
663    def dump(self, filename, datatype="json.gz", description="", **kwargs):
+664        """Dump the Obs to a file 'name' of chosen format.
+665
+666        Parameters
+667        ----------
+668        filename : str
+669            name of the file to be saved.
+670        datatype : str
+671            Format of the exported file. Supported formats include
+672            "json.gz" and "pickle"
+673        description : str
+674            Description for output file, only relevant for json.gz format.
+675        path : str
+676            specifies a custom path for the file (default '.')
+677        """
+678        if 'path' in kwargs:
+679            file_name = kwargs.get('path') + '/' + filename
+680        else:
+681            file_name = filename
+682
+683        if datatype == "json.gz":
+684            from .input.json import dump_to_json
+685            dump_to_json([self], file_name, description=description)
+686        elif datatype == "pickle":
+687            with open(file_name + '.p', 'wb') as fb:
+688                pickle.dump(self, fb)
+689        else:
+690            raise TypeError("Unknown datatype " + str(datatype))
 
@@ -4523,31 +4572,31 @@ specifies a custom path for the file (default '.')
-
667    def export_jackknife(self):
-668        """Export jackknife samples from the Obs
-669
-670        Returns
-671        -------
-672        numpy.ndarray
-673            Returns a numpy array of length N + 1 where N is the number of samples
-674            for the given ensemble and replicum. The zeroth entry of the array contains
-675            the mean value of the Obs, entries 1 to N contain the N jackknife samples
-676            derived from the Obs. The current implementation only works for observables
-677            defined on exactly one ensemble and replicum. The derived jackknife samples
-678            should agree with samples from a full jackknife analysis up to O(1/N).
-679        """
-680
-681        if len(self.names) != 1:
-682            raise ValueError("'export_jackknife' is only implemented for Obs defined on one ensemble and replicum.")
-683
-684        name = self.names[0]
-685        full_data = self.deltas[name] + self.r_values[name]
-686        n = full_data.size
-687        mean = self.value
-688        tmp_jacks = np.zeros(n + 1)
-689        tmp_jacks[0] = mean
-690        tmp_jacks[1:] = (n * mean - full_data) / (n - 1)
-691        return tmp_jacks
+            
692    def export_jackknife(self):
+693        """Export jackknife samples from the Obs
+694
+695        Returns
+696        -------
+697        numpy.ndarray
+698            Returns a numpy array of length N + 1 where N is the number of samples
+699            for the given ensemble and replicum. The zeroth entry of the array contains
+700            the mean value of the Obs, entries 1 to N contain the N jackknife samples
+701            derived from the Obs. The current implementation only works for observables
+702            defined on exactly one ensemble and replicum. The derived jackknife samples
+703            should agree with samples from a full jackknife analysis up to O(1/N).
+704        """
+705
+706        if len(self.names) != 1:
+707            raise ValueError("'export_jackknife' is only implemented for Obs defined on one ensemble and replicum.")
+708
+709        name = self.names[0]
+710        full_data = self.deltas[name] + self.r_values[name]
+711        n = full_data.size
+712        mean = self.value
+713        tmp_jacks = np.zeros(n + 1)
+714        tmp_jacks[0] = mean
+715        tmp_jacks[1:] = (n * mean - full_data) / (n - 1)
+716        return tmp_jacks
 
@@ -4578,48 +4627,48 @@ should agree with samples from a full jackknife analysis up to O(1/N).
-
693    def export_bootstrap(self, samples=500, random_numbers=None, save_rng=None):
-694        """Export bootstrap samples from the Obs
-695
-696        Parameters
-697        ----------
-698        samples : int
-699            Number of bootstrap samples to generate.
-700        random_numbers : np.ndarray
-701            Array of shape (samples, length) containing the random numbers to generate the bootstrap samples.
-702            If not provided the bootstrap samples are generated bashed on the md5 hash of the enesmble name.
-703        save_rng : str
-704            Save the random numbers to a file if a path is specified.
-705
-706        Returns
-707        -------
-708        numpy.ndarray
-709            Returns a numpy array of length N + 1 where N is the number of samples
-710            for the given ensemble and replicum. The zeroth entry of the array contains
-711            the mean value of the Obs, entries 1 to N contain the N import_bootstrap samples
-712            derived from the Obs. The current implementation only works for observables
-713            defined on exactly one ensemble and replicum. The derived bootstrap samples
-714            should agree with samples from a full bootstrap analysis up to O(1/N).
-715        """
-716        if len(self.names) != 1:
-717            raise ValueError("'export_boostrap' is only implemented for Obs defined on one ensemble and replicum.")
-718
-719        name = self.names[0]
-720        length = self.N
-721
-722        if random_numbers is None:
-723            seed = int(hashlib.md5(name.encode()).hexdigest(), 16) & 0xFFFFFFFF
-724            rng = np.random.default_rng(seed)
-725            random_numbers = rng.integers(0, length, size=(samples, length))
-726
-727        if save_rng is not None:
-728            np.savetxt(save_rng, random_numbers, fmt='%i')
-729
-730        proj = np.vstack([np.bincount(o, minlength=length) for o in random_numbers]) / length
-731        ret = np.zeros(samples + 1)
-732        ret[0] = self.value
-733        ret[1:] = proj @ (self.deltas[name] + self.r_values[name])
-734        return ret
+            
718    def export_bootstrap(self, samples=500, random_numbers=None, save_rng=None):
+719        """Export bootstrap samples from the Obs
+720
+721        Parameters
+722        ----------
+723        samples : int
+724            Number of bootstrap samples to generate.
+725        random_numbers : np.ndarray
+726            Array of shape (samples, length) containing the random numbers to generate the bootstrap samples.
+727            If not provided the bootstrap samples are generated bashed on the md5 hash of the enesmble name.
+728        save_rng : str
+729            Save the random numbers to a file if a path is specified.
+730
+731        Returns
+732        -------
+733        numpy.ndarray
+734            Returns a numpy array of length N + 1 where N is the number of samples
+735            for the given ensemble and replicum. The zeroth entry of the array contains
+736            the mean value of the Obs, entries 1 to N contain the N import_bootstrap samples
+737            derived from the Obs. The current implementation only works for observables
+738            defined on exactly one ensemble and replicum. The derived bootstrap samples
+739            should agree with samples from a full bootstrap analysis up to O(1/N).
+740        """
+741        if len(self.names) != 1:
+742            raise ValueError("'export_boostrap' is only implemented for Obs defined on one ensemble and replicum.")
+743
+744        name = self.names[0]
+745        length = self.N
+746
+747        if random_numbers is None:
+748            seed = int(hashlib.md5(name.encode()).hexdigest(), 16) & 0xFFFFFFFF
+749            rng = np.random.default_rng(seed)
+750            random_numbers = rng.integers(0, length, size=(samples, length))
+751
+752        if save_rng is not None:
+753            np.savetxt(save_rng, random_numbers, fmt='%i')
+754
+755        proj = np.vstack([np.bincount(o, minlength=length) for o in random_numbers]) / length
+756        ret = np.zeros(samples + 1)
+757        ret[0] = self.value
+758        ret[1:] = proj @ (self.deltas[name] + self.r_values[name])
+759        return ret
 
@@ -4662,8 +4711,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
873    def sqrt(self):
-874        return derived_observable(lambda x, **kwargs: np.sqrt(x[0]), [self], man_grad=[1 / 2 / np.sqrt(self.value)])
+            
898    def sqrt(self):
+899        return derived_observable(lambda x, **kwargs: np.sqrt(x[0]), [self], man_grad=[1 / 2 / np.sqrt(self.value)])
 
@@ -4681,8 +4730,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
876    def log(self):
-877        return derived_observable(lambda x, **kwargs: np.log(x[0]), [self], man_grad=[1 / self.value])
+            
901    def log(self):
+902        return derived_observable(lambda x, **kwargs: np.log(x[0]), [self], man_grad=[1 / self.value])
 
@@ -4700,8 +4749,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
879    def exp(self):
-880        return derived_observable(lambda x, **kwargs: np.exp(x[0]), [self], man_grad=[np.exp(self.value)])
+            
904    def exp(self):
+905        return derived_observable(lambda x, **kwargs: np.exp(x[0]), [self], man_grad=[np.exp(self.value)])
 
@@ -4719,8 +4768,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
882    def sin(self):
-883        return derived_observable(lambda x, **kwargs: np.sin(x[0]), [self], man_grad=[np.cos(self.value)])
+            
907    def sin(self):
+908        return derived_observable(lambda x, **kwargs: np.sin(x[0]), [self], man_grad=[np.cos(self.value)])
 
@@ -4738,8 +4787,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
885    def cos(self):
-886        return derived_observable(lambda x, **kwargs: np.cos(x[0]), [self], man_grad=[-np.sin(self.value)])
+            
910    def cos(self):
+911        return derived_observable(lambda x, **kwargs: np.cos(x[0]), [self], man_grad=[-np.sin(self.value)])
 
@@ -4757,8 +4806,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
888    def tan(self):
-889        return derived_observable(lambda x, **kwargs: np.tan(x[0]), [self], man_grad=[1 / np.cos(self.value) ** 2])
+            
913    def tan(self):
+914        return derived_observable(lambda x, **kwargs: np.tan(x[0]), [self], man_grad=[1 / np.cos(self.value) ** 2])
 
@@ -4776,8 +4825,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
891    def arcsin(self):
-892        return derived_observable(lambda x: anp.arcsin(x[0]), [self])
+            
916    def arcsin(self):
+917        return derived_observable(lambda x: anp.arcsin(x[0]), [self])
 
@@ -4795,8 +4844,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
894    def arccos(self):
-895        return derived_observable(lambda x: anp.arccos(x[0]), [self])
+            
919    def arccos(self):
+920        return derived_observable(lambda x: anp.arccos(x[0]), [self])
 
@@ -4814,8 +4863,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
897    def arctan(self):
-898        return derived_observable(lambda x: anp.arctan(x[0]), [self])
+            
922    def arctan(self):
+923        return derived_observable(lambda x: anp.arctan(x[0]), [self])
 
@@ -4833,8 +4882,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
900    def sinh(self):
-901        return derived_observable(lambda x, **kwargs: np.sinh(x[0]), [self], man_grad=[np.cosh(self.value)])
+            
925    def sinh(self):
+926        return derived_observable(lambda x, **kwargs: np.sinh(x[0]), [self], man_grad=[np.cosh(self.value)])
 
@@ -4852,8 +4901,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
903    def cosh(self):
-904        return derived_observable(lambda x, **kwargs: np.cosh(x[0]), [self], man_grad=[np.sinh(self.value)])
+            
928    def cosh(self):
+929        return derived_observable(lambda x, **kwargs: np.cosh(x[0]), [self], man_grad=[np.sinh(self.value)])
 
@@ -4871,8 +4920,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
906    def tanh(self):
-907        return derived_observable(lambda x, **kwargs: np.tanh(x[0]), [self], man_grad=[1 / np.cosh(self.value) ** 2])
+            
931    def tanh(self):
+932        return derived_observable(lambda x, **kwargs: np.tanh(x[0]), [self], man_grad=[1 / np.cosh(self.value) ** 2])
 
@@ -4890,8 +4939,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
909    def arcsinh(self):
-910        return derived_observable(lambda x: anp.arcsinh(x[0]), [self])
+            
934    def arcsinh(self):
+935        return derived_observable(lambda x: anp.arcsinh(x[0]), [self])
 
@@ -4909,8 +4958,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
912    def arccosh(self):
-913        return derived_observable(lambda x: anp.arccosh(x[0]), [self])
+            
937    def arccosh(self):
+938        return derived_observable(lambda x: anp.arccosh(x[0]), [self])
 
@@ -4928,8 +4977,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
915    def arctanh(self):
-916        return derived_observable(lambda x: anp.arctanh(x[0]), [self])
+            
940    def arctanh(self):
+941        return derived_observable(lambda x: anp.arctanh(x[0]), [self])
 
@@ -5080,123 +5129,125 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
 919class CObs:
- 920    """Class for a complex valued observable."""
- 921    __slots__ = ['_real', '_imag', 'tag']
- 922
- 923    def __init__(self, real, imag=0.0):
- 924        self._real = real
- 925        self._imag = imag
- 926        self.tag = None
- 927
- 928    @property
- 929    def real(self):
- 930        return self._real
- 931
- 932    @property
- 933    def imag(self):
- 934        return self._imag
- 935
- 936    def gamma_method(self, **kwargs):
- 937        """Executes the gamma_method for the real and the imaginary part."""
- 938        if isinstance(self.real, Obs):
- 939            self.real.gamma_method(**kwargs)
- 940        if isinstance(self.imag, Obs):
- 941            self.imag.gamma_method(**kwargs)
- 942
- 943    def is_zero(self):
- 944        """Checks whether both real and imaginary part are zero within machine precision."""
- 945        return self.real == 0.0 and self.imag == 0.0
- 946
- 947    def conjugate(self):
- 948        return CObs(self.real, -self.imag)
- 949
- 950    def __add__(self, other):
- 951        if isinstance(other, np.ndarray):
- 952            return other + self
- 953        elif hasattr(other, 'real') and hasattr(other, 'imag'):
- 954            return CObs(self.real + other.real,
- 955                        self.imag + other.imag)
- 956        else:
- 957            return CObs(self.real + other, self.imag)
- 958
- 959    def __radd__(self, y):
- 960        return self + y
- 961
- 962    def __sub__(self, other):
- 963        if isinstance(other, np.ndarray):
- 964            return -1 * (other - self)
- 965        elif hasattr(other, 'real') and hasattr(other, 'imag'):
- 966            return CObs(self.real - other.real, self.imag - other.imag)
- 967        else:
- 968            return CObs(self.real - other, self.imag)
- 969
- 970    def __rsub__(self, other):
- 971        return -1 * (self - other)
- 972
- 973    def __mul__(self, other):
- 974        if isinstance(other, np.ndarray):
- 975            return other * self
- 976        elif hasattr(other, 'real') and hasattr(other, 'imag'):
- 977            if all(isinstance(i, Obs) for i in [self.real, self.imag, other.real, other.imag]):
- 978                return CObs(derived_observable(lambda x, **kwargs: x[0] * x[1] - x[2] * x[3],
- 979                                               [self.real, other.real, self.imag, other.imag],
- 980                                               man_grad=[other.real.value, self.real.value, -other.imag.value, -self.imag.value]),
- 981                            derived_observable(lambda x, **kwargs: x[2] * x[1] + x[0] * x[3],
- 982                                               [self.real, other.real, self.imag, other.imag],
- 983                                               man_grad=[other.imag.value, self.imag.value, other.real.value, self.real.value]))
- 984            elif getattr(other, 'imag', 0) != 0:
- 985                return CObs(self.real * other.real - self.imag * other.imag,
- 986                            self.imag * other.real + self.real * other.imag)
- 987            else:
- 988                return CObs(self.real * other.real, self.imag * other.real)
- 989        else:
- 990            return CObs(self.real * other, self.imag * other)
- 991
- 992    def __rmul__(self, other):
- 993        return self * other
+            
 944class CObs:
+ 945    """Class for a complex valued observable."""
+ 946    __slots__ = ['_imag', '_real', 'tag']
+ 947
+ 948    def __init__(self, real, imag=0.0):
+ 949        self._real = real
+ 950        self._imag = imag
+ 951        self.tag = None
+ 952
+ 953    @property
+ 954    def real(self):
+ 955        return self._real
+ 956
+ 957    @property
+ 958    def imag(self):
+ 959        return self._imag
+ 960
+ 961    def gamma_method(self, **kwargs):
+ 962        """Executes the gamma_method for the real and the imaginary part."""
+ 963        if isinstance(self.real, Obs):
+ 964            self.real.gamma_method(**kwargs)
+ 965        if isinstance(self.imag, Obs):
+ 966            self.imag.gamma_method(**kwargs)
+ 967
+ 968    def is_zero(self):
+ 969        """Checks whether both real and imaginary part are zero within machine precision."""
+ 970        return self.real == 0.0 and self.imag == 0.0
+ 971
+ 972    def conjugate(self):
+ 973        return CObs(self.real, -self.imag)
+ 974
+ 975    def __add__(self, other):
+ 976        if isinstance(other, np.ndarray):
+ 977            return other + self
+ 978        elif hasattr(other, 'real') and hasattr(other, 'imag'):
+ 979            return CObs(self.real + other.real,
+ 980                        self.imag + other.imag)
+ 981        else:
+ 982            return CObs(self.real + other, self.imag)
+ 983
+ 984    def __radd__(self, y):
+ 985        return self + y
+ 986
+ 987    def __sub__(self, other):
+ 988        if isinstance(other, np.ndarray):
+ 989            return -1 * (other - self)
+ 990        elif hasattr(other, 'real') and hasattr(other, 'imag'):
+ 991            return CObs(self.real - other.real, self.imag - other.imag)
+ 992        else:
+ 993            return CObs(self.real - other, self.imag)
  994
- 995    def __truediv__(self, other):
- 996        if isinstance(other, np.ndarray):
- 997            return 1 / (other / self)
- 998        elif hasattr(other, 'real') and hasattr(other, 'imag'):
- 999            r = other.real ** 2 + other.imag ** 2
-1000            return CObs((self.real * other.real + self.imag * other.imag) / r, (self.imag * other.real - self.real * other.imag) / r)
-1001        else:
-1002            return CObs(self.real / other, self.imag / other)
-1003
-1004    def __rtruediv__(self, other):
-1005        r = self.real ** 2 + self.imag ** 2
-1006        if hasattr(other, 'real') and hasattr(other, 'imag'):
-1007            return CObs((self.real * other.real + self.imag * other.imag) / r, (self.real * other.imag - self.imag * other.real) / r)
-1008        else:
-1009            return CObs(self.real * other / r, -self.imag * other / r)
-1010
-1011    def __abs__(self):
-1012        return np.sqrt(self.real**2 + self.imag**2)
-1013
-1014    def __pos__(self):
-1015        return self
+ 995    def __rsub__(self, other):
+ 996        return -1 * (self - other)
+ 997
+ 998    def __mul__(self, other):
+ 999        if isinstance(other, np.ndarray):
+1000            return other * self
+1001        elif hasattr(other, 'real') and hasattr(other, 'imag'):
+1002            if all(isinstance(i, Obs) for i in [self.real, self.imag, other.real, other.imag]):
+1003                return CObs(derived_observable(lambda x, **kwargs: x[0] * x[1] - x[2] * x[3],
+1004                                               [self.real, other.real, self.imag, other.imag],
+1005                                               man_grad=[other.real.value, self.real.value, -other.imag.value, -self.imag.value]),
+1006                            derived_observable(lambda x, **kwargs: x[2] * x[1] + x[0] * x[3],
+1007                                               [self.real, other.real, self.imag, other.imag],
+1008                                               man_grad=[other.imag.value, self.imag.value, other.real.value, self.real.value]))
+1009            elif getattr(other, 'imag', 0) != 0:
+1010                return CObs(self.real * other.real - self.imag * other.imag,
+1011                            self.imag * other.real + self.real * other.imag)
+1012            else:
+1013                return CObs(self.real * other.real, self.imag * other.real)
+1014        else:
+1015            return CObs(self.real * other, self.imag * other)
 1016
-1017    def __neg__(self):
-1018        return -1 * self
+1017    def __rmul__(self, other):
+1018        return self * other
 1019
-1020    def __eq__(self, other):
-1021        return self.real == other.real and self.imag == other.imag
-1022
-1023    def __str__(self):
-1024        return '(' + str(self.real) + int(self.imag >= 0.0) * '+' + str(self.imag) + 'j)'
-1025
-1026    def __repr__(self):
-1027        return 'CObs[' + str(self) + ']'
+1020    def __truediv__(self, other):
+1021        if isinstance(other, np.ndarray):
+1022            return 1 / (other / self)
+1023        elif hasattr(other, 'real') and hasattr(other, 'imag'):
+1024            r = other.real ** 2 + other.imag ** 2
+1025            return CObs((self.real * other.real + self.imag * other.imag) / r, (self.imag * other.real - self.real * other.imag) / r)
+1026        else:
+1027            return CObs(self.real / other, self.imag / other)
 1028
-1029    def __format__(self, format_type):
-1030        if format_type == "":
-1031            significance = 2
-1032            format_type = "2"
+1029    def __rtruediv__(self, other):
+1030        r = self.real ** 2 + self.imag ** 2
+1031        if hasattr(other, 'real') and hasattr(other, 'imag'):
+1032            return CObs((self.real * other.real + self.imag * other.imag) / r, (self.real * other.imag - self.imag * other.real) / r)
 1033        else:
-1034            significance = int(float(format_type.replace("+", "").replace("-", "")))
-1035        return f"({self.real:{format_type}}{self.imag:+{significance}}j)"
+1034            return CObs(self.real * other / r, -self.imag * other / r)
+1035
+1036    def __abs__(self):
+1037        return np.sqrt(self.real**2 + self.imag**2)
+1038
+1039    def __pos__(self):
+1040        return self
+1041
+1042    def __neg__(self):
+1043        return -1 * self
+1044
+1045    def __eq__(self, other):
+1046        return self.real == other.real and self.imag == other.imag
+1047
+1048    __hash__ = None
+1049
+1050    def __str__(self):
+1051        return '(' + str(self.real) + int(self.imag >= 0.0) * '+' + str(self.imag) + 'j)'
+1052
+1053    def __repr__(self):
+1054        return 'CObs[' + str(self) + ']'
+1055
+1056    def __format__(self, format_type):
+1057        if format_type == "":
+1058            significance = 2
+1059            format_type = "2"
+1060        else:
+1061            significance = int(float(format_type.replace("+", "").replace("-", "")))
+1062        return f"({self.real:{format_type}}{self.imag:+{significance}}j)"
 
@@ -5214,10 +5265,10 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
923    def __init__(self, real, imag=0.0):
-924        self._real = real
-925        self._imag = imag
-926        self.tag = None
+            
948    def __init__(self, real, imag=0.0):
+949        self._real = real
+950        self._imag = imag
+951        self.tag = None
 
@@ -5244,9 +5295,9 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
928    @property
-929    def real(self):
-930        return self._real
+            
953    @property
+954    def real(self):
+955        return self._real
 
@@ -5262,9 +5313,9 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
932    @property
-933    def imag(self):
-934        return self._imag
+            
957    @property
+958    def imag(self):
+959        return self._imag
 
@@ -5282,12 +5333,12 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
936    def gamma_method(self, **kwargs):
-937        """Executes the gamma_method for the real and the imaginary part."""
-938        if isinstance(self.real, Obs):
-939            self.real.gamma_method(**kwargs)
-940        if isinstance(self.imag, Obs):
-941            self.imag.gamma_method(**kwargs)
+            
961    def gamma_method(self, **kwargs):
+962        """Executes the gamma_method for the real and the imaginary part."""
+963        if isinstance(self.real, Obs):
+964            self.real.gamma_method(**kwargs)
+965        if isinstance(self.imag, Obs):
+966            self.imag.gamma_method(**kwargs)
 
@@ -5307,9 +5358,9 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
943    def is_zero(self):
-944        """Checks whether both real and imaginary part are zero within machine precision."""
-945        return self.real == 0.0 and self.imag == 0.0
+            
968    def is_zero(self):
+969        """Checks whether both real and imaginary part are zero within machine precision."""
+970        return self.real == 0.0 and self.imag == 0.0
 
@@ -5329,8 +5380,8 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
947    def conjugate(self):
-948        return CObs(self.real, -self.imag)
+            
972    def conjugate(self):
+973        return CObs(self.real, -self.imag)
 
@@ -5349,12 +5400,12 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
1038def gamma_method(x, **kwargs):
-1039    """Vectorized version of the gamma_method applicable to lists or arrays of Obs.
-1040
-1041    See docstring of pe.Obs.gamma_method for details.
-1042    """
-1043    return np.vectorize(lambda o: o.gm(**kwargs))(x)
+            
1065def gamma_method(x, **kwargs):
+1066    """Vectorized version of the gamma_method applicable to lists or arrays of Obs.
+1067
+1068    See docstring of pe.Obs.gamma_method for details.
+1069    """
+1070    return np.vectorize(lambda o: o.gm(**kwargs))(x)
 
@@ -5376,12 +5427,12 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
1038def gamma_method(x, **kwargs):
-1039    """Vectorized version of the gamma_method applicable to lists or arrays of Obs.
-1040
-1041    See docstring of pe.Obs.gamma_method for details.
-1042    """
-1043    return np.vectorize(lambda o: o.gm(**kwargs))(x)
+            
1065def gamma_method(x, **kwargs):
+1066    """Vectorized version of the gamma_method applicable to lists or arrays of Obs.
+1067
+1068    See docstring of pe.Obs.gamma_method for details.
+1069    """
+1070    return np.vectorize(lambda o: o.gm(**kwargs))(x)
 
@@ -5403,194 +5454,194 @@ should agree with samples from a full bootstrap analysis up to O(1/N).
-
1173def derived_observable(func, data, array_mode=False, **kwargs):
-1174    """Construct a derived Obs according to func(data, **kwargs) using automatic differentiation.
-1175
-1176    Parameters
-1177    ----------
-1178    func : object
-1179        arbitrary function of the form func(data, **kwargs). For the
-1180        automatic differentiation to work, all numpy functions have to have
-1181        the autograd wrapper (use 'import autograd.numpy as anp').
-1182    data : list
-1183        list of Obs, e.g. [obs1, obs2, obs3].
-1184    num_grad : bool
-1185        if True, numerical derivatives are used instead of autograd
-1186        (default False). To control the numerical differentiation the
-1187        kwargs of numdifftools.step_generators.MaxStepGenerator
-1188        can be used.
-1189    man_grad : list
-1190        manually supply a list or an array which contains the jacobian
-1191        of func. Use cautiously, supplying the wrong derivative will
-1192        not be intercepted.
-1193
-1194    Notes
-1195    -----
-1196    For simple mathematical operations it can be practical to use anonymous
-1197    functions. For the ratio of two observables one can e.g. use
-1198
-1199    new_obs = derived_observable(lambda x: x[0] / x[1], [obs1, obs2])
-1200    """
-1201
-1202    data = np.asarray(data)
-1203    raveled_data = data.ravel()
-1204
-1205    # Workaround for matrix operations containing non Obs data
-1206    if not all(isinstance(x, Obs) for x in raveled_data):
-1207        for i in range(len(raveled_data)):
-1208            if isinstance(raveled_data[i], (int, float)):
-1209                raveled_data[i] = cov_Obs(raveled_data[i], 0.0, "###dummy_covobs###")
-1210
-1211    allcov = {}
-1212    for o in raveled_data:
-1213        for name in o.cov_names:
-1214            if name in allcov:
-1215                if not np.allclose(allcov[name], o.covobs[name].cov):
-1216                    raise Exception('Inconsistent covariance matrices for %s!' % (name))
-1217            else:
-1218                allcov[name] = o.covobs[name].cov
-1219
-1220    n_obs = len(raveled_data)
-1221    new_names = sorted(set([y for x in [o.names for o in raveled_data] for y in x]))
-1222    new_cov_names = sorted(set([y for x in [o.cov_names for o in raveled_data] for y in x]))
-1223    new_sample_names = sorted(set(new_names) - set(new_cov_names))
-1224
-1225    reweighted = len(list(filter(lambda o: o.reweighted is True, raveled_data))) > 0
-1226
-1227    if data.ndim == 1:
-1228        values = np.array([o.value for o in data])
-1229    else:
-1230        values = np.vectorize(lambda x: x.value)(data)
+            
1200def derived_observable(func, data, array_mode=False, **kwargs):
+1201    """Construct a derived Obs according to func(data, **kwargs) using automatic differentiation.
+1202
+1203    Parameters
+1204    ----------
+1205    func : object
+1206        arbitrary function of the form func(data, **kwargs). For the
+1207        automatic differentiation to work, all numpy functions have to have
+1208        the autograd wrapper (use 'import autograd.numpy as anp').
+1209    data : list
+1210        list of Obs, e.g. [obs1, obs2, obs3].
+1211    num_grad : bool
+1212        if True, numerical derivatives are used instead of autograd
+1213        (default False). To control the numerical differentiation the
+1214        kwargs of numdifftools.step_generators.MaxStepGenerator
+1215        can be used.
+1216    man_grad : list
+1217        manually supply a list or an array which contains the jacobian
+1218        of func. Use cautiously, supplying the wrong derivative will
+1219        not be intercepted.
+1220
+1221    Notes
+1222    -----
+1223    For simple mathematical operations it can be practical to use anonymous
+1224    functions. For the ratio of two observables one can e.g. use
+1225
+1226    new_obs = derived_observable(lambda x: x[0] / x[1], [obs1, obs2])
+1227    """
+1228
+1229    data = np.asarray(data)
+1230    raveled_data = data.ravel()
 1231
-1232    new_values = func(values, **kwargs)
-1233
-1234    multi = int(isinstance(new_values, np.ndarray))
-1235
-1236    new_r_values = {}
-1237    new_idl_d = {}
-1238    for name in new_sample_names:
-1239        idl = []
-1240        tmp_values = np.zeros(n_obs)
-1241        for i, item in enumerate(raveled_data):
-1242            tmp_values[i] = item.r_values.get(name, item.value)
-1243            tmp_idl = item.idl.get(name)
-1244            if tmp_idl is not None:
-1245                idl.append(tmp_idl)
-1246        if multi > 0:
-1247            tmp_values = np.array(tmp_values).reshape(data.shape)
-1248        new_r_values[name] = func(tmp_values, **kwargs)
-1249        new_idl_d[name] = _merge_idx(idl)
-1250
-1251    def _compute_scalefactor_missing_rep(obs):
-1252        """
-1253        Computes the scale factor that is to be multiplied with the deltas
-1254        in the case where Obs with different subsets of replica are merged.
-1255        Returns a dictionary with the scale factor for each Monte Carlo name.
-1256
-1257        Parameters
-1258        ----------
-1259        obs : Obs
-1260            The observable corresponding to the deltas that are to be scaled
-1261        """
-1262        scalef_d = {}
-1263        for mc_name in obs.mc_names:
-1264            mc_idl_d = [name for name in obs.idl if name.startswith(mc_name + '|')]
-1265            new_mc_idl_d = [name for name in new_idl_d if name.startswith(mc_name + '|')]
-1266            if len(mc_idl_d) > 0 and len(mc_idl_d) < len(new_mc_idl_d):
-1267                scalef_d[mc_name] = sum([len(new_idl_d[name]) for name in new_mc_idl_d]) / sum([len(new_idl_d[name]) for name in mc_idl_d])
-1268        return scalef_d
-1269
-1270    if 'man_grad' in kwargs:
-1271        deriv = np.asarray(kwargs.get('man_grad'))
-1272        if new_values.shape + data.shape != deriv.shape:
-1273            raise ValueError('Manual derivative does not have correct shape.')
-1274    elif kwargs.get('num_grad') is True:
-1275        if multi > 0:
-1276            raise Exception('Multi mode currently not supported for numerical derivative')
-1277        options = {
-1278            'base_step': 0.1,
-1279            'step_ratio': 2.5}
-1280        for key in options.keys():
-1281            kwarg = kwargs.get(key)
-1282            if kwarg is not None:
-1283                options[key] = kwarg
-1284        tmp_df = nd.Gradient(func, order=4, **{k: v for k, v in options.items() if v is not None})(values, **kwargs)
-1285        if tmp_df.size == 1:
-1286            deriv = np.array([tmp_df.real])
-1287        else:
-1288            deriv = tmp_df.real
-1289    else:
-1290        deriv = jacobian(func)(values, **kwargs)
-1291
-1292    final_result = np.zeros(new_values.shape, dtype=object)
-1293
-1294    if array_mode is True:
-1295
-1296        class _Zero_grad():
-1297            def __init__(self, N):
-1298                self.grad = np.zeros((N, 1))
-1299
-1300        new_covobs_lengths = dict(set([y for x in [[(n, o.covobs[n].N) for n in o.cov_names] for o in raveled_data] for y in x]))
-1301        d_extracted = {}
-1302        g_extracted = {}
-1303        for name in new_sample_names:
-1304            d_extracted[name] = []
-1305            ens_length = len(new_idl_d[name])
-1306            for i_dat, dat in enumerate(data):
-1307                d_extracted[name].append(np.array([_expand_deltas_for_merge(o.deltas.get(name, np.zeros(ens_length)), o.idl.get(name, new_idl_d[name]), o.shape.get(name, ens_length), new_idl_d[name], _compute_scalefactor_missing_rep(o).get(name.split('|')[0], 1)) for o in dat.reshape(np.prod(dat.shape))]).reshape(dat.shape + (ens_length, )))
-1308        for name in new_cov_names:
-1309            g_extracted[name] = []
-1310            zero_grad = _Zero_grad(new_covobs_lengths[name])
-1311            for i_dat, dat in enumerate(data):
-1312                g_extracted[name].append(np.array([o.covobs.get(name, zero_grad).grad for o in dat.reshape(np.prod(dat.shape))]).reshape(dat.shape + (new_covobs_lengths[name], 1)))
-1313
-1314    for i_val, new_val in np.ndenumerate(new_values):
-1315        new_deltas = {}
-1316        new_grad = {}
-1317        if array_mode is True:
-1318            for name in new_sample_names:
-1319                ens_length = d_extracted[name][0].shape[-1]
-1320                new_deltas[name] = np.zeros(ens_length)
-1321                for i_dat, dat in enumerate(d_extracted[name]):
-1322                    new_deltas[name] += np.tensordot(deriv[i_val + (i_dat, )], dat)
-1323            for name in new_cov_names:
-1324                new_grad[name] = 0
-1325                for i_dat, dat in enumerate(g_extracted[name]):
-1326                    new_grad[name] += np.tensordot(deriv[i_val + (i_dat, )], dat)
-1327        else:
-1328            for j_obs, obs in np.ndenumerate(data):
-1329                scalef_d = _compute_scalefactor_missing_rep(obs)
-1330                for name in obs.names:
-1331                    if name in obs.cov_names:
-1332                        new_grad[name] = new_grad.get(name, 0) + deriv[i_val + j_obs] * obs.covobs[name].grad
-1333                    else:
-1334                        new_deltas[name] = new_deltas.get(name, 0) + deriv[i_val + j_obs] * _expand_deltas_for_merge(obs.deltas[name], obs.idl[name], obs.shape[name], new_idl_d[name], scalef_d.get(name.split('|')[0], 1))
-1335
-1336        new_covobs = {name: Covobs(0, allcov[name], name, grad=new_grad[name]) for name in new_grad}
-1337
-1338        if not set(new_covobs.keys()).isdisjoint(new_deltas.keys()):
-1339            raise ValueError('The same name has been used for deltas and covobs!')
-1340        new_samples = []
-1341        new_means = []
-1342        new_idl = []
-1343        new_names_obs = []
-1344        for name in new_names:
-1345            if name not in new_covobs:
-1346                new_samples.append(new_deltas[name])
-1347                new_idl.append(new_idl_d[name])
-1348                new_means.append(new_r_values[name][i_val])
-1349                new_names_obs.append(name)
-1350        final_result[i_val] = Obs(new_samples, new_names_obs, means=new_means, idl=new_idl)
-1351        for name in new_covobs:
-1352            final_result[i_val].names.append(name)
-1353        final_result[i_val]._covobs = new_covobs
-1354        final_result[i_val]._value = new_val
-1355        final_result[i_val].reweighted = reweighted
-1356
-1357    if multi == 0:
-1358        final_result = final_result.item()
-1359
-1360    return final_result
+1232    # Workaround for matrix operations containing non Obs data
+1233    if not all(isinstance(x, Obs) for x in raveled_data):
+1234        for i in range(len(raveled_data)):
+1235            if isinstance(raveled_data[i], (int, float)):
+1236                raveled_data[i] = cov_Obs(raveled_data[i], 0.0, "###dummy_covobs###")
+1237
+1238    allcov = {}
+1239    for o in raveled_data:
+1240        for name in o.cov_names:
+1241            if name in allcov:
+1242                if not np.allclose(allcov[name], o.covobs[name].cov):
+1243                    raise Exception(f'Inconsistent covariance matrices for {name}!')
+1244            else:
+1245                allcov[name] = o.covobs[name].cov
+1246
+1247    n_obs = len(raveled_data)
+1248    new_names = sorted(set([y for x in [o.names for o in raveled_data] for y in x]))
+1249    new_cov_names = sorted(set([y for x in [o.cov_names for o in raveled_data] for y in x]))
+1250    new_sample_names = sorted(set(new_names) - set(new_cov_names))
+1251
+1252    reweighted = len(list(filter(lambda o: o.reweighted is True, raveled_data))) > 0
+1253
+1254    if data.ndim == 1:
+1255        values = np.array([o.value for o in data])
+1256    else:
+1257        values = np.vectorize(lambda x: x.value)(data)
+1258
+1259    new_values = func(values, **kwargs)
+1260
+1261    multi = int(isinstance(new_values, np.ndarray))
+1262
+1263    new_r_values = {}
+1264    new_idl_d = {}
+1265    for name in new_sample_names:
+1266        idl = []
+1267        tmp_values = np.zeros(n_obs)
+1268        for i, item in enumerate(raveled_data):
+1269            tmp_values[i] = item.r_values.get(name, item.value)
+1270            tmp_idl = item.idl.get(name)
+1271            if tmp_idl is not None:
+1272                idl.append(tmp_idl)
+1273        if multi > 0:
+1274            tmp_values = np.array(tmp_values).reshape(data.shape)
+1275        new_r_values[name] = func(tmp_values, **kwargs)
+1276        new_idl_d[name] = _merge_idx(idl)
+1277
+1278    def _compute_scalefactor_missing_rep(obs):
+1279        """
+1280        Computes the scale factor that is to be multiplied with the deltas
+1281        in the case where Obs with different subsets of replica are merged.
+1282        Returns a dictionary with the scale factor for each Monte Carlo name.
+1283
+1284        Parameters
+1285        ----------
+1286        obs : Obs
+1287            The observable corresponding to the deltas that are to be scaled
+1288        """
+1289        scalef_d = {}
+1290        for mc_name in obs.mc_names:
+1291            mc_idl_d = [name for name in obs.idl if name.startswith(mc_name + '|')]
+1292            new_mc_idl_d = [name for name in new_idl_d if name.startswith(mc_name + '|')]
+1293            if len(mc_idl_d) > 0 and len(mc_idl_d) < len(new_mc_idl_d):
+1294                scalef_d[mc_name] = sum([len(new_idl_d[name]) for name in new_mc_idl_d]) / sum([len(new_idl_d[name]) for name in mc_idl_d])
+1295        return scalef_d
+1296
+1297    if 'man_grad' in kwargs:
+1298        deriv = np.asarray(kwargs.get('man_grad'))
+1299        if new_values.shape + data.shape != deriv.shape:
+1300            raise ValueError('Manual derivative does not have correct shape.')
+1301    elif kwargs.get('num_grad') is True:
+1302        if multi > 0:
+1303            raise Exception('Multi mode currently not supported for numerical derivative')
+1304        options = {
+1305            'base_step': 0.1,
+1306            'step_ratio': 2.5}
+1307        for key in options.keys():
+1308            kwarg = kwargs.get(key)
+1309            if kwarg is not None:
+1310                options[key] = kwarg
+1311        tmp_df = nd.Gradient(func, order=4, **{k: v for k, v in options.items() if v is not None})(values, **kwargs)
+1312        if tmp_df.size == 1:
+1313            deriv = np.array([tmp_df.real])
+1314        else:
+1315            deriv = tmp_df.real
+1316    else:
+1317        deriv = jacobian(func)(values, **kwargs)
+1318
+1319    final_result = np.zeros(new_values.shape, dtype=object)
+1320
+1321    if array_mode is True:
+1322
+1323        class _Zero_grad:
+1324            def __init__(self, N):
+1325                self.grad = np.zeros((N, 1))
+1326
+1327        new_covobs_lengths = dict(set([y for x in [[(n, o.covobs[n].N) for n in o.cov_names] for o in raveled_data] for y in x]))
+1328        d_extracted = {}
+1329        g_extracted = {}
+1330        for name in new_sample_names:
+1331            d_extracted[name] = []
+1332            ens_length = len(new_idl_d[name])
+1333            for dat in data:
+1334                d_extracted[name].append(np.array([_expand_deltas_for_merge(o.deltas.get(name, np.zeros(ens_length)), o.idl.get(name, new_idl_d[name]), o.shape.get(name, ens_length), new_idl_d[name], _compute_scalefactor_missing_rep(o).get(name.split('|')[0], 1)) for o in dat.reshape(np.prod(dat.shape))]).reshape((*dat.shape, ens_length)))
+1335        for name in new_cov_names:
+1336            g_extracted[name] = []
+1337            zero_grad = _Zero_grad(new_covobs_lengths[name])
+1338            for dat in data:
+1339                g_extracted[name].append(np.array([o.covobs.get(name, zero_grad).grad for o in dat.reshape(np.prod(dat.shape))]).reshape((*dat.shape, new_covobs_lengths[name], 1)))
+1340
+1341    for i_val, new_val in np.ndenumerate(new_values):
+1342        new_deltas = {}
+1343        new_grad = {}
+1344        if array_mode is True:
+1345            for name in new_sample_names:
+1346                ens_length = d_extracted[name][0].shape[-1]
+1347                new_deltas[name] = np.zeros(ens_length)
+1348                for i_dat, dat in enumerate(d_extracted[name]):
+1349                    new_deltas[name] += np.tensordot(deriv[(*i_val, i_dat)], dat)
+1350            for name in new_cov_names:
+1351                new_grad[name] = 0
+1352                for i_dat, dat in enumerate(g_extracted[name]):
+1353                    new_grad[name] += np.tensordot(deriv[(*i_val, i_dat)], dat)
+1354        else:
+1355            for j_obs, obs in np.ndenumerate(data):
+1356                scalef_d = _compute_scalefactor_missing_rep(obs)
+1357                for name in obs.names:
+1358                    if name in obs.cov_names:
+1359                        new_grad[name] = new_grad.get(name, 0) + deriv[i_val + j_obs] * obs.covobs[name].grad
+1360                    else:
+1361                        new_deltas[name] = new_deltas.get(name, 0) + deriv[i_val + j_obs] * _expand_deltas_for_merge(obs.deltas[name], obs.idl[name], obs.shape[name], new_idl_d[name], scalef_d.get(name.split('|')[0], 1))
+1362
+1363        new_covobs = {name: Covobs(0, allcov[name], name, grad=new_grad[name]) for name in new_grad}
+1364
+1365        if not set(new_covobs.keys()).isdisjoint(new_deltas.keys()):
+1366            raise ValueError('The same name has been used for deltas and covobs!')
+1367        new_samples = []
+1368        new_means = []
+1369        new_idl = []
+1370        new_names_obs = []
+1371        for name in new_names:
+1372            if name not in new_covobs:
+1373                new_samples.append(new_deltas[name])
+1374                new_idl.append(new_idl_d[name])
+1375                new_means.append(new_r_values[name][i_val])
+1376                new_names_obs.append(name)
+1377        final_result[i_val] = Obs(new_samples, new_names_obs, means=new_means, idl=new_idl)
+1378        for name in new_covobs:
+1379            final_result[i_val].names.append(name)
+1380        final_result[i_val]._covobs = new_covobs
+1381        final_result[i_val]._value = new_val
+1382        final_result[i_val].reweighted = reweighted
+1383
+1384    if multi == 0:
+1385        final_result = final_result.item()
+1386
+1387    return final_result
 
@@ -5637,48 +5688,48 @@ functions. For the ratio of two observables one can e.g. use

-
1392def reweight(weight, obs, **kwargs):
-1393    """Reweight a list of observables.
-1394
-1395    Parameters
-1396    ----------
-1397    weight : Obs
-1398        Reweighting factor. An Observable that has to be defined on a superset of the
-1399        configurations in obs[i].idl for all i.
-1400    obs : list
-1401        list of Obs, e.g. [obs1, obs2, obs3].
-1402    all_configs : bool
-1403        if True, the reweighted observables are normalized by the average of
-1404        the reweighting factor on all configurations in weight.idl and not
-1405        on the configurations in obs[i].idl. Default False.
-1406    """
-1407    result = []
-1408    for i in range(len(obs)):
-1409        if len(obs[i].cov_names):
-1410            raise ValueError('Error: Not possible to reweight an Obs that contains covobs!')
-1411        if not set(obs[i].names).issubset(weight.names):
-1412            raise ValueError('Error: Ensembles do not fit')
-1413        if len(obs[i].mc_names) > 1 or len(weight.mc_names) > 1:
-1414            raise ValueError('Error: Cannot reweight an Obs that contains multiple ensembles.')
-1415        for name in obs[i].names:
-1416            if not set(obs[i].idl[name]).issubset(weight.idl[name]):
-1417                raise ValueError('obs[%d] has to be defined on a subset of the configs in weight.idl[%s]!' % (i, name))
-1418        new_samples = []
-1419        w_deltas = {}
-1420        for name in sorted(obs[i].names):
-1421            w_deltas[name] = _reduce_deltas(weight.deltas[name], weight.idl[name], obs[i].idl[name])
-1422            new_samples.append((w_deltas[name] + weight.r_values[name]) * (obs[i].deltas[name] + obs[i].r_values[name]))
-1423        tmp_obs = Obs(new_samples, sorted(obs[i].names), idl=[obs[i].idl[name] for name in sorted(obs[i].names)])
-1424
-1425        if kwargs.get('all_configs'):
-1426            new_weight = weight
-1427        else:
-1428            new_weight = Obs([w_deltas[name] + weight.r_values[name] for name in sorted(obs[i].names)], sorted(obs[i].names), idl=[obs[i].idl[name] for name in sorted(obs[i].names)])
-1429
-1430        result.append(tmp_obs / new_weight)
-1431        result[-1].reweighted = True
-1432
-1433    return result
+            
1419def reweight(weight, obs, **kwargs):
+1420    """Reweight a list of observables.
+1421
+1422    Parameters
+1423    ----------
+1424    weight : Obs
+1425        Reweighting factor. An Observable that has to be defined on a superset of the
+1426        configurations in obs[i].idl for all i.
+1427    obs : list
+1428        list of Obs, e.g. [obs1, obs2, obs3].
+1429    all_configs : bool
+1430        if True, the reweighted observables are normalized by the average of
+1431        the reweighting factor on all configurations in weight.idl and not
+1432        on the configurations in obs[i].idl. Default False.
+1433    """
+1434    result = []
+1435    for i in range(len(obs)):
+1436        if len(obs[i].cov_names):
+1437            raise ValueError('Error: Not possible to reweight an Obs that contains covobs!')
+1438        if not set(obs[i].names).issubset(weight.names):
+1439            raise ValueError('Error: Ensembles do not fit')
+1440        if len(obs[i].mc_names) > 1 or len(weight.mc_names) > 1:
+1441            raise ValueError('Error: Cannot reweight an Obs that contains multiple ensembles.')
+1442        for name in obs[i].names:
+1443            if not set(obs[i].idl[name]).issubset(weight.idl[name]):
+1444                raise ValueError(f'obs[{i}] has to be defined on a subset of the configs in weight.idl[{name}]!')
+1445        new_samples = []
+1446        w_deltas = {}
+1447        for name in sorted(obs[i].names):
+1448            w_deltas[name] = _reduce_deltas(weight.deltas[name], weight.idl[name], obs[i].idl[name])
+1449            new_samples.append((w_deltas[name] + weight.r_values[name]) * (obs[i].deltas[name] + obs[i].r_values[name]))
+1450        tmp_obs = Obs(new_samples, sorted(obs[i].names), idl=[obs[i].idl[name] for name in sorted(obs[i].names)])
+1451
+1452        if kwargs.get('all_configs'):
+1453            new_weight = weight
+1454        else:
+1455            new_weight = Obs([w_deltas[name] + weight.r_values[name] for name in sorted(obs[i].names)], sorted(obs[i].names), idl=[obs[i].idl[name] for name in sorted(obs[i].names)])
+1456
+1457        result.append(tmp_obs / new_weight)
+1458        result[-1].reweighted = True
+1459
+1460    return result
 
@@ -5712,50 +5763,50 @@ on the configurations in obs[i].idl. Default False.
-
1436def correlate(obs_a, obs_b):
-1437    """Correlate two observables.
-1438
-1439    Parameters
-1440    ----------
-1441    obs_a : Obs
-1442        First observable
-1443    obs_b : Obs
-1444        Second observable
-1445
-1446    Notes
-1447    -----
-1448    Keep in mind to only correlate primary observables which have not been reweighted
-1449    yet. The reweighting has to be applied after correlating the observables.
-1450    Only works if a single ensemble is present in the Obs.
-1451    Currently only works if ensemble content is identical (this is not strictly necessary).
-1452    """
-1453
-1454    if len(obs_a.mc_names) > 1 or len(obs_b.mc_names) > 1:
-1455        raise ValueError('Error: Cannot correlate Obs that contain multiple ensembles.')
-1456    if sorted(obs_a.names) != sorted(obs_b.names):
-1457        raise ValueError(f"Ensembles do not fit {set(sorted(obs_a.names)) ^ set(sorted(obs_b.names))}")
-1458    if len(obs_a.cov_names) or len(obs_b.cov_names):
-1459        raise ValueError('Error: Not possible to correlate Obs that contain covobs!')
-1460    for name in obs_a.names:
-1461        if obs_a.shape[name] != obs_b.shape[name]:
-1462            raise ValueError('Shapes of ensemble', name, 'do not fit')
-1463        if obs_a.idl[name] != obs_b.idl[name]:
-1464            raise ValueError('idl of ensemble', name, 'do not fit')
+            
1463def correlate(obs_a, obs_b):
+1464    """Correlate two observables.
 1465
-1466    if obs_a.reweighted is True:
-1467        warnings.warn("The first observable is already reweighted.", RuntimeWarning)
-1468    if obs_b.reweighted is True:
-1469        warnings.warn("The second observable is already reweighted.", RuntimeWarning)
-1470
-1471    new_samples = []
-1472    new_idl = []
-1473    for name in sorted(obs_a.names):
-1474        new_samples.append((obs_a.deltas[name] + obs_a.r_values[name]) * (obs_b.deltas[name] + obs_b.r_values[name]))
-1475        new_idl.append(obs_a.idl[name])
-1476
-1477    o = Obs(new_samples, sorted(obs_a.names), idl=new_idl)
-1478    o.reweighted = obs_a.reweighted or obs_b.reweighted
-1479    return o
+1466    Parameters
+1467    ----------
+1468    obs_a : Obs
+1469        First observable
+1470    obs_b : Obs
+1471        Second observable
+1472
+1473    Notes
+1474    -----
+1475    Keep in mind to only correlate primary observables which have not been reweighted
+1476    yet. The reweighting has to be applied after correlating the observables.
+1477    Only works if a single ensemble is present in the Obs.
+1478    Currently only works if ensemble content is identical (this is not strictly necessary).
+1479    """
+1480
+1481    if len(obs_a.mc_names) > 1 or len(obs_b.mc_names) > 1:
+1482        raise ValueError('Error: Cannot correlate Obs that contain multiple ensembles.')
+1483    if sorted(obs_a.names) != sorted(obs_b.names):
+1484        raise ValueError(f"Ensembles do not fit {set(sorted(obs_a.names)) ^ set(sorted(obs_b.names))}")
+1485    if len(obs_a.cov_names) or len(obs_b.cov_names):
+1486        raise ValueError('Error: Not possible to correlate Obs that contain covobs!')
+1487    for name in obs_a.names:
+1488        if obs_a.shape[name] != obs_b.shape[name]:
+1489            raise ValueError('Shapes of ensemble', name, 'do not fit')
+1490        if obs_a.idl[name] != obs_b.idl[name]:
+1491            raise ValueError('idl of ensemble', name, 'do not fit')
+1492
+1493    if obs_a.reweighted is True:
+1494        warnings.warn("The first observable is already reweighted.", RuntimeWarning, stacklevel=2)
+1495    if obs_b.reweighted is True:
+1496        warnings.warn("The second observable is already reweighted.", RuntimeWarning, stacklevel=2)
+1497
+1498    new_samples = []
+1499    new_idl = []
+1500    for name in sorted(obs_a.names):
+1501        new_samples.append((obs_a.deltas[name] + obs_a.r_values[name]) * (obs_b.deltas[name] + obs_b.r_values[name]))
+1502        new_idl.append(obs_a.idl[name])
+1503
+1504    o = Obs(new_samples, sorted(obs_a.names), idl=new_idl)
+1505    o.reweighted = obs_a.reweighted or obs_b.reweighted
+1506    return o
 
@@ -5791,74 +5842,74 @@ Currently only works if ensemble content is identical (this is not strictly nece
-
1482def covariance(obs, visualize=False, correlation=False, smooth=None, **kwargs):
-1483    r'''Calculates the error covariance matrix of a set of observables.
-1484
-1485    WARNING: This function should be used with care, especially for observables with support on multiple
-1486             ensembles with differing autocorrelations. See the notes below for details.
-1487
-1488    The gamma method has to be applied first to all observables.
-1489
-1490    Parameters
-1491    ----------
-1492    obs : list or numpy.ndarray
-1493        List or one dimensional array of Obs
-1494    visualize : bool
-1495        If True plots the corresponding normalized correlation matrix (default False).
-1496    correlation : bool
-1497        If True the correlation matrix instead of the error covariance matrix is returned (default False).
-1498    smooth : None or int
-1499        If smooth is an integer 'E' between 2 and the dimension of the matrix minus 1 the eigenvalue
-1500        smoothing procedure of hep-lat/9412087 is applied to the correlation matrix which leaves the
-1501        largest E eigenvalues essentially unchanged and smoothes the smaller eigenvalues to avoid extremely
-1502        small ones.
-1503
-1504    Notes
-1505    -----
-1506    The error covariance is defined such that it agrees with the squared standard error for two identical observables
-1507    $$\operatorname{cov}(a,a)=\sum_{s=1}^N\delta_a^s\delta_a^s/N^2=\Gamma_{aa}(0)/N=\operatorname{var}(a)/N=\sigma_a^2$$
-1508    in the absence of autocorrelation.
-1509    The error covariance is estimated by calculating the correlation matrix assuming no autocorrelation and then rescaling the correlation matrix by the full errors including the previous gamma method estimate for the autocorrelation of the observables. The covariance at windowsize 0 is guaranteed to be positive semi-definite
-1510    $$\sum_{i,j}v_i\Gamma_{ij}(0)v_j=\frac{1}{N}\sum_{s=1}^N\sum_{i,j}v_i\delta_i^s\delta_j^s v_j=\frac{1}{N}\sum_{s=1}^N\sum_{i}|v_i\delta_i^s|^2\geq 0\,,$$ for every $v\in\mathbb{R}^M$, while such an identity does not hold for larger windows/lags.
-1511    For observables defined on a single ensemble our approximation is equivalent to assuming that the integrated autocorrelation time of an off-diagonal element is equal to the geometric mean of the integrated autocorrelation times of the corresponding diagonal elements.
-1512    $$\tau_{\mathrm{int}, ij}=\sqrt{\tau_{\mathrm{int}, i}\times \tau_{\mathrm{int}, j}}$$
-1513    This construction ensures that the estimated covariance matrix is positive semi-definite (up to numerical rounding errors).
-1514    '''
-1515
-1516    length = len(obs)
-1517
-1518    max_samples = np.max([o.N for o in obs])
-1519    if max_samples <= length and not [item for sublist in [o.cov_names for o in obs] for item in sublist]:
-1520        warnings.warn(f"The dimension of the covariance matrix ({length}) is larger or equal to the number of samples ({max_samples}). This will result in a rank deficient matrix.", RuntimeWarning)
-1521
-1522    cov = np.zeros((length, length))
-1523    for i in range(length):
-1524        for j in range(i, length):
-1525            cov[i, j] = _covariance_element(obs[i], obs[j])
-1526    cov = cov + cov.T - np.diag(np.diag(cov))
-1527
-1528    corr = np.diag(1 / np.sqrt(np.diag(cov))) @ cov @ np.diag(1 / np.sqrt(np.diag(cov)))
-1529
-1530    if isinstance(smooth, int):
-1531        corr = _smooth_eigenvalues(corr, smooth)
-1532
-1533    if visualize:
-1534        plt.matshow(corr, vmin=-1, vmax=1)
-1535        plt.set_cmap('RdBu')
-1536        plt.colorbar()
-1537        plt.draw()
-1538
-1539    if correlation is True:
-1540        return corr
-1541
-1542    errors = [o.dvalue for o in obs]
-1543    cov = np.diag(errors) @ corr @ np.diag(errors)
+            
1509def covariance(obs, visualize=False, correlation=False, smooth=None, **kwargs):
+1510    r'''Calculates the error covariance matrix of a set of observables.
+1511
+1512    WARNING: This function should be used with care, especially for observables with support on multiple
+1513             ensembles with differing autocorrelations. See the notes below for details.
+1514
+1515    The gamma method has to be applied first to all observables.
+1516
+1517    Parameters
+1518    ----------
+1519    obs : list or numpy.ndarray
+1520        List or one dimensional array of Obs
+1521    visualize : bool
+1522        If True plots the corresponding normalized correlation matrix (default False).
+1523    correlation : bool
+1524        If True the correlation matrix instead of the error covariance matrix is returned (default False).
+1525    smooth : None or int
+1526        If smooth is an integer 'E' between 2 and the dimension of the matrix minus 1 the eigenvalue
+1527        smoothing procedure of hep-lat/9412087 is applied to the correlation matrix which leaves the
+1528        largest E eigenvalues essentially unchanged and smoothes the smaller eigenvalues to avoid extremely
+1529        small ones.
+1530
+1531    Notes
+1532    -----
+1533    The error covariance is defined such that it agrees with the squared standard error for two identical observables
+1534    $$\operatorname{cov}(a,a)=\sum_{s=1}^N\delta_a^s\delta_a^s/N^2=\Gamma_{aa}(0)/N=\operatorname{var}(a)/N=\sigma_a^2$$
+1535    in the absence of autocorrelation.
+1536    The error covariance is estimated by calculating the correlation matrix assuming no autocorrelation and then rescaling the correlation matrix by the full errors including the previous gamma method estimate for the autocorrelation of the observables. The covariance at windowsize 0 is guaranteed to be positive semi-definite
+1537    $$\sum_{i,j}v_i\Gamma_{ij}(0)v_j=\frac{1}{N}\sum_{s=1}^N\sum_{i,j}v_i\delta_i^s\delta_j^s v_j=\frac{1}{N}\sum_{s=1}^N\sum_{i}|v_i\delta_i^s|^2\geq 0\,,$$ for every $v\in\mathbb{R}^M$, while such an identity does not hold for larger windows/lags.
+1538    For observables defined on a single ensemble our approximation is equivalent to assuming that the integrated autocorrelation time of an off-diagonal element is equal to the geometric mean of the integrated autocorrelation times of the corresponding diagonal elements.
+1539    $$\tau_{\mathrm{int}, ij}=\sqrt{\tau_{\mathrm{int}, i}\times \tau_{\mathrm{int}, j}}$$
+1540    This construction ensures that the estimated covariance matrix is positive semi-definite (up to numerical rounding errors).
+1541    '''
+1542
+1543    length = len(obs)
 1544
-1545    eigenvalues = np.linalg.eigh(cov)[0]
-1546    if not np.all(eigenvalues >= 0):
-1547        warnings.warn("Covariance matrix is not positive semi-definite (Eigenvalues: " + str(eigenvalues) + ")", RuntimeWarning)
+1545    max_samples = np.max([o.N for o in obs])
+1546    if max_samples <= length and not [item for sublist in [o.cov_names for o in obs] for item in sublist]:
+1547        warnings.warn(f"The dimension of the covariance matrix ({length}) is larger or equal to the number of samples ({max_samples}). This will result in a rank deficient matrix.", RuntimeWarning, stacklevel=2)
 1548
-1549    return cov
+1549    cov = np.zeros((length, length))
+1550    for i in range(length):
+1551        for j in range(i, length):
+1552            cov[i, j] = _covariance_element(obs[i], obs[j])
+1553    cov = cov + cov.T - np.diag(np.diag(cov))
+1554
+1555    corr = np.diag(1 / np.sqrt(np.diag(cov))) @ cov @ np.diag(1 / np.sqrt(np.diag(cov)))
+1556
+1557    if isinstance(smooth, int):
+1558        corr = _smooth_eigenvalues(corr, smooth)
+1559
+1560    if visualize:
+1561        plt.matshow(corr, vmin=-1, vmax=1)
+1562        plt.set_cmap('RdBu')
+1563        plt.colorbar()
+1564        plt.draw()
+1565
+1566    if correlation is True:
+1567        return corr
+1568
+1569    errors = [o.dvalue for o in obs]
+1570    cov = np.diag(errors) @ corr @ np.diag(errors)
+1571
+1572    eigenvalues = np.linalg.eigh(cov)[0]
+1573    if not np.all(eigenvalues >= 0):
+1574        warnings.warn("Covariance matrix is not positive semi-definite (Eigenvalues: " + str(eigenvalues) + ")", RuntimeWarning, stacklevel=2)
+1575
+1576    return cov
 
@@ -5910,27 +5961,27 @@ This construction ensures that the estimated covariance matrix is positive semi-
-
1552def invert_corr_cov_cholesky(corr, inverrdiag):
-1553    """Constructs a lower triangular matrix `chol` via the Cholesky decomposition of the correlation matrix `corr`
-1554       and then returns the inverse covariance matrix `chol_inv` as a lower triangular matrix by solving `chol * x = inverrdiag`.
-1555
-1556    Parameters
-1557    ----------
-1558    corr : np.ndarray
-1559           correlation matrix
-1560    inverrdiag : np.ndarray
-1561              diagonal matrix, the entries are the inverse errors of the data points considered
-1562    """
-1563
-1564    condn = np.linalg.cond(corr)
-1565    if condn > 0.1 / np.finfo(float).eps:
-1566        raise ValueError(f"Cannot invert correlation matrix as its condition number exceeds machine precision ({condn:1.2e})")
-1567    if condn > 1e13:
-1568        warnings.warn("Correlation matrix may be ill-conditioned, condition number: {%1.2e}" % (condn), RuntimeWarning)
-1569    chol = np.linalg.cholesky(corr)
-1570    chol_inv = scipy.linalg.solve_triangular(chol, inverrdiag, lower=True)
-1571
-1572    return chol_inv
+            
1579def invert_corr_cov_cholesky(corr, inverrdiag):
+1580    """Constructs a lower triangular matrix `chol` via the Cholesky decomposition of the correlation matrix `corr`
+1581       and then returns the inverse covariance matrix `chol_inv` as a lower triangular matrix by solving `chol * x = inverrdiag`.
+1582
+1583    Parameters
+1584    ----------
+1585    corr : np.ndarray
+1586           correlation matrix
+1587    inverrdiag : np.ndarray
+1588              diagonal matrix, the entries are the inverse errors of the data points considered
+1589    """
+1590
+1591    condn = np.linalg.cond(corr)
+1592    if condn > 0.1 / np.finfo(float).eps:
+1593        raise ValueError(f"Cannot invert correlation matrix as its condition number exceeds machine precision ({condn:1.2e})")
+1594    if condn > 1e13:
+1595        warnings.warn(f"Correlation matrix may be ill-conditioned, condition number: {{{condn:1.2e}}}", RuntimeWarning, stacklevel=2)
+1596    chol = np.linalg.cholesky(corr)
+1597    chol_inv = scipy.linalg.solve_triangular(chol, inverrdiag, lower=True)
+1598
+1599    return chol_inv
 
@@ -5960,67 +6011,67 @@ diagonal matrix, the entries are the inverse errors of the data points considere
-
1575def sort_corr(corr, kl, yd):
-1576    """ Reorders a correlation matrix to match the alphabetical order of its underlying y data.
-1577
-1578    The ordering of the input correlation matrix `corr` is given by the list of keys `kl`.
-1579    The input dictionary `yd` (with the same keys `kl`) must contain the corresponding y data
-1580    that the correlation matrix is based on.
-1581    This function sorts the list of keys `kl` alphabetically and sorts the matrix `corr`
-1582    according to this alphabetical order such that the sorted matrix `corr_sorted` corresponds
-1583    to the y data `yd` when arranged in an alphabetical order by its keys.
-1584
-1585    Parameters
-1586    ----------
-1587    corr : np.ndarray
-1588        A square correlation matrix constructed using the order of the y data specified by `kl`.
-1589        The dimensions of `corr` should match the total number of y data points in `yd` combined.
-1590    kl : list of str
-1591        A list of keys that denotes the order in which the y data from `yd` was used to build the
-1592        input correlation matrix `corr`.
-1593    yd : dict of list
-1594        A dictionary where each key corresponds to a unique identifier, and its value is a list of
-1595        y data points. The total number of y data points across all keys must match the dimensions
-1596        of `corr`. The lists in the dictionary can be lists of Obs.
-1597
-1598    Returns
-1599    -------
-1600    np.ndarray
-1601        A new, sorted correlation matrix that corresponds to the y data from `yd` when arranged alphabetically by its keys.
-1602
-1603    Example
-1604    -------
-1605    >>> import numpy as np
-1606    >>> import pyerrors as pe
-1607    >>> corr = np.array([[1, 0.2, 0.3], [0.2, 1, 0.4], [0.3, 0.4, 1]])
-1608    >>> kl = ['b', 'a']
-1609    >>> yd = {'a': [1, 2], 'b': [3]}
-1610    >>> sorted_corr = pe.obs.sort_corr(corr, kl, yd)
-1611    >>> print(sorted_corr)
-1612    array([[1. , 0.3, 0.4],
-1613           [0.3, 1. , 0.2],
-1614           [0.4, 0.2, 1. ]])
-1615
-1616    """
-1617    kl_sorted = sorted(kl)
-1618
-1619    posd = {}
-1620    ofs = 0
-1621    for ki, k in enumerate(kl):
-1622        posd[k] = [i + ofs for i in range(len(yd[k]))]
-1623        ofs += len(posd[k])
+            
1602def sort_corr(corr, kl, yd):
+1603    """ Reorders a correlation matrix to match the alphabetical order of its underlying y data.
+1604
+1605    The ordering of the input correlation matrix `corr` is given by the list of keys `kl`.
+1606    The input dictionary `yd` (with the same keys `kl`) must contain the corresponding y data
+1607    that the correlation matrix is based on.
+1608    This function sorts the list of keys `kl` alphabetically and sorts the matrix `corr`
+1609    according to this alphabetical order such that the sorted matrix `corr_sorted` corresponds
+1610    to the y data `yd` when arranged in an alphabetical order by its keys.
+1611
+1612    Parameters
+1613    ----------
+1614    corr : np.ndarray
+1615        A square correlation matrix constructed using the order of the y data specified by `kl`.
+1616        The dimensions of `corr` should match the total number of y data points in `yd` combined.
+1617    kl : list of str
+1618        A list of keys that denotes the order in which the y data from `yd` was used to build the
+1619        input correlation matrix `corr`.
+1620    yd : dict of list
+1621        A dictionary where each key corresponds to a unique identifier, and its value is a list of
+1622        y data points. The total number of y data points across all keys must match the dimensions
+1623        of `corr`. The lists in the dictionary can be lists of Obs.
 1624
-1625    mapping = []
-1626    for k in kl_sorted:
-1627        for i in range(len(yd[k])):
-1628            mapping.append(posd[k][i])
+1625    Returns
+1626    -------
+1627    np.ndarray
+1628        A new, sorted correlation matrix that corresponds to the y data from `yd` when arranged alphabetically by its keys.
 1629
-1630    corr_sorted = np.zeros_like(corr)
-1631    for i in range(corr.shape[0]):
-1632        for j in range(corr.shape[0]):
-1633            corr_sorted[i][j] = corr[mapping[i]][mapping[j]]
-1634
-1635    return corr_sorted
+1630    Example
+1631    -------
+1632    >>> import numpy as np
+1633    >>> import pyerrors as pe
+1634    >>> corr = np.array([[1, 0.2, 0.3], [0.2, 1, 0.4], [0.3, 0.4, 1]])
+1635    >>> kl = ['b', 'a']
+1636    >>> yd = {'a': [1, 2], 'b': [3]}
+1637    >>> sorted_corr = pe.obs.sort_corr(corr, kl, yd)
+1638    >>> print(sorted_corr)
+1639    array([[1. , 0.3, 0.4],
+1640           [0.3, 1. , 0.2],
+1641           [0.4, 0.2, 1. ]])
+1642
+1643    """
+1644    kl_sorted = sorted(kl)
+1645
+1646    posd = {}
+1647    ofs = 0
+1648    for _ki, k in enumerate(kl):
+1649        posd[k] = [i + ofs for i in range(len(yd[k]))]
+1650        ofs += len(posd[k])
+1651
+1652    mapping = []
+1653    for k in kl_sorted:
+1654        for i in range(len(yd[k])):
+1655            mapping.append(posd[k][i])
+1656
+1657    corr_sorted = np.zeros_like(corr)
+1658    for i in range(corr.shape[0]):
+1659        for j in range(corr.shape[0]):
+1660            corr_sorted[i][j] = corr[mapping[i]][mapping[j]]
+1661
+1662    return corr_sorted
 
@@ -6084,24 +6135,24 @@ of corr. The lists in the dictionary can be lists of Obs.
-
1715def import_jackknife(jacks, name, idl=None):
-1716    """Imports jackknife samples and returns an Obs
-1717
-1718    Parameters
-1719    ----------
-1720    jacks : numpy.ndarray
-1721        numpy array containing the mean value as zeroth entry and
-1722        the N jackknife samples as first to Nth entry.
-1723    name : str
-1724        name of the ensemble the samples are defined on.
-1725    """
-1726    length = len(jacks) - 1
-1727    prj = (np.ones((length, length)) - (length - 1) * np.identity(length))
-1728    samples = jacks[1:] @ prj
-1729    mean = np.mean(samples)
-1730    new_obs = Obs([samples - mean], [name], idl=idl, means=[mean])
-1731    new_obs._value = jacks[0]
-1732    return new_obs
+            
1742def import_jackknife(jacks, name, idl=None):
+1743    """Imports jackknife samples and returns an Obs
+1744
+1745    Parameters
+1746    ----------
+1747    jacks : numpy.ndarray
+1748        numpy array containing the mean value as zeroth entry and
+1749        the N jackknife samples as first to Nth entry.
+1750    name : str
+1751        name of the ensemble the samples are defined on.
+1752    """
+1753    length = len(jacks) - 1
+1754    prj = (np.ones((length, length)) - (length - 1) * np.identity(length))
+1755    samples = jacks[1:] @ prj
+1756    mean = np.mean(samples)
+1757    new_obs = Obs([samples - mean], [name], idl=idl, means=[mean])
+1758    new_obs._value = jacks[0]
+1759    return new_obs
 
@@ -6131,34 +6182,34 @@ name of the ensemble the samples are defined on.
-
1735def import_bootstrap(boots, name, random_numbers):
-1736    """Imports bootstrap samples and returns an Obs
-1737
-1738    Parameters
-1739    ----------
-1740    boots : numpy.ndarray
-1741        numpy array containing the mean value as zeroth entry and
-1742        the N bootstrap samples as first to Nth entry.
-1743    name : str
-1744        name of the ensemble the samples are defined on.
-1745    random_numbers : np.ndarray
-1746        Array of shape (samples, length) containing the random numbers to generate the bootstrap samples,
-1747        where samples is the number of bootstrap samples and length is the length of the original Monte Carlo
-1748        chain to be reconstructed.
-1749    """
-1750    samples, length = random_numbers.shape
-1751    if samples != len(boots) - 1:
-1752        raise ValueError("Random numbers do not have the correct shape.")
-1753
-1754    if samples < length:
-1755        raise ValueError("Obs can't be reconstructed if there are fewer bootstrap samples than Monte Carlo data points.")
-1756
-1757    proj = np.vstack([np.bincount(o, minlength=length) for o in random_numbers]) / length
-1758
-1759    samples = scipy.linalg.lstsq(proj, boots[1:])[0]
-1760    ret = Obs([samples], [name])
-1761    ret._value = boots[0]
-1762    return ret
+            
1762def import_bootstrap(boots, name, random_numbers):
+1763    """Imports bootstrap samples and returns an Obs
+1764
+1765    Parameters
+1766    ----------
+1767    boots : numpy.ndarray
+1768        numpy array containing the mean value as zeroth entry and
+1769        the N bootstrap samples as first to Nth entry.
+1770    name : str
+1771        name of the ensemble the samples are defined on.
+1772    random_numbers : np.ndarray
+1773        Array of shape (samples, length) containing the random numbers to generate the bootstrap samples,
+1774        where samples is the number of bootstrap samples and length is the length of the original Monte Carlo
+1775        chain to be reconstructed.
+1776    """
+1777    samples, length = random_numbers.shape
+1778    if samples != len(boots) - 1:
+1779        raise ValueError("Random numbers do not have the correct shape.")
+1780
+1781    if samples < length:
+1782        raise ValueError("Obs can't be reconstructed if there are fewer bootstrap samples than Monte Carlo data points.")
+1783
+1784    proj = np.vstack([np.bincount(o, minlength=length) for o in random_numbers]) / length
+1785
+1786    samples = scipy.linalg.lstsq(proj, boots[1:])[0]
+1787    ret = Obs([samples], [name])
+1788    ret._value = boots[0]
+1789    return ret
 
@@ -6192,38 +6243,38 @@ chain to be reconstructed.
-
1765def merge_obs(list_of_obs):
-1766    """Combine all observables in list_of_obs into one new observable.
-1767    This allows to merge Obs that have been computed on multiple replica
-1768    of the same ensemble.
-1769    If you like to merge Obs that are based on several ensembles, please
-1770    average them yourself.
-1771
-1772    Parameters
-1773    ----------
-1774    list_of_obs : list
-1775        list of the Obs object to be combined
-1776
-1777    Notes
-1778    -----
-1779    It is not possible to combine obs which are based on the same replicum
-1780    """
-1781    replist = [item for obs in list_of_obs for item in obs.names]
-1782    if (len(replist) == len(set(replist))) is False:
-1783        raise ValueError('list_of_obs contains duplicate replica: %s' % (str(replist)))
-1784    if any([len(o.cov_names) for o in list_of_obs]):
-1785        raise ValueError('Not possible to merge data that contains covobs!')
-1786    new_dict = {}
-1787    idl_dict = {}
-1788    for o in list_of_obs:
-1789        new_dict.update({key: o.deltas.get(key, 0) + o.r_values.get(key, 0)
-1790                        for key in set(o.deltas) | set(o.r_values)})
-1791        idl_dict.update({key: o.idl.get(key, 0) for key in set(o.deltas)})
-1792
-1793    names = sorted(new_dict.keys())
-1794    o = Obs([new_dict[name] for name in names], names, idl=[idl_dict[name] for name in names])
-1795    o.reweighted = np.max([oi.reweighted for oi in list_of_obs])
-1796    return o
+            
1792def merge_obs(list_of_obs):
+1793    """Combine all observables in list_of_obs into one new observable.
+1794    This allows to merge Obs that have been computed on multiple replica
+1795    of the same ensemble.
+1796    If you like to merge Obs that are based on several ensembles, please
+1797    average them yourself.
+1798
+1799    Parameters
+1800    ----------
+1801    list_of_obs : list
+1802        list of the Obs object to be combined
+1803
+1804    Notes
+1805    -----
+1806    It is not possible to combine obs which are based on the same replicum
+1807    """
+1808    replist = [item for obs in list_of_obs for item in obs.names]
+1809    if (len(replist) == len(set(replist))) is False:
+1810        raise ValueError(f'list_of_obs contains duplicate replica: {replist!s}')
+1811    if any([len(o.cov_names) for o in list_of_obs]):
+1812        raise ValueError('Not possible to merge data that contains covobs!')
+1813    new_dict = {}
+1814    idl_dict = {}
+1815    for o in list_of_obs:
+1816        new_dict.update({key: o.deltas.get(key, 0) + o.r_values.get(key, 0)
+1817                        for key in set(o.deltas) | set(o.r_values)})
+1818        idl_dict.update({key: o.idl.get(key, 0) for key in set(o.deltas)})
+1819
+1820    names = sorted(new_dict.keys())
+1821    o = Obs([new_dict[name] for name in names], names, idl=[idl_dict[name] for name in names])
+1822    o.reweighted = np.max([oi.reweighted for oi in list_of_obs])
+1823    return o
 
@@ -6258,47 +6309,47 @@ list of the Obs object to be combined
-
1799def cov_Obs(means, cov, name, grad=None):
-1800    """Create an Obs based on mean(s) and a covariance matrix
-1801
-1802    Parameters
-1803    ----------
-1804    mean : list of floats or float
-1805        N mean value(s) of the new Obs
-1806    cov : list or array
-1807        2d (NxN) Covariance matrix, 1d diagonal entries or 0d covariance
-1808    name : str
-1809        identifier for the covariance matrix
-1810    grad : list or array
-1811        Gradient of the Covobs wrt. the means belonging to cov.
-1812    """
-1813
-1814    def covobs_to_obs(co):
-1815        """Make an Obs out of a Covobs
-1816
-1817        Parameters
-1818        ----------
-1819        co : Covobs
-1820            Covobs to be embedded into the Obs
-1821        """
-1822        o = Obs([], [], means=[])
-1823        o._value = co.value
-1824        o.names.append(co.name)
-1825        o._covobs[co.name] = co
-1826        o._dvalue = np.sqrt(co.errsq())
-1827        return o
+            
1826def cov_Obs(means, cov, name, grad=None):
+1827    """Create an Obs based on mean(s) and a covariance matrix
 1828
-1829    ol = []
-1830    if isinstance(means, (float, int)):
-1831        means = [means]
-1832
-1833    for i in range(len(means)):
-1834        ol.append(covobs_to_obs(Covobs(means[i], cov, name, pos=i, grad=grad)))
-1835    if ol[0].covobs[name].N != len(means):
-1836        raise ValueError('You have to provide %d mean values!' % (ol[0].N))
-1837    if len(ol) == 1:
-1838        return ol[0]
-1839    return ol
+1829    Parameters
+1830    ----------
+1831    mean : list of floats or float
+1832        N mean value(s) of the new Obs
+1833    cov : list or array
+1834        2d (NxN) Covariance matrix, 1d diagonal entries or 0d covariance
+1835    name : str
+1836        identifier for the covariance matrix
+1837    grad : list or array
+1838        Gradient of the Covobs wrt. the means belonging to cov.
+1839    """
+1840
+1841    def covobs_to_obs(co):
+1842        """Make an Obs out of a Covobs
+1843
+1844        Parameters
+1845        ----------
+1846        co : Covobs
+1847            Covobs to be embedded into the Obs
+1848        """
+1849        o = Obs([], [], means=[])
+1850        o._value = co.value
+1851        o.names.append(co.name)
+1852        o._covobs[co.name] = co
+1853        o._dvalue = np.sqrt(co.errsq())
+1854        return o
+1855
+1856    ol = []
+1857    if isinstance(means, (float, int)):
+1858        means = [means]
+1859
+1860    for i in range(len(means)):
+1861        ol.append(covobs_to_obs(Covobs(means[i], cov, name, pos=i, grad=grad)))
+1862    if ol[0].covobs[name].N != len(means):
+1863        raise ValueError(f'You have to provide {ol[0].N} mean values!')
+1864    if len(ol) == 1:
+1865        return ol[0]
+1866    return ol
 
diff --git a/docs/pyerrors/roots.html b/docs/pyerrors/roots.html index a1af04d7..9ea1af15 100644 --- a/docs/pyerrors/roots.html +++ b/docs/pyerrors/roots.html @@ -79,46 +79,47 @@
 1import numpy as np
  2import scipy.optimize
  3from autograd import jacobian
- 4from .obs import derived_observable
- 5
+ 4
+ 5from .obs import derived_observable
  6
- 7def find_root(d, func, guess=1.0, **kwargs):
- 8    r'''Finds the root of the function func(x, d) where d is an `Obs`.
- 9
-10    Parameters
-11    -----------------
-12    d : Obs
-13        Obs passed to the function.
-14    func : object
-15        Function to be minimized. Any numpy functions have to use the autograd.numpy wrapper.
-16        Example:
-17        ```python
-18        import autograd.numpy as anp
-19        def root_func(x, d):
-20            return anp.exp(-x ** 2) - d
-21        ```
-22    guess : float
-23        Initial guess for the minimization.
-24
-25    Returns
-26    -------
-27    res : Obs
-28        `Obs` valued root of the function.
-29    '''
-30    d_val = np.vectorize(lambda x: x.value)(np.array(d))
-31
-32    root = scipy.optimize.fsolve(func, guess, d_val)
-33
-34    # Error propagation as detailed in arXiv:1809.01289
-35    try:
-36        dx = jacobian(func)(root[0], d_val)
-37        da = jacobian(lambda u, v: func(v, u))(d_val, root[0])
-38    except (TypeError, ValueError, np.linalg.LinAlgError):
-39        raise Exception("It is required to use autograd.numpy instead of numpy within root functions, see the documentation for details.") from None
-40    deriv = - da / dx
-41    res = derived_observable(lambda x, **kwargs: (x[0] + np.finfo(np.float64).eps) / (np.array(d).reshape(-1)[0].value + np.finfo(np.float64).eps) * root[0],
-42                             np.array(d).reshape(-1), man_grad=np.array(deriv).reshape(-1))
-43    return res
+ 7
+ 8def find_root(d, func, guess=1.0, **kwargs):
+ 9    r'''Finds the root of the function func(x, d) where d is an `Obs`.
+10
+11    Parameters
+12    -----------------
+13    d : Obs
+14        Obs passed to the function.
+15    func : object
+16        Function to be minimized. Any numpy functions have to use the autograd.numpy wrapper.
+17        Example:
+18        ```python
+19        import autograd.numpy as anp
+20        def root_func(x, d):
+21            return anp.exp(-x ** 2) - d
+22        ```
+23    guess : float
+24        Initial guess for the minimization.
+25
+26    Returns
+27    -------
+28    res : Obs
+29        `Obs` valued root of the function.
+30    '''
+31    d_val = np.vectorize(lambda x: x.value)(np.array(d))
+32
+33    root = scipy.optimize.fsolve(func, guess, d_val)
+34
+35    # Error propagation as detailed in arXiv:1809.01289
+36    try:
+37        dx = jacobian(func)(root[0], d_val)
+38        da = jacobian(lambda u, v: func(v, u))(d_val, root[0])
+39    except (TypeError, ValueError, np.linalg.LinAlgError):
+40        raise Exception("It is required to use autograd.numpy instead of numpy within root functions, see the documentation for details.") from None
+41    deriv = - da / dx
+42    res = derived_observable(lambda x, **kwargs: (x[0] + np.finfo(np.float64).eps) / (np.array(d).reshape(-1)[0].value + np.finfo(np.float64).eps) * root[0],
+43                             np.array(d).reshape(-1), man_grad=np.array(deriv).reshape(-1))
+44    return res
 
@@ -134,43 +135,43 @@
-
 8def find_root(d, func, guess=1.0, **kwargs):
- 9    r'''Finds the root of the function func(x, d) where d is an `Obs`.
-10
-11    Parameters
-12    -----------------
-13    d : Obs
-14        Obs passed to the function.
-15    func : object
-16        Function to be minimized. Any numpy functions have to use the autograd.numpy wrapper.
-17        Example:
-18        ```python
-19        import autograd.numpy as anp
-20        def root_func(x, d):
-21            return anp.exp(-x ** 2) - d
-22        ```
-23    guess : float
-24        Initial guess for the minimization.
-25
-26    Returns
-27    -------
-28    res : Obs
-29        `Obs` valued root of the function.
-30    '''
-31    d_val = np.vectorize(lambda x: x.value)(np.array(d))
-32
-33    root = scipy.optimize.fsolve(func, guess, d_val)
-34
-35    # Error propagation as detailed in arXiv:1809.01289
-36    try:
-37        dx = jacobian(func)(root[0], d_val)
-38        da = jacobian(lambda u, v: func(v, u))(d_val, root[0])
-39    except (TypeError, ValueError, np.linalg.LinAlgError):
-40        raise Exception("It is required to use autograd.numpy instead of numpy within root functions, see the documentation for details.") from None
-41    deriv = - da / dx
-42    res = derived_observable(lambda x, **kwargs: (x[0] + np.finfo(np.float64).eps) / (np.array(d).reshape(-1)[0].value + np.finfo(np.float64).eps) * root[0],
-43                             np.array(d).reshape(-1), man_grad=np.array(deriv).reshape(-1))
-44    return res
+            
 9def find_root(d, func, guess=1.0, **kwargs):
+10    r'''Finds the root of the function func(x, d) where d is an `Obs`.
+11
+12    Parameters
+13    -----------------
+14    d : Obs
+15        Obs passed to the function.
+16    func : object
+17        Function to be minimized. Any numpy functions have to use the autograd.numpy wrapper.
+18        Example:
+19        ```python
+20        import autograd.numpy as anp
+21        def root_func(x, d):
+22            return anp.exp(-x ** 2) - d
+23        ```
+24    guess : float
+25        Initial guess for the minimization.
+26
+27    Returns
+28    -------
+29    res : Obs
+30        `Obs` valued root of the function.
+31    '''
+32    d_val = np.vectorize(lambda x: x.value)(np.array(d))
+33
+34    root = scipy.optimize.fsolve(func, guess, d_val)
+35
+36    # Error propagation as detailed in arXiv:1809.01289
+37    try:
+38        dx = jacobian(func)(root[0], d_val)
+39        da = jacobian(lambda u, v: func(v, u))(d_val, root[0])
+40    except (TypeError, ValueError, np.linalg.LinAlgError):
+41        raise Exception("It is required to use autograd.numpy instead of numpy within root functions, see the documentation for details.") from None
+42    deriv = - da / dx
+43    res = derived_observable(lambda x, **kwargs: (x[0] + np.finfo(np.float64).eps) / (np.array(d).reshape(-1)[0].value + np.finfo(np.float64).eps) * root[0],
+44                             np.array(d).reshape(-1), man_grad=np.array(deriv).reshape(-1))
+45    return res
 
diff --git a/docs/pyerrors/special.html b/docs/pyerrors/special.html index cc23a492..9894d668 100644 --- a/docs/pyerrors/special.html +++ b/docs/pyerrors/special.html @@ -61,20 +61,26 @@
  • betaln
  • -
  • - polygamma -
  • -
  • - psi -
  • digamma
  • - gamma + erf
  • - gammaln + erfc +
  • +
  • + erfcinv +
  • +
  • + erfinv +
  • +
  • + expit +
  • +
  • + gamma
  • gammainc @@ -82,36 +88,12 @@
  • gammaincc
  • +
  • + gammaln +
  • gammasgn
  • -
  • - rgamma -
  • -
  • - multigammaln -
  • -
  • - kn -
  • -
  • - j0 -
  • -
  • - y0 -
  • -
  • - j1 -
  • -
  • - y1 -
  • -
  • - jn -
  • -
  • - yn -
  • i0
  • @@ -125,25 +107,43 @@ ive
  • - erf + j0
  • - erfc + j1
  • - erfinv + jn
  • - erfcinv + kn
  • logit
  • - expit + logsumexp
  • - logsumexp + multigammaln +
  • +
  • + polygamma +
  • +
  • + psi +
  • +
  • + rgamma +
  • +
  • + y0 +
  • +
  • + y1 +
  • +
  • + yn
  • @@ -166,29 +166,86 @@ -
     1import scipy
    - 2import numpy as np
    - 3from autograd.extend import primitive, defvjp
    - 4from autograd.scipy.special import j0, y0, j1, y1, jn, yn, i0, i1, iv, ive, beta, betainc, betaln
    - 5from autograd.scipy.special import polygamma, psi, digamma, gamma, gammaln, gammainc, gammaincc, gammasgn, rgamma, multigammaln
    - 6from autograd.scipy.special import erf, erfc, erfinv, erfcinv, logit, expit, logsumexp
    - 7
    - 8
    - 9__all__ = ["beta", "betainc", "betaln",
    -10           "polygamma", "psi", "digamma", "gamma", "gammaln", "gammainc", "gammaincc", "gammasgn", "rgamma", "multigammaln",
    -11           "kn", "j0", "y0", "j1", "y1", "jn", "yn", "i0", "i1", "iv", "ive",
    -12           "erf", "erfc", "erfinv", "erfcinv", "logit", "expit", "logsumexp"]
    -13
    -14
    -15@primitive
    -16def kn(n, x):
    -17    """Modified Bessel function of the second kind of integer order n"""
    -18    if int(n) != n:
    -19        raise TypeError("The order 'n' needs to be an integer.")
    -20    return scipy.special.kn(n, x)
    -21
    -22
    -23defvjp(kn, None, lambda ans, n, x: lambda g: - g * 0.5 * (kn(np.abs(n - 1), x) + kn(n + 1, x)))
    +                        
     1import numpy as np
    + 2import scipy
    + 3from autograd.extend import defvjp, primitive
    + 4from autograd.scipy.special import (
    + 5    beta,
    + 6    betainc,
    + 7    betaln,
    + 8    digamma,
    + 9    erf,
    +10    erfc,
    +11    erfcinv,
    +12    erfinv,
    +13    expit,
    +14    gamma,
    +15    gammainc,
    +16    gammaincc,
    +17    gammaln,
    +18    gammasgn,
    +19    i0,
    +20    i1,
    +21    iv,
    +22    ive,
    +23    j0,
    +24    j1,
    +25    jn,
    +26    logit,
    +27    logsumexp,
    +28    multigammaln,
    +29    polygamma,
    +30    psi,
    +31    rgamma,
    +32    y0,
    +33    y1,
    +34    yn,
    +35)
    +36
    +37__all__ = [
    +38    "beta",
    +39    "betainc",
    +40    "betaln",
    +41    "digamma",
    +42    "erf",
    +43    "erfc",
    +44    "erfcinv",
    +45    "erfinv",
    +46    "expit",
    +47    "gamma",
    +48    "gammainc",
    +49    "gammaincc",
    +50    "gammaln",
    +51    "gammasgn",
    +52    "i0",
    +53    "i1",
    +54    "iv",
    +55    "ive",
    +56    "j0",
    +57    "j1",
    +58    "jn",
    +59    "kn",
    +60    "logit",
    +61    "logsumexp",
    +62    "multigammaln",
    +63    "polygamma",
    +64    "psi",
    +65    "rgamma",
    +66    "y0",
    +67    "y1",
    +68    "yn",
    +69]
    +70
    +71
    +72@primitive
    +73def kn(n, x):
    +74    """Modified Bessel function of the second kind of integer order n"""
    +75    if int(n) != n:
    +76        raise TypeError("The order 'n' needs to be an integer.")
    +77    return scipy.special.kn(n, x)
    +78
    +79
    +80defvjp(kn, None, lambda ans, n, x: lambda g: - g * 0.5 * (kn(np.abs(n - 1), x) + kn(n + 1, x)))
     
    @@ -706,289 +763,6 @@ the logarithm of the actual value.

    - -
    - -
    -
    @wraps(f_raw)
    - - def - polygamma(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    Polygamma functions.

    - -

    Defined as \( \psi^{(n)}(x) \) where \( \psi \) is the -digamma function. See [dlmf]_ for details.

    - -
    Parameters
    - -
      -
    • n (array_like): -The order of the derivative of the digamma function; must be -integral
    • -
    • x (array_like): -Real valued input
    • -
    - -
    Returns
    - -
      -
    • ndarray: Function results
    • -
    - -
    See Also
    - -

    `digamma() -..`

    - -
    Notes
    - -

    Array API Standard Support

    - -

    polygamma has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ✅
    -JAX ✅ ✅
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    References
    - -

    .. [dlmf] NIST, Digital Library of Mathematical Functions, - https://dlmf.nist.gov/5.15

    - -
    Examples
    - -
    -
    >>> from scipy import special
    ->>> x = [2, 3, 25.5]
    ->>> special.polygamma(1, x)
    -array([ 0.64493407,  0.39493407,  0.03999467])
    ->>> special.polygamma(0, x) == special.psi(x)
    -array([ True,  True,  True], dtype=bool)
    -
    -
    -
    - - -
    -
    - -
    -
    @wraps(f_raw)
    - - def - psi(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    psi(z, out=None)

    - -

    The digamma function.

    - -

    The logarithmic derivative of the gamma function evaluated at z.

    - -
    Parameters
    - -
      -
    • z (array_like): -Real or complex argument.
    • -
    • out (ndarray, optional): -Array for the computed values of psi.
    • -
    - -
    Returns
    - -
      -
    • digamma (scalar or ndarray): -Computed values of psi.
    • -
    - -
    Notes
    - -

    For large values not close to the negative real axis, psi is -computed using the asymptotic series (5.11.2) from 1. For small -arguments not close to the negative real axis, the recurrence -relation (5.5.2) from 1 is used until the argument is large -enough to use the asymptotic series. For values close to the -negative real axis, the reflection formula (5.5.4) from 1 is -used first. Note that psi has a family of zeros on the -negative real axis which occur between the poles at nonpositive -integers. Around the zeros the reflection formula suffers from -cancellation and the implementation loses precision. The sole -positive zero and the first negative zero, however, are handled -separately by precomputing series expansions using 2, so the -function should maintain full accuracy around the origin.

    - -

    Array API Standard Support

    - -

    psi has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ✅
    -JAX ✅ ✅
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    References
    - -
    Examples
    - -
    -
    >>> from scipy.special import psi
    ->>> z = 3 + 4j
    ->>> psi(z)
    -(1.55035981733341+1.0105022091860445j)
    -
    -
    - -

    Verify psi(z) = psi(z + 1) - 1/z:

    - -
    -
    >>> psi(z + 1) - 1/z
    -(1.55035981733341+1.0105022091860445j)
    -
    -
    - -
    -
    -
      -
    1. -

      NIST Digital Library of Mathematical Functions -https://dlmf.nist.gov/5 

      -
    2. - -
    3. -

      Fredrik Johansson and others. -"mpmath: a Python library for arbitrary-precision floating-point arithmetic" -(Version 0.19) http://mpmath.org/ 

      -
    4. -
    -
    -
    - -
    @@ -1145,6 +919,755 @@ Dask ✅ n/a
    + +
    + +
    +
    @wraps(f_raw)
    + + def + erf(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    erf(z, out=None)

    + +

    Error function of real or complex argument.

    + +

    $$\operatorname{erf}(z) = \frac{2}{\sqrt{\pi}} \int_0^z e^{-t^2} dt$$

    + +
    Parameters
    + +
      +
    • z (ndarray): +Input array.
    • +
    • out (ndarray, optional): +Optional output array for the function values.
    • +
    + +
    Returns
    + +
      +
    • res (scalar or ndarray): +The values of the error function at the given points z.
    • +
    + +
    See Also
    + +

    erfc()`,`,erfcx(),, erfi()`,`,erfinv(),, erfcinv()`,`,wofz() +..

    + +
    Notes
    + +

    The cumulative distribution function (CDF) of the standard normal distribution can +be expressed in terms of the error function as

    + +

    $$\Phi(z) = \frac{1}{2} +\left[1 + \operatorname{erf} \left(\frac{z}{\sqrt{2}}\right)\right]$$

    + +

    Array API Standard Support

    + +

    erf has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ✅
    +JAX ✅ ✅
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    References
    + +
    Examples
    + +
    +
    >>> import numpy as np
    +>>> from scipy import special
    +>>> import matplotlib.pyplot as plt
    +>>> z = np.linspace(-3, 3)
    +>>> plt.plot(z, special.erf(z))
    +>>> plt.xlabel('$z$')
    +>>> plt.ylabel('$erf(z)$')
    +>>> plt.show()
    +
    +
    + +
    +
    +
      +
    +
    +
    + + +
    +
    + +
    +
    @wraps(f_raw)
    + + def + erfc(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    erfc(x, out=None)

    + +

    Complementary error function.

    + +

    The complementary error function is defined as

    + +

    $$\operatorname{erfc}(x) = 1 - \operatorname{erf}(x)$$

    + +
    Parameters
    + +
      +
    • x (array_like): +Real or complex valued argument
    • +
    • out (ndarray, optional): +Optional output array for the function results
    • +
    + +
    Returns
    + +
      +
    • scalar or ndarray: Values of the complementary error function
    • +
    + +
    See Also
    + +

    erf()`,`,erfi(),, erfcx()`,`,dawsn(),, `wofz() +..`

    + +
    Notes
    + +

    Array API Standard Support

    + +

    erfc has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ✅
    +JAX ✅ ✅
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    References
    + +
    Examples
    + +
    +
    >>> import numpy as np
    +>>> from scipy import special
    +>>> import matplotlib.pyplot as plt
    +>>> x = np.linspace(-3, 3)
    +>>> plt.plot(x, special.erfc(x))
    +>>> plt.xlabel('$x$')
    +>>> plt.ylabel('$erfc(x)$')
    +>>> plt.show()
    +
    +
    + +
    +
    +
      +
    +
    +
    + + +
    +
    + +
    +
    @wraps(f_raw)
    + + def + erfcinv(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    erfcinv(x, /, out=None, *, where=True, casting='same_kind', order='K', dtype=None, subok=True[, signature])

    + +

    erfcinv(y, out=None)

    + +

    Inverse of the complementary error function.

    + +

    Computes the inverse of the complementary error function.

    + +

    In the complex domain, there is no unique complex number \( w \) satisfying +\( \operatorname{erfc}(w) = z \). This indicates a true inverse function +would be multivalued. +When the domain restricts to the real interval \( 0 < x < 2 \), there is +a unique real number satisfying

    + +

    $$\operatorname{erfc}(\operatorname{erfcinv}(x)) = x$$

    + +

    It is related to the inverse of the error function by

    + +

    $$\operatorname{erfcinv}(1 - x) = \operatorname{erfinv}(x)$$

    + +
    Parameters
    + +
      +
    • y (ndarray): +Argument at which to evaluate. Domain: \( [0, 2] \)
    • +
    • out (ndarray, optional): +Optional output array for the function values
    • +
    + +
    Returns
    + +
      +
    • erfcinv (scalar or ndarray): +The inverse of \( \operatorname{erfc} \) of \( y \), element-wise
    • +
    + +
    See Also
    + +

    erf: Error function
    +erfc: Complementary error function
    +erfinv: Inverse of the error function

    + +
    Examples
    + +
    +
    >>> import numpy as np
    +>>> import matplotlib.pyplot as plt
    +>>> from scipy.special import erfcinv
    +
    +
    + +
    +
    >>> erfcinv(0.5)
    +0.4769362762044699
    +
    +
    + +
    +
    >>> y = np.linspace(0.0, 2.0, num=11)
    +>>> erfcinv(y)
    +array([        inf,  0.9061938 ,  0.59511608,  0.37080716,  0.17914345,
    +       -0.        , -0.17914345, -0.37080716, -0.59511608, -0.9061938 ,
    +              -inf])
    +
    +
    + +

    Plot the function:

    + +
    +
    >>> y = np.linspace(0, 2, 200)
    +>>> fig, ax = plt.subplots()
    +>>> ax.plot(y, erfcinv(y))
    +>>> ax.grid(True)
    +>>> ax.set_xlabel('y')
    +>>> ax.set_title('erfcinv(y)')
    +>>> plt.show()
    +
    +
    +
    + + +
    +
    + +
    +
    @wraps(f_raw)
    + + def + erfinv(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    erfinv(y, out=None)

    + +

    Inverse of the error function.

    + +

    Computes the inverse of the error function.

    + +

    In the complex domain, there is no unique complex number w satisfying +erf(w)=z. This indicates a true inverse function would be multivalued. +When the domain restricts to the real, -1 < x < 1, there is a unique real +number satisfying erf(erfinv(x)) = x.

    + +
    Parameters
    + +
      +
    • y (ndarray): +Argument at which to evaluate. Domain: [-1, 1]
    • +
    • out (ndarray, optional): +Optional output array for the function values
    • +
    + +
    Returns
    + +
      +
    • erfinv (scalar or ndarray): +The inverse of erf of y, element-wise
    • +
    + +
    See Also
    + +

    erf()` +Error`, `function`, `of`, `a`, `complex`, `argument` +erfc() +Complementary, error, function,, 1`, `-`, `erf(x)
    +`erfcinv() +Inverse,of,the,complementary,error,function`

    + +
    Notes
    + +

    This function wraps the erf_inv routine from the +Boost Math C++ library 1.

    + +

    Array API Standard Support

    + +

    erfinv has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ✅
    +JAX ✅ ✅
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    References
    + +
    Examples
    + +
    +
    >>> import numpy as np
    +>>> import matplotlib.pyplot as plt
    +>>> from scipy.special import erfinv, erf
    +
    +
    + +
    +
    >>> erfinv(0.5)
    +0.4769362762044699
    +
    +
    + +
    +
    >>> y = np.linspace(-1.0, 1.0, num=9)
    +>>> x = erfinv(y)
    +>>> x
    +array([       -inf, -0.81341985, -0.47693628, -0.22531206,  0.        ,
    +        0.22531206,  0.47693628,  0.81341985,         inf])
    +
    +
    + +

    Verify that erf(erfinv(y)) is y.

    + +
    +
    >>> erf(x)
    +array([-1.  , -0.75, -0.5 , -0.25,  0.  ,  0.25,  0.5 ,  0.75,  1.  ])
    +
    +
    + +

    Plot the function:

    + +
    +
    >>> y = np.linspace(-1, 1, 200)
    +>>> fig, ax = plt.subplots()
    +>>> ax.plot(y, erfinv(y))
    +>>> ax.grid(True)
    +>>> ax.set_xlabel('y')
    +>>> ax.set_title('erfinv(y)')
    +>>> plt.show()
    +
    +
    + +
    +
    +
      +
    1. +

      The Boost Developers. "Boost C++ Libraries". https://www.boost.org/

      +
    2. +
    +
    +
    + + +
    +
    + +
    +
    @wraps(f_raw)
    + + def + expit(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    expit(x, out=None)

    + +

    Expit (also known as logistic sigmoid) ufunc for ndarrays.

    + +

    The expit function, also known as the logistic sigmoid function, is +defined as expit(x) = 1/(1+exp(-x)). It is the inverse of the +logit function.

    + +
    Parameters
    + +
      +
    • x (ndarray): +The ndarray to apply expit to element-wise.
    • +
    • out (ndarray, optional): +Optional output array for the function values
    • +
    + +
    Returns
    + +
      +
    • scalar or ndarray: An ndarray of the same shape as x. Its entries +are expit of the corresponding entry of x.
    • +
    + +
    See Also
    + +

    `logit() +..`

    + +
    Notes
    + +

    As a ufunc expit takes a number of optional +keyword arguments. For more information +see ufuncs

    + +

    New in version 0.10.0.

    + +

    Array API Standard Support

    + +

    expit has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ✅
    +JAX ✅ ✅
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    Examples
    + +
    +
    >>> import numpy as np
    +>>> from scipy.special import expit, logit
    +
    +
    + +
    +
    >>> expit([-np.inf, -1.5, 0, 1.5, np.inf])
    +array([ 0.        ,  0.18242552,  0.5       ,  0.81757448,  1.        ])
    +
    +
    + +

    logit is the inverse of expit:

    + +
    +
    >>> logit(expit([-2.5, 0, 3.1, 5.0]))
    +array([-2.5,  0. ,  3.1,  5. ])
    +
    +
    + +

    Plot expit(x) for x in [-6, 6]:

    + +
    +
    >>> import matplotlib.pyplot as plt
    +>>> x = np.linspace(-6, 6, 121)
    +>>> y = expit(x)
    +>>> plt.plot(x, y)
    +>>> plt.grid()
    +>>> plt.xlim(-6, 6)
    +>>> plt.xlabel('x')
    +>>> plt.title('expit(x)')
    +>>> plt.show()
    +
    +
    +
    + +
    @@ -1345,173 +1868,6 @@ Dask ✅ n/a
    - -
    - -
    -
    @wraps(f_raw)
    - - def - gammaln(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    gammaln(x, out=None)

    - -

    Logarithm of the absolute value of the gamma function.

    - -

    Defined as

    - -

    $$\ln(\lvert\Gamma(x)\rvert)$$

    - -

    where \( \Gamma \) is the gamma function. For more details on -the gamma function, see [dlmf]_.

    - -
    Parameters
    - -
      -
    • x (array_like): -Real argument
    • -
    • out (ndarray, optional): -Optional output array for the function results
    • -
    - -
    Returns
    - -
      -
    • scalar or ndarray: Values of the log of the absolute value of gamma
    • -
    - -
    See Also
    - -

    gammasgn()` -sign`, `of`, `the`, `gamma`, `function` -loggamma() -principal, branch, of, the, logarithm, of, the, gamma, function

    - -
    Notes
    - -

    It is the same function as the Python standard library function -math.lgamma().

    - -

    When used in conjunction with gammasgn, this function is useful -for working in logspace on the real axis without having to deal -with complex numbers via the relation exp(gammaln(x)) = -gammasgn(x) * gamma(x).

    - -

    For complex-valued log-gamma, use loggamma instead of gammaln.

    - -

    Array API Standard Support

    - -

    gammaln has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ✅
    -JAX ✅ ✅
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    References
    - -

    .. [dlmf] NIST Digital Library of Mathematical Functions - https://dlmf.nist.gov/5

    - -
    Examples
    - -
    -
    >>> import numpy as np
    ->>> import scipy.special as sc
    -
    -
    - -

    It has two positive zeros.

    - -
    -
    >>> sc.gammaln([1, 2])
    -array([0., 0.])
    -
    -
    - -

    It has poles at nonpositive integers.

    - -
    -
    >>> sc.gammaln([0, -1, -2, -3, -4])
    -array([inf, inf, inf, inf, inf])
    -
    -
    - -

    It asymptotically approaches x * log(x) (Stirling's formula).

    - -
    -
    >>> x = np.array([1e10, 1e20, 1e40, 1e80])
    ->>> sc.gammaln(x)
    -array([2.20258509e+11, 4.50517019e+21, 9.11034037e+41, 1.83206807e+82])
    ->>> x * np.log(x)
    -array([2.30258509e+11, 4.60517019e+21, 9.21034037e+41, 1.84206807e+82])
    -
    -
    -
    - -
    @@ -1833,6 +2189,173 @@ starts at 1 and monotonically decreases to 0.

    + +
    + +
    +
    @wraps(f_raw)
    + + def + gammaln(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    gammaln(x, out=None)

    + +

    Logarithm of the absolute value of the gamma function.

    + +

    Defined as

    + +

    $$\ln(\lvert\Gamma(x)\rvert)$$

    + +

    where \( \Gamma \) is the gamma function. For more details on +the gamma function, see [dlmf]_.

    + +
    Parameters
    + +
      +
    • x (array_like): +Real argument
    • +
    • out (ndarray, optional): +Optional output array for the function results
    • +
    + +
    Returns
    + +
      +
    • scalar or ndarray: Values of the log of the absolute value of gamma
    • +
    + +
    See Also
    + +

    gammasgn()` +sign`, `of`, `the`, `gamma`, `function` +loggamma() +principal, branch, of, the, logarithm, of, the, gamma, function

    + +
    Notes
    + +

    It is the same function as the Python standard library function +math.lgamma().

    + +

    When used in conjunction with gammasgn, this function is useful +for working in logspace on the real axis without having to deal +with complex numbers via the relation exp(gammaln(x)) = +gammasgn(x) * gamma(x).

    + +

    For complex-valued log-gamma, use loggamma instead of gammaln.

    + +

    Array API Standard Support

    + +

    gammaln has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ✅
    +JAX ✅ ✅
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    References
    + +

    .. [dlmf] NIST Digital Library of Mathematical Functions + https://dlmf.nist.gov/5

    + +
    Examples
    + +
    +
    >>> import numpy as np
    +>>> import scipy.special as sc
    +
    +
    + +

    It has two positive zeros.

    + +
    +
    >>> sc.gammaln([1, 2])
    +array([0., 0.])
    +
    +
    + +

    It has poles at nonpositive integers.

    + +
    +
    >>> sc.gammaln([0, -1, -2, -3, -4])
    +array([inf, inf, inf, inf, inf])
    +
    +
    + +

    It asymptotically approaches x * log(x) (Stirling's formula).

    + +
    +
    >>> x = np.array([1e10, 1e20, 1e40, 1e80])
    +>>> sc.gammaln(x)
    +array([2.20258509e+11, 4.50517019e+21, 9.11034037e+41, 1.83206807e+82])
    +>>> x * np.log(x)
    +array([2.30258509e+11, 4.60517019e+21, 9.21034037e+41, 1.84206807e+82])
    +
    +
    +
    + +
    @@ -2000,1436 +2523,6 @@ Dask ✅ n/a
    - -
    - -
    -
    @wraps(f_raw)
    - - def - rgamma(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    rgamma(z, out=None)

    - -

    Reciprocal of the gamma function.

    - -

    Defined as \( 1 / \Gamma(z) \), where \( \Gamma \) is the -gamma function. For more on the gamma function see gamma.

    - -
    Parameters
    - -
      -
    • z (array_like): -Real or complex valued input
    • -
    • out (ndarray, optional): -Optional output array for the function results
    • -
    - -
    Returns
    - -
      -
    • scalar or ndarray: Function results
    • -
    - -
    See Also
    - -

    gamma()`,`,gammaln(),, `loggamma() -..`

    - -
    Notes
    - -

    The gamma function has no zeros and has simple poles at -nonpositive integers, so rgamma is an entire function with zeros -at the nonpositive integers. See the discussion in [dlmf]_ for -more details.

    - -

    Array API Standard Support

    - -

    rgamma has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ⛔
    -JAX ✅ ⛔
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    References
    - -

    .. [dlmf] Nist, Digital Library of Mathematical functions, - https://dlmf.nist.gov/5.2#i

    - -
    Examples
    - -
    -
    >>> import scipy.special as sc
    -
    -
    - -

    It is the reciprocal of the gamma function.

    - -
    -
    >>> sc.rgamma([1, 2, 3, 4])
    -array([1.        , 1.        , 0.5       , 0.16666667])
    ->>> 1 / sc.gamma([1, 2, 3, 4])
    -array([1.        , 1.        , 0.5       , 0.16666667])
    -
    -
    - -

    It is zero at nonpositive integers.

    - -
    -
    >>> sc.rgamma([0, -1, -2, -3])
    -array([0., 0., 0., 0.])
    -
    -
    - -

    It rapidly underflows to zero along the positive real axis.

    - -
    -
    >>> sc.rgamma([10, 100, 179])
    -array([2.75573192e-006, 1.07151029e-156, 0.00000000e+000])
    -
    -
    -
    - - -
    -
    - -
    -
    @wraps(f_raw)
    - - def - multigammaln(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    Returns the log of multivariate gamma, also sometimes called the -generalized gamma.

    - -
    Parameters
    - -
      -
    • a (ndarray): -The multivariate gamma is computed for each item of a.
    • -
    • d (int): -The dimension of the space of integration.
    • -
    - -
    Returns
    - -
      -
    • res (ndarray): -The values of the log multivariate gamma at the given points a.
    • -
    - -
    Notes
    - -

    The formal definition of the multivariate gamma of dimension d for a real -a is

    - -

    $$\Gamma_d(a) = \int_{A>0} e^{-tr(A)} |A|^{a - (d+1)/2} dA$$

    - -

    with the condition \( a > (d-1)/2 \), and \( A > 0 \) being the set of -all the positive definite matrices of dimension d. Note that a is a -scalar: the integrand only is multivariate, the argument is not (the -function is defined over a subset of the real set).

    - -

    This can be proven to be equal to the much friendlier equation

    - -

    $$\Gamma_d(a) = \pi^{d(d-1)/4} \prod_{i=1}^{d} \Gamma(a - (i-1)/2).$$

    - -

    Array API Standard Support

    - -

    multigammaln has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ✅
    -JAX ✅ ✅
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    References
    - -

    R. J. Muirhead, Aspects of multivariate statistical theory (Wiley Series in -probability and mathematical statistics).

    - -
    Examples
    - -
    -
    >>> import numpy as np
    ->>> from scipy.special import multigammaln, gammaln
    ->>> a = 23.5
    ->>> d = 10
    ->>> multigammaln(a, d)
    -454.1488605074416
    -
    -
    - -

    Verify that the result agrees with the logarithm of the equation -shown above:

    - -
    -
    >>> d*(d-1)/4*np.log(np.pi) + gammaln(a - 0.5*np.arange(0, d)).sum()
    -454.1488605074416
    -
    -
    -
    - - -
    -
    - -
    -
    @wraps(f_raw)
    - - def - kn(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    Modified Bessel function of the second kind of integer order n

    -
    - - -
    -
    - -
    -
    @wraps(f_raw)
    - - def - j0(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    j0(x, out=None)

    - -

    Bessel function of the first kind of order 0.

    - -
    Parameters
    - -
      -
    • x (array_like): -Argument (float).
    • -
    • out (ndarray, optional): -Optional output array for the function values
    • -
    - -
    Returns
    - -
      -
    • J (scalar or ndarray): -Value of the Bessel function of the first kind of order 0 at x.
    • -
    - -
    See Also
    - -

    jv()` -Bessel`, `function`, `of`, `real`, `order`, `and`, `complex`, `argument.` -spherical_jn() -spherical, Bessel, functions.

    - -
    Notes
    - -

    The domain is divided into the intervals [0, 5] and (5, infinity). In the -first interval the following rational approximation is used:

    - -

    $$J_0(x) \approx (w - r_1^2)(w - r_2^2) \frac{P_3(w)}{Q_8(w)},$$

    - -

    where \( w = x^2 \) and \( r_1 \), \( r_2 \) are the zeros of -\( J_0 \), and \( P_3 \) and \( Q_8 \) are polynomials of degrees 3 -and 8, respectively.

    - -

    In the second interval, the Hankel asymptotic expansion is employed with -two rational functions of degree 6/6 and 7/7.

    - -

    This function is a wrapper for the Cephes 1 routine j0. -It should not be confused with the spherical Bessel functions (see -spherical_jn).

    - -

    Array API Standard Support

    - -

    j0 has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ✅
    -JAX ✅ ⛔
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    References
    - -
    Examples
    - -

    Calculate the function at one point:

    - -
    -
    >>> from scipy.special import j0
    ->>> j0(1.)
    -0.7651976865579665
    -
    -
    - -

    Calculate the function at several points:

    - -
    -
    >>> import numpy as np
    ->>> j0(np.array([-2., 0., 4.]))
    -array([ 0.22389078,  1.        , -0.39714981])
    -
    -
    - -

    Plot the function from -20 to 20.

    - -
    -
    >>> import matplotlib.pyplot as plt
    ->>> fig, ax = plt.subplots()
    ->>> x = np.linspace(-20., 20., 1000)
    ->>> y = j0(x)
    ->>> ax.plot(x, y)
    ->>> plt.show()
    -
    -
    - -
    -
    -
      -
    1. -

      Cephes Mathematical Functions Library, -http://www.netlib.org/cephes/ 

      -
    2. -
    -
    -
    - - -
    -
    - -
    -
    @wraps(f_raw)
    - - def - y0(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    y0(x, out=None)

    - -

    Bessel function of the second kind of order 0.

    - -
    Parameters
    - -
      -
    • x (array_like): -Argument (float).
    • -
    • out (ndarray, optional): -Optional output array for the function results
    • -
    - -
    Returns
    - -
      -
    • Y (scalar or ndarray): -Value of the Bessel function of the second kind of order 0 at x.
    • -
    - -
    See Also
    - -

    j0()` -Bessel`, `function`, `of`, `the`, `first`, `kind`, `of`, `order`, `0` -yv() -Bessel, function, of, the, first, kind

    - -
    Notes
    - -

    The domain is divided into the intervals [0, 5] and (5, infinity). In the -first interval a rational approximation \( R(x) \) is employed to -compute,

    - -

    $$Y_0(x) = R(x) + \frac{2 \log(x) J_0(x)}{\pi},$$

    - -

    where \( J_0 \) is the Bessel function of the first kind of order 0.

    - -

    In the second interval, the Hankel asymptotic expansion is employed with -two rational functions of degree 6/6 and 7/7.

    - -

    This function is a wrapper for the Cephes 1 routine y0.

    - -

    Array API Standard Support

    - -

    y0 has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ✅
    -JAX ✅ ⛔
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    References
    - -
    Examples
    - -

    Calculate the function at one point:

    - -
    -
    >>> from scipy.special import y0
    ->>> y0(1.)
    -0.08825696421567697
    -
    -
    - -

    Calculate at several points:

    - -
    -
    >>> import numpy as np
    ->>> y0(np.array([0.5, 2., 3.]))
    -array([-0.44451873,  0.51037567,  0.37685001])
    -
    -
    - -

    Plot the function from 0 to 10.

    - -
    -
    >>> import matplotlib.pyplot as plt
    ->>> fig, ax = plt.subplots()
    ->>> x = np.linspace(0., 10., 1000)
    ->>> y = y0(x)
    ->>> ax.plot(x, y)
    ->>> plt.show()
    -
    -
    - -
    -
    -
      -
    1. -

      Cephes Mathematical Functions Library, -http://www.netlib.org/cephes/ 

      -
    2. -
    -
    -
    - - -
    -
    - -
    -
    @wraps(f_raw)
    - - def - j1(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    j1(x, out=None)

    - -

    Bessel function of the first kind of order 1.

    - -
    Parameters
    - -
      -
    • x (array_like): -Argument (float).
    • -
    • out (ndarray, optional): -Optional output array for the function values
    • -
    - -
    Returns
    - -
      -
    • J (scalar or ndarray): -Value of the Bessel function of the first kind of order 1 at x.
    • -
    - -
    See Also
    - -

    jv()` -Bessel`, `function`, `of`, `the`, `first`, `kind` -spherical_jn() -spherical, Bessel, functions.

    - -
    Notes
    - -

    The domain is divided into the intervals [0, 8] and (8, infinity). In the -first interval a 24 term Chebyshev expansion is used. In the second, the -asymptotic trigonometric representation is employed using two rational -functions of degree 5/5.

    - -

    This function is a wrapper for the Cephes 1 routine j1. -It should not be confused with the spherical Bessel functions (see -spherical_jn).

    - -

    Array API Standard Support

    - -

    j1 has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ✅
    -JAX ✅ ⛔
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    References
    - -
    Examples
    - -

    Calculate the function at one point:

    - -
    -
    >>> from scipy.special import j1
    ->>> j1(1.)
    -0.44005058574493355
    -
    -
    - -

    Calculate the function at several points:

    - -
    -
    >>> import numpy as np
    ->>> j1(np.array([-2., 0., 4.]))
    -array([-0.57672481,  0.        , -0.06604333])
    -
    -
    - -

    Plot the function from -20 to 20.

    - -
    -
    >>> import matplotlib.pyplot as plt
    ->>> fig, ax = plt.subplots()
    ->>> x = np.linspace(-20., 20., 1000)
    ->>> y = j1(x)
    ->>> ax.plot(x, y)
    ->>> plt.show()
    -
    -
    - -
    -
    -
      -
    1. -

      Cephes Mathematical Functions Library, -http://www.netlib.org/cephes/ 

      -
    2. -
    -
    -
    - - -
    -
    - -
    -
    @wraps(f_raw)
    - - def - y1(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    y1(x, out=None)

    - -

    Bessel function of the second kind of order 1.

    - -
    Parameters
    - -
      -
    • x (array_like): -Argument (float).
    • -
    • out (ndarray, optional): -Optional output array for the function results
    • -
    - -
    Returns
    - -
      -
    • Y (scalar or ndarray): -Value of the Bessel function of the second kind of order 1 at x.
    • -
    - -
    See Also
    - -

    j1()` -Bessel`, `function`, `of`, `the`, `first`, `kind`, `of`, `order`, `1` -yn() -Bessel, function, of, the, second, kind
    -`yv() -Bessel,function,of,the,second,kind`

    - -
    Notes
    - -

    The domain is divided into the intervals [0, 8] and (8, infinity). In the -first interval a 25 term Chebyshev expansion is used, and computing -\( J_1 \) (the Bessel function of the first kind) is required. In the -second, the asymptotic trigonometric representation is employed using two -rational functions of degree 5/5.

    - -

    This function is a wrapper for the Cephes 1 routine y1.

    - -

    Array API Standard Support

    - -

    y1 has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ✅
    -JAX ✅ ⛔
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    References
    - -
    Examples
    - -

    Calculate the function at one point:

    - -
    -
    >>> from scipy.special import y1
    ->>> y1(1.)
    --0.7812128213002888
    -
    -
    - -

    Calculate at several points:

    - -
    -
    >>> import numpy as np
    ->>> y1(np.array([0.5, 2., 3.]))
    -array([-1.47147239, -0.10703243,  0.32467442])
    -
    -
    - -

    Plot the function from 0 to 10.

    - -
    -
    >>> import matplotlib.pyplot as plt
    ->>> fig, ax = plt.subplots()
    ->>> x = np.linspace(0., 10., 1000)
    ->>> y = y1(x)
    ->>> ax.plot(x, y)
    ->>> plt.show()
    -
    -
    - -
    -
    -
      -
    1. -

      Cephes Mathematical Functions Library, -http://www.netlib.org/cephes/ 

      -
    2. -
    -
    -
    - - -
    -
    - -
    -
    @wraps(f_raw)
    - - def - jn(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    jv(x1, x2, /, out=None, *, where=True, casting='same_kind', order='K', dtype=None, subok=True[, signature])

    - -

    jv(v, z, out=None)

    - -

    Bessel function of the first kind of real order and complex argument.

    - -
    Parameters
    - -
      -
    • v (array_like): -Order (float).
    • -
    • z (array_like): -Argument (float or complex).
    • -
    • out (ndarray, optional): -Optional output array for the function values
    • -
    - -
    Returns
    - -
      -
    • J (scalar or ndarray): -Value of the Bessel function, \( J_v(z) \).
    • -
    - -
    See Also
    - -

    jve: \( J_v \) with leading exponential behavior stripped off.
    -spherical_jn: spherical Bessel functions.
    -j0: faster version of this function for order 0.
    -j1: faster version of this function for order 1.

    - -
    Notes
    - -

    For positive v values, the computation is carried out using the AMOS -1 zbesj routine, which exploits the connection to the modified -Bessel function \( I_v \),

    - -

    $$J_v(z) = \exp(v\pi\imath/2) I_v(-\imath z)\qquad (\Im z > 0)

    - -

    J_v(z) = \exp(-v\pi\imath/2) I_v(\imath z)\qquad (\Im z < 0)$$

    - -

    For negative v values the formula,

    - -

    $$J_{-v}(z) = J_v(z) \cos(\pi v) - Y_v(z) \sin(\pi v)$$

    - -

    is used, where \( Y_v(z) \) is the Bessel function of the second -kind, computed using the AMOS routine zbesy. Note that the second -term is exactly zero for integer v; to improve accuracy the second -term is explicitly omitted for v values such that v = floor(v).

    - -

    Not to be confused with the spherical Bessel functions (see spherical_jn).

    - -
    References
    - -
    Examples
    - -

    Evaluate the function of order 0 at one point.

    - -
    -
    >>> from scipy.special import jv
    ->>> jv(0, 1.)
    -0.7651976865579666
    -
    -
    - -

    Evaluate the function at one point for different orders.

    - -
    -
    >>> jv(0, 1.), jv(1, 1.), jv(1.5, 1.)
    -(0.7651976865579666, 0.44005058574493355, 0.24029783912342725)
    -
    -
    - -

    The evaluation for different orders can be carried out in one call by -providing a list or NumPy array as argument for the v parameter:

    - -
    -
    >>> jv([0, 1, 1.5], 1.)
    -array([0.76519769, 0.44005059, 0.24029784])
    -
    -
    - -

    Evaluate the function at several points for order 0 by providing an -array for z.

    - -
    -
    >>> import numpy as np
    ->>> points = np.array([-2., 0., 3.])
    ->>> jv(0, points)
    -array([ 0.22389078,  1.        , -0.26005195])
    -
    -
    - -

    If z is an array, the order parameter v must be broadcastable to -the correct shape if different orders shall be computed in one call. -To calculate the orders 0 and 1 for a 1D array:

    - -
    -
    >>> orders = np.array([[0], [1]])
    ->>> orders.shape
    -(2, 1)
    -
    -
    - -
    -
    >>> jv(orders, points)
    -array([[ 0.22389078,  1.        , -0.26005195],
    -       [-0.57672481,  0.        ,  0.33905896]])
    -
    -
    - -

    Plot the functions of order 0 to 3 from -10 to 10.

    - -
    -
    >>> import matplotlib.pyplot as plt
    ->>> fig, ax = plt.subplots()
    ->>> x = np.linspace(-10., 10., 1000)
    ->>> for i in range(4):
    -...     ax.plot(x, jv(i, x), label=f'$J_{i!r}$')
    ->>> ax.legend()
    ->>> plt.show()
    -
    -
    - -
    -
    -
      -
    1. -

      Donald E. Amos, "AMOS, A Portable Package for Bessel Functions -of a Complex Argument and Nonnegative Order", -http://netlib.org/amos/ 

      -
    2. -
    -
    -
    - - -
    -
    - -
    -
    @wraps(f_raw)
    - - def - yn(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    yn(n, x, out=None)

    - -

    Bessel function of the second kind of integer order and real argument.

    - -
    Parameters
    - -
      -
    • n (array_like): -Order (integer).
    • -
    • x (array_like): -Argument (float).
    • -
    • out (ndarray, optional): -Optional output array for the function results
    • -
    - -
    Returns
    - -
      -
    • Y (scalar or ndarray): -Value of the Bessel function, \( Y_n(x) \).
    • -
    - -
    See Also
    - -

    yv()` -For`, `real`, `order`, `and`, `real`, `or`, `complex`, `argument.` -y0() -faster, implementation, of, this, function, for, order, 0
    -`y1() -faster,implementation,of,this,function,for,order,1`

    - -
    Notes
    - -

    Wrapper for the Cephes 1 routine yn.

    - -

    The function is evaluated by forward recurrence on n, starting with -values computed by the Cephes routines y0 and y1. If n = 0 or 1, -the routine for y0 or y1 is called directly.

    - -

    Array API Standard Support

    - -

    yn has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ⛔
    -JAX ✅ ⛔
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    References
    - -
    Examples
    - -

    Evaluate the function of order 0 at one point.

    - -
    -
    >>> from scipy.special import yn
    ->>> yn(0, 1.)
    -0.08825696421567697
    -
    -
    - -

    Evaluate the function at one point for different orders.

    - -
    -
    >>> yn(0, 1.), yn(1, 1.), yn(2, 1.)
    -(0.08825696421567697, -0.7812128213002888, -1.6506826068162546)
    -
    -
    - -

    The evaluation for different orders can be carried out in one call by -providing a list or NumPy array as argument for the v parameter:

    - -
    -
    >>> yn([0, 1, 2], 1.)
    -array([ 0.08825696, -0.78121282, -1.65068261])
    -
    -
    - -

    Evaluate the function at several points for order 0 by providing an -array for z.

    - -
    -
    >>> import numpy as np
    ->>> points = np.array([0.5, 3., 8.])
    ->>> yn(0, points)
    -array([-0.44451873,  0.37685001,  0.22352149])
    -
    -
    - -

    If z is an array, the order parameter v must be broadcastable to -the correct shape if different orders shall be computed in one call. -To calculate the orders 0 and 1 for a 1D array:

    - -
    -
    >>> orders = np.array([[0], [1]])
    ->>> orders.shape
    -(2, 1)
    -
    -
    - -
    -
    >>> yn(orders, points)
    -array([[-0.44451873,  0.37685001,  0.22352149],
    -       [-1.47147239,  0.32467442, -0.15806046]])
    -
    -
    - -

    Plot the functions of order 0 to 3 from 0 to 10.

    - -
    -
    >>> import matplotlib.pyplot as plt
    ->>> fig, ax = plt.subplots()
    ->>> x = np.linspace(0., 10., 1000)
    ->>> for i in range(4):
    -...     ax.plot(x, yn(i, x), label=f'$Y_{i!r}$')
    ->>> ax.set_ylim(-3, 1)
    ->>> ax.legend()
    ->>> plt.show()
    -
    -
    - -
    -
    -
      -
    1. -

      Cephes Mathematical Functions Library, -https://netlib.org/cephes/ 

      -
    2. -
    -
    -
    - -
    @@ -4167,354 +3260,74 @@ of a Complex Argument and Nonnegative Order",
    -
    - +
    +
    @wraps(f_raw)
    def - erf(*args, called_by_autograd_dispatcher=False, **kwargs): + j0(*args, called_by_autograd_dispatcher=False, **kwargs): - +
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    +    
    +            
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
     
    -

    erf(z, out=None)

    +

    j0(x, out=None)

    -

    Error function of real or complex argument.

    - -

    $$\operatorname{erf}(z) = \frac{2}{\sqrt{\pi}} \int_0^z e^{-t^2} dt$$

    - -
    Parameters
    - -
      -
    • z (ndarray): -Input array.
    • -
    • out (ndarray, optional): -Optional output array for the function values.
    • -
    - -
    Returns
    - -
      -
    • res (scalar or ndarray): -The values of the error function at the given points z.
    • -
    - -
    See Also
    - -

    erfc()`,`,erfcx(),, erfi()`,`,erfinv(),, erfcinv()`,`,wofz() -..

    - -
    Notes
    - -

    The cumulative distribution function (CDF) of the standard normal distribution can -be expressed in terms of the error function as

    - -

    $$\Phi(z) = \frac{1}{2} -\left[1 + \operatorname{erf} \left(\frac{z}{\sqrt{2}}\right)\right]$$

    - -

    Array API Standard Support

    - -

    erf has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ✅
    -JAX ✅ ✅
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    References
    - -
    Examples
    - -
    -
    >>> import numpy as np
    ->>> from scipy import special
    ->>> import matplotlib.pyplot as plt
    ->>> z = np.linspace(-3, 3)
    ->>> plt.plot(z, special.erf(z))
    ->>> plt.xlabel('$z$')
    ->>> plt.ylabel('$erf(z)$')
    ->>> plt.show()
    -
    -
    - -
    -
    -
      -
    -
    -
    - - -
    -
    - -
    -
    @wraps(f_raw)
    - - def - erfc(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    erfc(x, out=None)

    - -

    Complementary error function.

    - -

    The complementary error function is defined as

    - -

    $$\operatorname{erfc}(x) = 1 - \operatorname{erf}(x)$$

    +

    Bessel function of the first kind of order 0.

    Parameters
    • x (array_like): -Real or complex valued argument
    • -
    • out (ndarray, optional): -Optional output array for the function results
    • -
    - -
    Returns
    - -
      -
    • scalar or ndarray: Values of the complementary error function
    • -
    - -
    See Also
    - -

    erf()`,`,erfi(),, erfcx()`,`,dawsn(),, `wofz() -..`

    - -
    Notes
    - -

    Array API Standard Support

    - -

    erfc has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ✅
    -JAX ✅ ✅
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    References
    - -
    Examples
    - -
    -
    >>> import numpy as np
    ->>> from scipy import special
    ->>> import matplotlib.pyplot as plt
    ->>> x = np.linspace(-3, 3)
    ->>> plt.plot(x, special.erfc(x))
    ->>> plt.xlabel('$x$')
    ->>> plt.ylabel('$erfc(x)$')
    ->>> plt.show()
    -
    -
    - -
    -
    -
      -
    -
    -
    - - -
    -
    - -
    -
    @wraps(f_raw)
    - - def - erfinv(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    erfinv(y, out=None)

    - -

    Inverse of the error function.

    - -

    Computes the inverse of the error function.

    - -

    In the complex domain, there is no unique complex number w satisfying -erf(w)=z. This indicates a true inverse function would be multivalued. -When the domain restricts to the real, -1 < x < 1, there is a unique real -number satisfying erf(erfinv(x)) = x.

    - -
    Parameters
    - -
      -
    • y (ndarray): -Argument at which to evaluate. Domain: [-1, 1]
    • +Argument (float).
    • out (ndarray, optional): Optional output array for the function values
    @@ -4522,27 +3335,38 @@ Optional output array for the function values
    Returns
      -
    • erfinv (scalar or ndarray): -The inverse of erf of y, element-wise
    • +
    • J (scalar or ndarray): +Value of the Bessel function of the first kind of order 0 at x.
    See Also
    -

    erf()` -Error`, `function`, `of`, `a`, `complex`, `argument` -erfc() -Complementary, error, function,, 1`, `-`, `erf(x)
    -`erfcinv() -Inverse,of,the,complementary,error,function`

    +

    jv()` +Bessel`, `function`, `of`, `real`, `order`, `and`, `complex`, `argument.` +spherical_jn() +spherical, Bessel, functions.

    Notes
    -

    This function wraps the erf_inv routine from the -Boost Math C++ library 1.

    +

    The domain is divided into the intervals [0, 5] and (5, infinity). In the +first interval the following rational approximation is used:

    + +

    $$J_0(x) \approx (w - r_1^2)(w - r_2^2) \frac{P_3(w)}{Q_8(w)},$$

    + +

    where \( w = x^2 \) and \( r_1 \), \( r_2 \) are the zeros of +\( J_0 \), and \( P_3 \) and \( Q_8 \) are polynomials of degrees 3 +and 8, respectively.

    + +

    In the second interval, the Hankel asymptotic expansion is employed with +two rational functions of degree 6/6 and 7/7.

    + +

    This function is a wrapper for the Cephes 1 routine j0. +It should not be confused with the spherical Bessel functions (see +spherical_jn).

    Array API Standard Support

    -

    erfinv has experimental support for Python Array API Standard compatible +

    j0 has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following @@ -4554,7 +3378,7 @@ Library CPU GPU NumPy ✅ n/a
    CuPy n/a ✅
    PyTorch ✅ ✅
    -JAX ✅ ✅
    +JAX ✅ ⛔
    Dask ✅ n/a
    ==================== ==================== ====================

    @@ -4564,45 +3388,32 @@ Dask ✅ n/a
    Examples
    +

    Calculate the function at one point:

    + +
    +
    >>> from scipy.special import j0
    +>>> j0(1.)
    +0.7651976865579665
    +
    +
    + +

    Calculate the function at several points:

    +
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from scipy.special import erfinv, erf
    +>>> j0(np.array([-2., 0., 4.]))
    +array([ 0.22389078,  1.        , -0.39714981])
     
    -
    -
    >>> erfinv(0.5)
    -0.4769362762044699
    -
    -
    +

    Plot the function from -20 to 20.

    -
    >>> y = np.linspace(-1.0, 1.0, num=9)
    ->>> x = erfinv(y)
    ->>> x
    -array([       -inf, -0.81341985, -0.47693628, -0.22531206,  0.        ,
    -        0.22531206,  0.47693628,  0.81341985,         inf])
    -
    -
    - -

    Verify that erf(erfinv(y)) is y.

    - -
    -
    >>> erf(x)
    -array([-1.  , -0.75, -0.5 , -0.25,  0.  ,  0.25,  0.5 ,  0.75,  1.  ])
    -
    -
    - -

    Plot the function:

    - -
    -
    >>> y = np.linspace(-1, 1, 200)
    +
    >>> import matplotlib.pyplot as plt
     >>> fig, ax = plt.subplots()
    ->>> ax.plot(y, erfinv(y))
    ->>> ax.grid(True)
    ->>> ax.set_xlabel('y')
    ->>> ax.set_title('erfinv(y)')
    +>>> x = np.linspace(-20., 20., 1000)
    +>>> y = j0(x)
    +>>> ax.plot(x, y)
     >>> plt.show()
     
    @@ -4611,7 +3422,8 @@ Dask ✅ n/a

    1. -

      The Boost Developers. "Boost C++ Libraries". https://www.boost.org/

      +

      Cephes Mathematical Functions Library, +http://www.netlib.org/cephes/ 

    @@ -4619,90 +3431,74 @@ Dask ✅ n/a
    -
    - +
    +
    @wraps(f_raw)
    def - erfcinv(*args, called_by_autograd_dispatcher=False, **kwargs): + j1(*args, called_by_autograd_dispatcher=False, **kwargs): - +
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    +    
    +            
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
     
    -

    erfcinv(x, /, out=None, *, where=True, casting='same_kind', order='K', dtype=None, subok=True[, signature])

    +

    j1(x, out=None)

    -

    erfcinv(y, out=None)

    - -

    Inverse of the complementary error function.

    - -

    Computes the inverse of the complementary error function.

    - -

    In the complex domain, there is no unique complex number \( w \) satisfying -\( \operatorname{erfc}(w) = z \). This indicates a true inverse function -would be multivalued. -When the domain restricts to the real interval \( 0 < x < 2 \), there is -a unique real number satisfying

    - -

    $$\operatorname{erfc}(\operatorname{erfcinv}(x)) = x$$

    - -

    It is related to the inverse of the error function by

    - -

    $$\operatorname{erfcinv}(1 - x) = \operatorname{erfinv}(x)$$

    +

    Bessel function of the first kind of order 1.

    Parameters
      -
    • y (ndarray): -Argument at which to evaluate. Domain: \( [0, 2] \)
    • +
    • x (array_like): +Argument (float).
    • out (ndarray, optional): Optional output array for the function values
    @@ -4710,52 +3506,352 @@ Optional output array for the function values
    Returns
      -
    • erfcinv (scalar or ndarray): -The inverse of \( \operatorname{erfc} \) of \( y \), element-wise
    • +
    • J (scalar or ndarray): +Value of the Bessel function of the first kind of order 1 at x.
    See Also
    -

    erf: Error function
    -erfc: Complementary error function
    -erfinv: Inverse of the error function

    +

    jv()` +Bessel`, `function`, `of`, `the`, `first`, `kind` +spherical_jn() +spherical, Bessel, functions.

    + +
    Notes
    + +

    The domain is divided into the intervals [0, 8] and (8, infinity). In the +first interval a 24 term Chebyshev expansion is used. In the second, the +asymptotic trigonometric representation is employed using two rational +functions of degree 5/5.

    + +

    This function is a wrapper for the Cephes 1 routine j1. +It should not be confused with the spherical Bessel functions (see +spherical_jn).

    + +

    Array API Standard Support

    + +

    j1 has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ✅
    +JAX ✅ ⛔
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    References
    Examples
    +

    Calculate the function at one point:

    + +
    +
    >>> from scipy.special import j1
    +>>> j1(1.)
    +0.44005058574493355
    +
    +
    + +

    Calculate the function at several points:

    +
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from scipy.special import erfcinv
    +>>> j1(np.array([-2., 0., 4.]))
    +array([-0.57672481,  0.        , -0.06604333])
     
    -
    -
    >>> erfcinv(0.5)
    -0.4769362762044699
    -
    -
    +

    Plot the function from -20 to 20.

    -
    >>> y = np.linspace(0.0, 2.0, num=11)
    ->>> erfcinv(y)
    -array([        inf,  0.9061938 ,  0.59511608,  0.37080716,  0.17914345,
    -       -0.        , -0.17914345, -0.37080716, -0.59511608, -0.9061938 ,
    -              -inf])
    -
    -
    - -

    Plot the function:

    - -
    -
    >>> y = np.linspace(0, 2, 200)
    +
    >>> import matplotlib.pyplot as plt
     >>> fig, ax = plt.subplots()
    ->>> ax.plot(y, erfcinv(y))
    ->>> ax.grid(True)
    ->>> ax.set_xlabel('y')
    ->>> ax.set_title('erfcinv(y)')
    +>>> x = np.linspace(-20., 20., 1000)
    +>>> y = j1(x)
    +>>> ax.plot(x, y)
     >>> plt.show()
     
    + +
    +
    +
      +
    1. +

      Cephes Mathematical Functions Library, +http://www.netlib.org/cephes/ 

      +
    2. +
    +
    +
    + + +
    +
    + +
    +
    @wraps(f_raw)
    + + def + jn(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    jv(x1, x2, /, out=None, *, where=True, casting='same_kind', order='K', dtype=None, subok=True[, signature])

    + +

    jv(v, z, out=None)

    + +

    Bessel function of the first kind of real order and complex argument.

    + +
    Parameters
    + +
      +
    • v (array_like): +Order (float).
    • +
    • z (array_like): +Argument (float or complex).
    • +
    • out (ndarray, optional): +Optional output array for the function values
    • +
    + +
    Returns
    + +
      +
    • J (scalar or ndarray): +Value of the Bessel function, \( J_v(z) \).
    • +
    + +
    See Also
    + +

    jve: \( J_v \) with leading exponential behavior stripped off.
    +spherical_jn: spherical Bessel functions.
    +j0: faster version of this function for order 0.
    +j1: faster version of this function for order 1.

    + +
    Notes
    + +

    For positive v values, the computation is carried out using the AMOS +1 zbesj routine, which exploits the connection to the modified +Bessel function \( I_v \),

    + +

    $$J_v(z) = \exp(v\pi\imath/2) I_v(-\imath z)\qquad (\Im z > 0)

    + +

    J_v(z) = \exp(-v\pi\imath/2) I_v(\imath z)\qquad (\Im z < 0)$$

    + +

    For negative v values the formula,

    + +

    $$J_{-v}(z) = J_v(z) \cos(\pi v) - Y_v(z) \sin(\pi v)$$

    + +

    is used, where \( Y_v(z) \) is the Bessel function of the second +kind, computed using the AMOS routine zbesy. Note that the second +term is exactly zero for integer v; to improve accuracy the second +term is explicitly omitted for v values such that v = floor(v).

    + +

    Not to be confused with the spherical Bessel functions (see spherical_jn).

    + +
    References
    + +
    Examples
    + +

    Evaluate the function of order 0 at one point.

    + +
    +
    >>> from scipy.special import jv
    +>>> jv(0, 1.)
    +0.7651976865579666
    +
    +
    + +

    Evaluate the function at one point for different orders.

    + +
    +
    >>> jv(0, 1.), jv(1, 1.), jv(1.5, 1.)
    +(0.7651976865579666, 0.44005058574493355, 0.24029783912342725)
    +
    +
    + +

    The evaluation for different orders can be carried out in one call by +providing a list or NumPy array as argument for the v parameter:

    + +
    +
    >>> jv([0, 1, 1.5], 1.)
    +array([0.76519769, 0.44005059, 0.24029784])
    +
    +
    + +

    Evaluate the function at several points for order 0 by providing an +array for z.

    + +
    +
    >>> import numpy as np
    +>>> points = np.array([-2., 0., 3.])
    +>>> jv(0, points)
    +array([ 0.22389078,  1.        , -0.26005195])
    +
    +
    + +

    If z is an array, the order parameter v must be broadcastable to +the correct shape if different orders shall be computed in one call. +To calculate the orders 0 and 1 for a 1D array:

    + +
    +
    >>> orders = np.array([[0], [1]])
    +>>> orders.shape
    +(2, 1)
    +
    +
    + +
    +
    >>> jv(orders, points)
    +array([[ 0.22389078,  1.        , -0.26005195],
    +       [-0.57672481,  0.        ,  0.33905896]])
    +
    +
    + +

    Plot the functions of order 0 to 3 from -10 to 10.

    + +
    +
    >>> import matplotlib.pyplot as plt
    +>>> fig, ax = plt.subplots()
    +>>> x = np.linspace(-10., 10., 1000)
    +>>> for i in range(4):
    +...     ax.plot(x, jv(i, x), label=f'$J_{i!r}$')
    +>>> ax.legend()
    +>>> plt.show()
    +
    +
    + +
    +
    +
      +
    1. +

      Donald E. Amos, "AMOS, A Portable Package for Bessel Functions +of a Complex Argument and Nonnegative Order", +http://netlib.org/amos/ 

      +
    2. +
    +
    +
    + + +
    +
    + +
    +
    @wraps(f_raw)
    + + def + kn(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    Modified Bessel function of the second kind of integer order n

    @@ -4915,162 +4011,6 @@ Dask ✅ n/a
    - -
    - -
    -
    @wraps(f_raw)
    - - def - expit(*args, called_by_autograd_dispatcher=False, **kwargs): - - - -
    - -
    51    @wraps(f_raw)
    -52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    -53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    -54        if boxed_args:
    -55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    -56            # argument, then first forward further handling to the ufunc dispatching mechanism
    -57            # (if we aren't already running inside it after being called by the ArrayBox
    -58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    -59            # to also try to handle the call.
    -60            #
    -61            # (It's possible our handling attempt below will get the first shot; the handlers
    -62            # order is determined by the dispatch mechanism. Also, if no other array-like
    -63            # arguments are interested in handling the ufunc, then we don't defer to the
    -64            # dispatch mechanism because there is no point).
    -65            #
    -66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    -67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    -68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    -69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    -70            # handling of the call might succeed: it might contain an ndarray, either
    -71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    -72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    -73            # DataArray.
    -74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    -75                return f_raw(*args, **kwargs)
    -76
    -77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    -78            if f_wrapped in notrace_primitives[node_constructor]:
    -79                return f_wrapped(
    -80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    -81                )
    -82            parents = tuple(box._node for _, box in boxed_args)
    -83            argnums = tuple(argnum for argnum, _ in boxed_args)
    -84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    -85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    -86            try:
    -87                box = new_box(ans, trace, node)
    -88                return box
    -89            except Exception as e:
    -90                if called_by_autograd_dispatcher:
    -91                    raise NotImplementedError from e
    -92                raise
    -93        else:
    -94            return f_raw(*args, **kwargs)
    -
    - - -

    expit(x, out=None)

    - -

    Expit (also known as logistic sigmoid) ufunc for ndarrays.

    - -

    The expit function, also known as the logistic sigmoid function, is -defined as expit(x) = 1/(1+exp(-x)). It is the inverse of the -logit function.

    - -
    Parameters
    - -
      -
    • x (ndarray): -The ndarray to apply expit to element-wise.
    • -
    • out (ndarray, optional): -Optional output array for the function values
    • -
    - -
    Returns
    - -
      -
    • scalar or ndarray: An ndarray of the same shape as x. Its entries -are expit of the corresponding entry of x.
    • -
    - -
    See Also
    - -

    `logit() -..`

    - -
    Notes
    - -

    As a ufunc expit takes a number of optional -keyword arguments. For more information -see ufuncs

    - -

    New in version 0.10.0.

    - -

    Array API Standard Support

    - -

    expit has experimental support for Python Array API Standard compatible -backends in addition to NumPy. Please consider testing these features -by setting an environment variable SCIPY_ARRAY_API=1 and providing -CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following -combinations of backend and device (or other capability) are supported.

    - -

    ==================== ==================== ==================== -Library CPU GPU -==================== ==================== ==================== -NumPy ✅ n/a
    -CuPy n/a ✅
    -PyTorch ✅ ✅
    -JAX ✅ ✅
    -Dask ✅ n/a
    -==================== ==================== ====================

    - -

    See :ref:dev-arrayapi for more information.

    - -
    Examples
    - -
    -
    >>> import numpy as np
    ->>> from scipy.special import expit, logit
    -
    -
    - -
    -
    >>> expit([-np.inf, -1.5, 0, 1.5, np.inf])
    -array([ 0.        ,  0.18242552,  0.5       ,  0.81757448,  1.        ])
    -
    -
    - -

    logit is the inverse of expit:

    - -
    -
    >>> logit(expit([-2.5, 0, 3.1, 5.0]))
    -array([-2.5,  0. ,  3.1,  5. ])
    -
    -
    - -

    Plot expit(x) for x in [-6, 6]:

    - -
    -
    >>> import matplotlib.pyplot as plt
    ->>> x = np.linspace(-6, 6, 121)
    ->>> y = expit(x)
    ->>> plt.plot(x, y)
    ->>> plt.grid()
    ->>> plt.xlim(-6, 6)
    ->>> plt.xlabel('x')
    ->>> plt.title('expit(x)')
    ->>> plt.show()
    -
    -
    -
    - -
    @@ -5263,6 +4203,1123 @@ on a masked array, convert the mask into zero weights:

    + +
    + +
    +
    @wraps(f_raw)
    + + def + multigammaln(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    Returns the log of multivariate gamma, also sometimes called the +generalized gamma.

    + +
    Parameters
    + +
      +
    • a (ndarray): +The multivariate gamma is computed for each item of a.
    • +
    • d (int): +The dimension of the space of integration.
    • +
    + +
    Returns
    + +
      +
    • res (ndarray): +The values of the log multivariate gamma at the given points a.
    • +
    + +
    Notes
    + +

    The formal definition of the multivariate gamma of dimension d for a real +a is

    + +

    $$\Gamma_d(a) = \int_{A>0} e^{-tr(A)} |A|^{a - (d+1)/2} dA$$

    + +

    with the condition \( a > (d-1)/2 \), and \( A > 0 \) being the set of +all the positive definite matrices of dimension d. Note that a is a +scalar: the integrand only is multivariate, the argument is not (the +function is defined over a subset of the real set).

    + +

    This can be proven to be equal to the much friendlier equation

    + +

    $$\Gamma_d(a) = \pi^{d(d-1)/4} \prod_{i=1}^{d} \Gamma(a - (i-1)/2).$$

    + +

    Array API Standard Support

    + +

    multigammaln has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ✅
    +JAX ✅ ✅
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    References
    + +

    R. J. Muirhead, Aspects of multivariate statistical theory (Wiley Series in +probability and mathematical statistics).

    + +
    Examples
    + +
    +
    >>> import numpy as np
    +>>> from scipy.special import multigammaln, gammaln
    +>>> a = 23.5
    +>>> d = 10
    +>>> multigammaln(a, d)
    +454.1488605074416
    +
    +
    + +

    Verify that the result agrees with the logarithm of the equation +shown above:

    + +
    +
    >>> d*(d-1)/4*np.log(np.pi) + gammaln(a - 0.5*np.arange(0, d)).sum()
    +454.1488605074416
    +
    +
    +
    + + +
    +
    + +
    +
    @wraps(f_raw)
    + + def + polygamma(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    Polygamma functions.

    + +

    Defined as \( \psi^{(n)}(x) \) where \( \psi \) is the +digamma function. See [dlmf]_ for details.

    + +
    Parameters
    + +
      +
    • n (array_like): +The order of the derivative of the digamma function; must be +integral
    • +
    • x (array_like): +Real valued input
    • +
    + +
    Returns
    + +
      +
    • ndarray: Function results
    • +
    + +
    See Also
    + +

    `digamma() +..`

    + +
    Notes
    + +

    Array API Standard Support

    + +

    polygamma has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ✅
    +JAX ✅ ✅
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    References
    + +

    .. [dlmf] NIST, Digital Library of Mathematical Functions, + https://dlmf.nist.gov/5.15

    + +
    Examples
    + +
    +
    >>> from scipy import special
    +>>> x = [2, 3, 25.5]
    +>>> special.polygamma(1, x)
    +array([ 0.64493407,  0.39493407,  0.03999467])
    +>>> special.polygamma(0, x) == special.psi(x)
    +array([ True,  True,  True], dtype=bool)
    +
    +
    +
    + + +
    +
    + +
    +
    @wraps(f_raw)
    + + def + psi(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    psi(z, out=None)

    + +

    The digamma function.

    + +

    The logarithmic derivative of the gamma function evaluated at z.

    + +
    Parameters
    + +
      +
    • z (array_like): +Real or complex argument.
    • +
    • out (ndarray, optional): +Array for the computed values of psi.
    • +
    + +
    Returns
    + +
      +
    • digamma (scalar or ndarray): +Computed values of psi.
    • +
    + +
    Notes
    + +

    For large values not close to the negative real axis, psi is +computed using the asymptotic series (5.11.2) from 1. For small +arguments not close to the negative real axis, the recurrence +relation (5.5.2) from 1 is used until the argument is large +enough to use the asymptotic series. For values close to the +negative real axis, the reflection formula (5.5.4) from 1 is +used first. Note that psi has a family of zeros on the +negative real axis which occur between the poles at nonpositive +integers. Around the zeros the reflection formula suffers from +cancellation and the implementation loses precision. The sole +positive zero and the first negative zero, however, are handled +separately by precomputing series expansions using 2, so the +function should maintain full accuracy around the origin.

    + +

    Array API Standard Support

    + +

    psi has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ✅
    +JAX ✅ ✅
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    References
    + +
    Examples
    + +
    +
    >>> from scipy.special import psi
    +>>> z = 3 + 4j
    +>>> psi(z)
    +(1.55035981733341+1.0105022091860445j)
    +
    +
    + +

    Verify psi(z) = psi(z + 1) - 1/z:

    + +
    +
    >>> psi(z + 1) - 1/z
    +(1.55035981733341+1.0105022091860445j)
    +
    +
    + +
    +
    +
      +
    1. +

      NIST Digital Library of Mathematical Functions +https://dlmf.nist.gov/5 

      +
    2. + +
    3. +

      Fredrik Johansson and others. +"mpmath: a Python library for arbitrary-precision floating-point arithmetic" +(Version 0.19) http://mpmath.org/ 

      +
    4. +
    +
    +
    + + +
    +
    + +
    +
    @wraps(f_raw)
    + + def + rgamma(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    rgamma(z, out=None)

    + +

    Reciprocal of the gamma function.

    + +

    Defined as \( 1 / \Gamma(z) \), where \( \Gamma \) is the +gamma function. For more on the gamma function see gamma.

    + +
    Parameters
    + +
      +
    • z (array_like): +Real or complex valued input
    • +
    • out (ndarray, optional): +Optional output array for the function results
    • +
    + +
    Returns
    + +
      +
    • scalar or ndarray: Function results
    • +
    + +
    See Also
    + +

    gamma()`,`,gammaln(),, `loggamma() +..`

    + +
    Notes
    + +

    The gamma function has no zeros and has simple poles at +nonpositive integers, so rgamma is an entire function with zeros +at the nonpositive integers. See the discussion in [dlmf]_ for +more details.

    + +

    Array API Standard Support

    + +

    rgamma has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ⛔
    +JAX ✅ ⛔
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    References
    + +

    .. [dlmf] Nist, Digital Library of Mathematical functions, + https://dlmf.nist.gov/5.2#i

    + +
    Examples
    + +
    +
    >>> import scipy.special as sc
    +
    +
    + +

    It is the reciprocal of the gamma function.

    + +
    +
    >>> sc.rgamma([1, 2, 3, 4])
    +array([1.        , 1.        , 0.5       , 0.16666667])
    +>>> 1 / sc.gamma([1, 2, 3, 4])
    +array([1.        , 1.        , 0.5       , 0.16666667])
    +
    +
    + +

    It is zero at nonpositive integers.

    + +
    +
    >>> sc.rgamma([0, -1, -2, -3])
    +array([0., 0., 0., 0.])
    +
    +
    + +

    It rapidly underflows to zero along the positive real axis.

    + +
    +
    >>> sc.rgamma([10, 100, 179])
    +array([2.75573192e-006, 1.07151029e-156, 0.00000000e+000])
    +
    +
    +
    + + +
    +
    + +
    +
    @wraps(f_raw)
    + + def + y0(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    y0(x, out=None)

    + +

    Bessel function of the second kind of order 0.

    + +
    Parameters
    + +
      +
    • x (array_like): +Argument (float).
    • +
    • out (ndarray, optional): +Optional output array for the function results
    • +
    + +
    Returns
    + +
      +
    • Y (scalar or ndarray): +Value of the Bessel function of the second kind of order 0 at x.
    • +
    + +
    See Also
    + +

    j0()` +Bessel`, `function`, `of`, `the`, `first`, `kind`, `of`, `order`, `0` +yv() +Bessel, function, of, the, first, kind

    + +
    Notes
    + +

    The domain is divided into the intervals [0, 5] and (5, infinity). In the +first interval a rational approximation \( R(x) \) is employed to +compute,

    + +

    $$Y_0(x) = R(x) + \frac{2 \log(x) J_0(x)}{\pi},$$

    + +

    where \( J_0 \) is the Bessel function of the first kind of order 0.

    + +

    In the second interval, the Hankel asymptotic expansion is employed with +two rational functions of degree 6/6 and 7/7.

    + +

    This function is a wrapper for the Cephes 1 routine y0.

    + +

    Array API Standard Support

    + +

    y0 has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ✅
    +JAX ✅ ⛔
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    References
    + +
    Examples
    + +

    Calculate the function at one point:

    + +
    +
    >>> from scipy.special import y0
    +>>> y0(1.)
    +0.08825696421567697
    +
    +
    + +

    Calculate at several points:

    + +
    +
    >>> import numpy as np
    +>>> y0(np.array([0.5, 2., 3.]))
    +array([-0.44451873,  0.51037567,  0.37685001])
    +
    +
    + +

    Plot the function from 0 to 10.

    + +
    +
    >>> import matplotlib.pyplot as plt
    +>>> fig, ax = plt.subplots()
    +>>> x = np.linspace(0., 10., 1000)
    +>>> y = y0(x)
    +>>> ax.plot(x, y)
    +>>> plt.show()
    +
    +
    + +
    +
    +
      +
    1. +

      Cephes Mathematical Functions Library, +http://www.netlib.org/cephes/ 

      +
    2. +
    +
    +
    + + +
    +
    + +
    +
    @wraps(f_raw)
    + + def + y1(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    y1(x, out=None)

    + +

    Bessel function of the second kind of order 1.

    + +
    Parameters
    + +
      +
    • x (array_like): +Argument (float).
    • +
    • out (ndarray, optional): +Optional output array for the function results
    • +
    + +
    Returns
    + +
      +
    • Y (scalar or ndarray): +Value of the Bessel function of the second kind of order 1 at x.
    • +
    + +
    See Also
    + +

    j1()` +Bessel`, `function`, `of`, `the`, `first`, `kind`, `of`, `order`, `1` +yn() +Bessel, function, of, the, second, kind
    +`yv() +Bessel,function,of,the,second,kind`

    + +
    Notes
    + +

    The domain is divided into the intervals [0, 8] and (8, infinity). In the +first interval a 25 term Chebyshev expansion is used, and computing +\( J_1 \) (the Bessel function of the first kind) is required. In the +second, the asymptotic trigonometric representation is employed using two +rational functions of degree 5/5.

    + +

    This function is a wrapper for the Cephes 1 routine y1.

    + +

    Array API Standard Support

    + +

    y1 has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ✅
    +JAX ✅ ⛔
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    References
    + +
    Examples
    + +

    Calculate the function at one point:

    + +
    +
    >>> from scipy.special import y1
    +>>> y1(1.)
    +-0.7812128213002888
    +
    +
    + +

    Calculate at several points:

    + +
    +
    >>> import numpy as np
    +>>> y1(np.array([0.5, 2., 3.]))
    +array([-1.47147239, -0.10703243,  0.32467442])
    +
    +
    + +

    Plot the function from 0 to 10.

    + +
    +
    >>> import matplotlib.pyplot as plt
    +>>> fig, ax = plt.subplots()
    +>>> x = np.linspace(0., 10., 1000)
    +>>> y = y1(x)
    +>>> ax.plot(x, y)
    +>>> plt.show()
    +
    +
    + +
    +
    +
      +
    1. +

      Cephes Mathematical Functions Library, +http://www.netlib.org/cephes/ 

      +
    2. +
    +
    +
    + + +
    +
    + +
    +
    @wraps(f_raw)
    + + def + yn(*args, called_by_autograd_dispatcher=False, **kwargs): + + + +
    + +
    51    @wraps(f_raw)
    +52    def f_wrapped(*args, called_by_autograd_dispatcher=False, **kwargs):
    +53        boxed_args, trace, node_constructor, ufunc_dispatch_needed = find_top_boxed_args(args)
    +54        if boxed_args:
    +55            # If we are a wrapper around a ufunc, and if there is at least one ArrayBox
    +56            # argument, then first forward further handling to the ufunc dispatching mechanism
    +57            # (if we aren't already running inside it after being called by the ArrayBox
    +58            # __array_ufunc__ function). This allows other operands which define __array_ufunc__
    +59            # to also try to handle the call.
    +60            #
    +61            # (It's possible our handling attempt below will get the first shot; the handlers
    +62            # order is determined by the dispatch mechanism. Also, if no other array-like
    +63            # arguments are interested in handling the ufunc, then we don't defer to the
    +64            # dispatch mechanism because there is no point).
    +65            #
    +66            # For example, consider multiplying an ndarray wrapped inside an ArrayBox
    +67            # by an xarray.DataArray. The handling below will fail: The ndarray will
    +68            # be unboxed and multiplied by the DataArray resulting in a DataArray,
    +69            # for which `new_box` will raise an exception. In contrast, the DataArray's
    +70            # handling of the call might succeed: it might contain an ndarray, either
    +71            # plain or boxed in an ArrayBox, in which case it will be multiplied by
    +72            # the other ArrayBox yielding a new ArrayBox, which will be stored in a new
    +73            # DataArray.
    +74            if is_ufunc and ufunc_dispatch_needed and not called_by_autograd_dispatcher:
    +75                return f_raw(*args, **kwargs)
    +76
    +77            argvals = subvals(args, [(argnum, box._value) for argnum, box in boxed_args])
    +78            if f_wrapped in notrace_primitives[node_constructor]:
    +79                return f_wrapped(
    +80                    *argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs
    +81                )
    +82            parents = tuple(box._node for _, box in boxed_args)
    +83            argnums = tuple(argnum for argnum, _ in boxed_args)
    +84            ans = f_wrapped(*argvals, called_by_autograd_dispatcher=called_by_autograd_dispatcher, **kwargs)
    +85            node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
    +86            try:
    +87                box = new_box(ans, trace, node)
    +88                return box
    +89            except Exception as e:
    +90                if called_by_autograd_dispatcher:
    +91                    raise NotImplementedError from e
    +92                raise
    +93        else:
    +94            return f_raw(*args, **kwargs)
    +
    + + +

    yn(n, x, out=None)

    + +

    Bessel function of the second kind of integer order and real argument.

    + +
    Parameters
    + +
      +
    • n (array_like): +Order (integer).
    • +
    • x (array_like): +Argument (float).
    • +
    • out (ndarray, optional): +Optional output array for the function results
    • +
    + +
    Returns
    + +
      +
    • Y (scalar or ndarray): +Value of the Bessel function, \( Y_n(x) \).
    • +
    + +
    See Also
    + +

    yv()` +For`, `real`, `order`, `and`, `real`, `or`, `complex`, `argument.` +y0() +faster, implementation, of, this, function, for, order, 0
    +`y1() +faster,implementation,of,this,function,for,order,1`

    + +
    Notes
    + +

    Wrapper for the Cephes 1 routine yn.

    + +

    The function is evaluated by forward recurrence on n, starting with +values computed by the Cephes routines y0 and y1. If n = 0 or 1, +the routine for y0 or y1 is called directly.

    + +

    Array API Standard Support

    + +

    yn has experimental support for Python Array API Standard compatible +backends in addition to NumPy. Please consider testing these features +by setting an environment variable SCIPY_ARRAY_API=1 and providing +CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following +combinations of backend and device (or other capability) are supported.

    + +

    ==================== ==================== ==================== +Library CPU GPU +==================== ==================== ==================== +NumPy ✅ n/a
    +CuPy n/a ✅
    +PyTorch ✅ ⛔
    +JAX ✅ ⛔
    +Dask ✅ n/a
    +==================== ==================== ====================

    + +

    See :ref:dev-arrayapi for more information.

    + +
    References
    + +
    Examples
    + +

    Evaluate the function of order 0 at one point.

    + +
    +
    >>> from scipy.special import yn
    +>>> yn(0, 1.)
    +0.08825696421567697
    +
    +
    + +

    Evaluate the function at one point for different orders.

    + +
    +
    >>> yn(0, 1.), yn(1, 1.), yn(2, 1.)
    +(0.08825696421567697, -0.7812128213002888, -1.6506826068162546)
    +
    +
    + +

    The evaluation for different orders can be carried out in one call by +providing a list or NumPy array as argument for the v parameter:

    + +
    +
    >>> yn([0, 1, 2], 1.)
    +array([ 0.08825696, -0.78121282, -1.65068261])
    +
    +
    + +

    Evaluate the function at several points for order 0 by providing an +array for z.

    + +
    +
    >>> import numpy as np
    +>>> points = np.array([0.5, 3., 8.])
    +>>> yn(0, points)
    +array([-0.44451873,  0.37685001,  0.22352149])
    +
    +
    + +

    If z is an array, the order parameter v must be broadcastable to +the correct shape if different orders shall be computed in one call. +To calculate the orders 0 and 1 for a 1D array:

    + +
    +
    >>> orders = np.array([[0], [1]])
    +>>> orders.shape
    +(2, 1)
    +
    +
    + +
    +
    >>> yn(orders, points)
    +array([[-0.44451873,  0.37685001,  0.22352149],
    +       [-1.47147239,  0.32467442, -0.15806046]])
    +
    +
    + +

    Plot the functions of order 0 to 3 from 0 to 10.

    + +
    +
    >>> import matplotlib.pyplot as plt
    +>>> fig, ax = plt.subplots()
    +>>> x = np.linspace(0., 10., 1000)
    +>>> for i in range(4):
    +...     ax.plot(x, yn(i, x), label=f'$Y_{i!r}$')
    +>>> ax.set_ylim(-3, 1)
    +>>> ax.legend()
    +>>> plt.show()
    +
    +
    + +
    +
    +
      +
    1. +

      Cephes Mathematical Functions Library, +https://netlib.org/cephes/ 

      +
    2. +
    +
    +
    + +