Ejemplo n.º 1
0
    def __setitem__(self, args, yobs):
        if isinstance(args,
                      (int, numpy.int32, numpy.int64, slice, numpy.ndarray)):
            args = [args]
        if (self.mean[tuple(args)].size == 1):
            if yobs.size != 1:
                raise pyobs.PyobsError('set item : dimensions do not match')
        else:
            if self.mean[tuple(args)].shape != yobs.shape:
                raise pyobs.PyobsError('set item : dimensions do not match')
        self.mean[tuple(args)] = yobs.mean

        idx = numpy.arange(self.size).reshape(self.shape)[tuple(args)]
        submask = idx.flatten()

        for key in yobs.delta:
            if not key in self.delta:
                raise pyobs.PyobsError(
                    'Ensembles do not match; can not set item')
            self.delta[key].assign(submask, yobs.delta[key])

        for key in yobs.cdata:
            if not key in self.cdata:
                raise pyobs.PyobsError(
                    'Covariance data do not match; can not set item')
            self.cdata[key].assign(submask, yobs.cdata[key])
Ejemplo n.º 2
0
 def create_from_cov(self, cname, value, covariance):
     """
     Create observables based on covariance matrices
     
     Parameters:
        cname (str): label that uniquely identifies the data set
        value (array): central value of the observable; only 1-D arrays are supported
        covariance (array): a 2-D covariance matrix; if `covariance` is a 1-D array of
           same length as `value`, a diagonal covariance matrix is assumed.
     
     Examples:
        >>> mpi = pyobs.observable(description='pion masses, charged and neutral')
        >>> mpi.create_cd('mpi-pdg18',[139.57061,134.9770],[0.00023**2,0.0005**2])
        >>> print(mpi)
        139.57061(23)    134.97700(50)
     """
     if isinstance(value, (int, float, numpy.float64, numpy.float32)):
         self.mean = numpy.reshape(value, (1, ))
         cov = numpy.reshape(covariance, (1, ))
     else:
         self.mean = numpy.array(value)
         cov = numpy.array(covariance)
     self.shape = numpy.shape(self.mean)
     if numpy.ndim(self.shape) != 1:
         raise pyobs.PyobsError(
             f'Unexpected value, only 1-D arrays are supported')
     self.size = numpy.prod(self.shape)
     if cov.shape != (self.size, ) and cov.shape != (self.size, self.size):
         raise pyobs.PyobsError(
             f'Unexpected shape for covariance {cov.shape}')
     pyobs.check_type(cname, 'cname', str)
     self.cdata[cname] = cdata(cov)
     pyobs.memory.update(self)
Ejemplo n.º 3
0
def load(fname):
    """
    Load the observable/data from disk.

    Parameters:
       name (str): string with the source file (path+filename)

    Returns:
       observable: the loaded observable

    Examples:
       >>> obsA = pyobs.load('~/analysis/obsA.json.gz')
    """

    if not os.path.isfile(fname):
        raise pyobs.PyobsError(f'File {fname} does not exists')

    if '.pyobs' in fname:
        fmt = default
    elif '.json.gz' in fname:
        fmt = json
    else:  # pragma: no cover
        raise pyobs.PyobsError(f'Format not supported')

    return fmt.load(fname)
Ejemplo n.º 4
0
 def add_syst_err(self, name, err):
     """
     Add a systematic error to the observable
     
     Parameters:
        name (str): label that uniquely identifies the syst. error
        err (array): array with the same dimensions of the observable
           with the systematic error
     
     Examples:
        >>> data = [0.198638, 0.403983, 1.215960, 1.607684, 0.199049, ... ]
        >>> vec = pyobs.observable(description='vector')
        >>> vec.create('A',data,shape=(4,))
        >>> print(vec)
        0.201(13)    0.399(26)    1.199(24)    1.603(47)
        >>> vec.add_syst_err('syst.err',[0.05,0.05,0,0])
        >>> print(vec)
        0.201(52)    0.399(56)    1.199(24)    1.603(47)
        
     """
     pyobs.check_type(name, 'name', str)
     if name in self.cdata:
         raise pyobs.PyobsError(f'Label {name} already used')
     if numpy.shape(err) != self.shape:
         raise pyobs.PyobsError(
             f'Unexpected error, dimensions do not match {self.shape}')
     cov = numpy.reshape(numpy.array(err)**2, (self.size, ))
     self.cdata[name] = cdata(cov)
     pyobs.memory.update(self)
Ejemplo n.º 5
0
    def __init__(self, mask, idx, data=None, mean=None, lat=None):
        # idx is expected to be a list or range
        self.size = len(mask)
        self.mask = [m for m in mask]
        self.it = 0
        if lat is None:
            self.lat = None
        else:
            self.lat = numpy.array(lat, dtype=numpy.int32)

        if (type(idx) is list):
            dc = numpy.unique(numpy.diff(idx))
            if numpy.any(dc < 0):
                raise pyobs.PyobsError(f'Unsorted idx')
            if len(dc) == 1:
                self.idx = range(idx[0], idx[-1] + dc[0], dc[0])
            else:
                self.idx = list(idx)
        elif (type(idx) is range):
            self.idx = idx
        else:
            raise pyobs.PyobsError(f'Unexpected idx')
        self.n = len(self.idx)

        self.delta = numpy.zeros((self.size, self.n), dtype=numpy.float64)

        if not mean is None:
            self.delta = numpy.reshape(data,
                                       (self.n, self.size)).T - numpy.stack(
                                           [mean] * self.n, axis=1)
Ejemplo n.º 6
0
 def __init__(self, x, W, f, df, v):
     self.v = v
     if numpy.ndim(x) == 2:
         (self.n, self.nx) = numpy.shape(x)
     elif numpy.ndim(x) == 1:
         self.n = len(x)
         self.nx = 1
     else:
         raise pyobs.PyobsError(f'Unexpected x')
     if len(self.v) != self.nx:
         raise pyobs.PyobsError(f'Unexpected x')
     self.x = numpy.reshape(x, (self.n, self.nx))
     self.W = numpy.array(W)
     self.f = f
     self.df = df
     if f.__code__.co_varnames != df.__code__.co_varnames:
         raise pyobs.PyobsError(
             f'Unexpected f and df: varnames do not match')
     self.pars = []
     for vn in f.__code__.co_varnames:
         if not vn in self.v:
             self.pars.append(vn)
     self.np = len(self.pars)
     self.e = numpy.zeros((self.n, ), dtype=numpy.float64)
     self.de = numpy.zeros((self.n, self.np), dtype=numpy.float64)
     self.p = [0.0] * self.np
Ejemplo n.º 7
0
 def __call__(self, y):
     if len(y) != self.n:
         raise pyobs.PyobsError(
             f'Unexpected length of observable {len(y)} w.r.t. x-axis {self.n}'
         )
     if numpy.shape(self.W)[0] != self.n:
         raise pyobs.PyobsError(
             f'Unexpected size of W matrix {numpy.shape(self.W)} w.r.t. x-axis {self.n}'
         )
     for i in range(self.n):
         self.e[i] = self.f(*self.x[i, :], *self.p) - y[i]
     return self.e @ self.W @ self.e
Ejemplo n.º 8
0
 def __init__(self, x, W, f, df, v='x'):
     if numpy.ndim(W) == 1:
         r = len(W)
         W = numpy.diag(W)
     elif numpy.ndim(W) == 2:
         [r, c] = numpy.shape(W)
         if r != c:
             raise pyobs.PyobsError(f'Rectangular W matrix, must be square')
     else:
         raise pyobs.PyobsError(f'Unexpected size of W matrix')
     tmp = v.rsplit(',')
     self.csq = {0: chisquare(x, W, f, df, tmp)}
     self.pdict = {}
     for i in range(len(self.csq[0].pars)):
         self.pdict[self.csq[0].pars[i]] = i
Ejemplo n.º 9
0
    def __init__(self, orig=None, description='unknown'):
        if orig is None:
            pyobs.check_type(description, 'text', str)
            self.description = description
            self.www = [
                pwd.getpwuid(os.getuid())[0],
                os.uname()[1],
                datetime.datetime.now().strftime('%c')
            ]
            self.shape = []
            self.size = 0
            self.mean = []
            self.ename = []
            self.delta = {}
            self.cdata = {}
        else:
            if isinstance(orig, observable):
                self.description = orig.description
                self.www = orig.www
                self.shape = orig.shape
                self.size = numpy.prod(self.shape)
                self.mean = numpy.array(orig.mean)  # or orig.mean.copy()

                self.ename = [e for e in orig.ename]
                self.delta = {}
                for key in orig.delta:
                    self.delta[key] = orig.delta[key].copy()

                self.cdata = {}
                for key in orig.cdata:
                    self.cdata[key] = cdata(orig.cdata[key].cov)
                pyobs.memory.add(self)
            else:
                raise pyobs.PyobsError('Unexpected orig argument')
        pyobs.memory.add(self)
Ejemplo n.º 10
0
 def __mul__(self, y):
     if isinstance(y, observable):
         if self.shape == y.shape:
             g0 = pyobs.gradient(lambda x: x * y.mean,
                                 self.mean,
                                 gtype='diag')
             g1 = pyobs.gradient(lambda x: self.mean * x,
                                 y.mean,
                                 gtype='diag')
         elif self.shape == (1, ):
             g0 = pyobs.gradient(lambda x: x * y.mean,
                                 self.mean,
                                 gtype='full')
             g1 = pyobs.gradient(lambda x: self.mean * x,
                                 y.mean,
                                 gtype='diag')
         elif y.shape == (1, ):
             g0 = pyobs.gradient(lambda x: x * y.mean,
                                 self.mean,
                                 gtype='diag')
             g1 = pyobs.gradient(lambda x: self.mean * x,
                                 y.mean,
                                 gtype='full')
         else:
             raise pyobs.PyobsError('Shape mismatch, cannot multiply')
         return pyobs.derobs([self, y], self.mean * y.mean, [g0, g1])
     else:
         # if gradient below was 'full' it would allow scalar_obs * array([4,5,6])
         # which would create a vector obs. right now that generates an error
         # but is faster for large gradients
         g0 = pyobs.gradient(lambda x: x * y, self.mean, gtype='diag')
         return pyobs.derobs([self], self.mean * y, [g0])
Ejemplo n.º 11
0
 def slice(self, *args):
     na = len(args)
     if na != len(self.shape):
         raise pyobs.PyobsError('Unexpected argument')
     f = lambda x: pyobs.slice_ndarray(x, *args)
     g0 = pyobs.gradient(f, self.mean, gtype='slice')
     return pyobs.derobs([self], f(self.mean), [g0])
Ejemplo n.º 12
0
def inv(x):
    """
    Compute the inverse of a square matrix

    Parameters:
       x (obs): Matrix to be inverted
    
    Returns:
       obs: (Multiplicative) inverse of `x`
    
    Examples:
       >>> from pyobs.linalg import inv
       >>> a = pyobs.observable()
       >>> a.create('A',data,shape=(2,2))
       >>> ainv = pyobs.inv(a)
    
    Notes:
       If the number of dimensions is bigger than 2, 
       `x` is treated as a stack of matrices residing 
       in the last two indexes and broadcast accordingly.
    """
    if (x.shape[-2] != x.shape[-1]):  # pragma: no cover
        raise pyobs.PyobsError(
            f'Unexpected matrix for inverse with shape={x.shape}')
    mean = numpy.linalg.inv(x.mean)
    # V Vinv = 1,   dV Vinv + V dVinv = 0 ,  dVinv = - Vinv dV Vinv
    g = pyobs.gradient(lambda x: -mean @ x @ mean, x.mean)
    return pyobs.derobs([x], mean, [g])
Ejemplo n.º 13
0
def load(fname):
    tmp = json.loads(gzip.open(fname, 'r').read())
    res = pyobs.observable(description=tmp['description'])
    res.www = list(tmp['www'])

    res.mean = numpy.array(tmp['mean'])
    res.shape = tuple(tmp['shape'])
    res.size=numpy.prod(res.shape)
    res.ename = list(tmp['ename'])
    
    for key in tmp['delta']:
        if (type(tmp['delta'][key]['idx']) is str):
            regex=re.compile('[(,)]')
            h = regex.split(tmp['delta'][key]['idx'])
            if h[0]!='range': # pragma: no cover
                raise pyobs.PyobsError('Unexpected idx')
            res.delta[key] = pyobs.core.data.delta(tmp['delta'][key]['mask'],range(int(h[1]),int(h[2]),int(h[3])),lat=tmp['delta'][key]['lat'])
        else:
            res.delta[key] = pyobs.core.data.delta(tmp['delta'][key]['mask'],tmp['delta'][key]['idx'],lat=tmp['delta'][key]['lat'])
        res.delta[key].delta = numpy.array(tmp['delta'][key]['delta'])
                
    for key in tmp['cdata']:
        res.cdata[key] = pyobs.core.cdata.cdata(tmp['cdata'][key]['cov'])
    pyobs.memory.update(res)
    return res
Ejemplo n.º 14
0
def eig(x):
    """
    Computes the eigenvalues and eigenvectors of a square matrix observable.
    The central values are computed using the `numpy.linalg.eig` routine.

    Parameters:
       x (obs): a symmetric square matrix (observable) with dimensions `NxN`

    Returns:
       list of obs: a vector observable with the eigenvalues and a matrix observable whose columns correspond to the eigenvectors

    Notes:
       The error on the eigenvectors is based on the assumption that the input
       matrix is symmetric. If this not respected, the returned eigenvectors will
       have under or over-estimated errors.
    
    Examples:
       >>> [w,v] = pyobs.linalg.eig(mat)
       >>> for i in range(N):
       >>>     # check eigenvalue equation  
       >>>     print(mat @ v[:,i] - v[:,i] * w[i])
    """
    if len(x.shape) > 2:  # pragma: no cover
        raise pyobs.PyobsError(
            f'Unexpected matrix with shape {x.shape}; only 2-D arrays are supported'
        )
    if numpy.any(numpy.fabs(x.mean - x.mean.T) > 1e-10):  # pragma: no cover
        raise pyobs.PyobsError(f'Unexpected non-symmetric matrix: user eigLR')

    [w, v] = numpy.linalg.eig(x.mean)

    # d l_n = (v_n, dA v_n)
    gw = pyobs.gradient(lambda x: numpy.diag(v.T @ x @ v), x.mean)

    # d v_n = sum_{m \neq n} (v_m, dA v_n) / (l_n - l_m) v_m
    def gradv(y):
        tmp = v.T @ y @ v
        h = []
        for m in range(x.shape[0]):
            h.append((w != w[m]) * 1.0 / (w - w[m] + 1e-16))
        h = numpy.array(h)
        return v @ (tmp * h)

    gv = pyobs.gradient(gradv, x.mean)
    return [pyobs.derobs([x], w, [gw]), pyobs.derobs([x], v, [gv])]
Ejemplo n.º 15
0
def save(fname, *args):
    """
    Save data to disk. 

    Parameters:
       name (str): string with the destination (path+filename). To select
       the file format the user must provide one file extension among 
       `.pyobs` (default) and `.json.gz`.
       args : data to save (see below)

    Notes:
       Available output formats:

       * pyobs: the default binary format, with automatic checksums
         for file corruptions and fast read/write speed. It is based
         on the `bison <https://mbruno46.github.io/bison/>`_ file 
         format. `args` can be an arbitrary sequence of python basic
         types, numpy arrays and observables. (check the bison 
         documentation for more information).

       * json.gz: apart from the compression with gunzip, the file 
         is a plain text file generated with json format, for easy 
         human readability and compatibility with other programming 
         languages (json format is widely supported). Currently this
         format supports only a single observable.

    Examples:
       >>> obsA = pyobs.observable('obsA')
       >>> obsA.create('A',data)
       >>> pyobs.save('~/analysis/obsA.json.gz', obsA)
    """
    if os.path.isfile(fname) is True:
        raise pyobs.PyobsError(f'File {fname} already exists')

    if '.pyobs' in fname:
        fmt = default
    elif '.json.gz' in fname:
        fmt = json
        if len(args) > 1 or not isinstance(args[0], pyobs.observable):
            raise pyobs.PyobsError(
                f'json file format supports only single observable')
    else:
        raise pyobs.PyobsError(f'Format not supported')

    fmt.save(fname, *args)
Ejemplo n.º 16
0
    def __init__(self, g, x0=None, gtype='full'):
        if not callable(g):
            (self.Na, self.Ni) = numpy.shape(g)
            self.gtype = 'full'
            self.grad = g
            return

        self.Na = numpy.size(g(x0))
        self.Ni = numpy.size(x0)
        self.gtype = gtype

        if gtype is 'full':
            gsh = (self.Na, self.Ni)
        elif gtype is 'diag':
            gsh = self.Na
            if self.Na != self.Ni:
                raise pyobs.PyobsError('diagonal gradient error')
        elif gtype is 'slice':
            gsh = self.Na
        elif gtype is 'extend':
            gsh = self.Ni
        else:
            raise pyobs.PyobsError('gradient error')

        self.grad = numpy.zeros(gsh, dtype=numpy.float64)

        if gtype is 'full':
            dx = numpy.zeros(self.Ni)
            for i in range(self.Ni):
                dx[i] = 1.0
                self.grad[:, i] = numpy.reshape(g(numpy.reshape(dx, x0.shape)),
                                                self.Na)
                dx[i] = 0.0
        elif gtype is 'diag':
            self.grad = g(numpy.ones(x0.shape)).flatten()
        elif gtype is 'slice':
            self.grad = numpy.reshape(
                g(numpy.arange(self.Ni).reshape(x0.shape)), self.Na)
        elif gtype is 'extend':
            self.grad = numpy.nonzero(g(numpy.ones(x0.shape)).flatten())[0]
Ejemplo n.º 17
0
 def __getitem__(self, args):
     if isinstance(args,
                   (int, numpy.int32, numpy.int64, slice, numpy.ndarray)):
         args = [args]
     na = len(args)
     if na != len(self.shape):
         raise pyobs.PyobsError('Unexpected argument')
     if self.mean[tuple(args)].size == 1:
         f = lambda x: numpy.reshape(x[tuple(args)], (1, ))
     else:
         f = lambda x: x[tuple(args)]
     g0 = pyobs.gradient(f, self.mean, gtype='slice')
     return pyobs.derobs([self], f(self.mean), [g0])
Ejemplo n.º 18
0
    def __call__(self, yobs, p0=None, min_search=None):
        if len(self.csq) > 1:
            pyobs.check_type(yobs, 'yobs', list)
        else:
            if isinstance(yobs, pyobs.observable):
                yobs = [yobs]
        if len(yobs) != len(self.csq):
            raise pyobs.PyobsError(
                f'Unexpected number of observables for {len(self.csq)} fits')
        if p0 is None:
            p0 = [1.0] * len(self.pdict)
        if min_search is None:
            min_search = lm

        def csq(p0):
            res = 0.0
            for i in range(len(yobs)):
                self.csq[i].set_pars(self.pdict, p0)
                res += self.csq[i](yobs[i].mean)
            return res

        dcsq = lambda x: sum([
            self.csq[i].grad(yobs[i].mean, self.pdict)
            for i in range(len(yobs))
        ])
        ddcsq = lambda x: sum([
            self.csq[i].hess(yobs[i].mean, self.pdict)
            for i in range(len(yobs))
        ])

        t0 = time()
        res = min_search(csq, p0, jac=dcsq, hess=ddcsq)

        # properly create gradients
        H = self.csq[0].Hmat(self.pdict, res.x)
        for i in range(1, len(yobs)):
            H += self.csq[i].Hmat(self.pdict, res.x)
        Hinv = numpy.linalg.inv(H)

        g = []
        for i in range(len(yobs)):
            tmp = self.csq[i].gvec(self.pdict, res.x)
            g.append(pyobs.gradient(Hinv @ tmp))

        if pyobs.is_verbose('mfit.run') or pyobs.is_verbose('mfit'):
            print(f'chisquare = {res.fun}')
            print(f'minimizer iterations = {res.nit}')
            print(f'minimizer status: {res.message}')
            print(f'mfit.run executed in {time()-t0:g} secs')
        return pyobs.derobs(yobs, res.x, g)
Ejemplo n.º 19
0
    def eval(self, x, pdict, p0):
        if numpy.ndim(x) == 2:
            (n, nx) = numpy.shape(x)
        elif numpy.ndim(x) == 1:
            n = len(x)
            nx = 1
        elif numpy.ndim(x) == 0:
            n = 1
            nx = 1
        else:
            raise pyobs.PyobsError(f'Unexpected x')
        x = numpy.reshape(x, (n, nx))
        res = numpy.zeros((n, len(pdict)))

        self.set_pars(pdict, p0)
        new_mean = numpy.array([self.f(*x[i, :], *self.p) for i in range(n)])
        g = numpy.array([self.df(*x[i, :], *self.p)
                         for i in range(n)])  # N x Na
        for pn in self.pars:
            a = pdict[pn]
            i = self.pars.index(pn)
            res[:, a] = g[:, i]
        return [new_mean, res]
Ejemplo n.º 20
0
    def eval(self, xax, pars):
        """
        Evaluates the function on a list of coordinates using the parameters
        obtained from a :math:`\chi^2` minimization.
        
        Parameters:
           xax (array,list of arrays) : the coordinates :math:`x_i^\mu` where 
              the function must be evaluated. For combined fits, a list of 
              arrays must be passed, one for each fit.
           pars (obs) : the observable returned by calling this class
        
        Returns:
           list of obs : observables corresponding to the functions evaluated 
           at the coordinates `xax`.
        
        Examples:
           >>> fit1 = mfit(xax,W,f,df)
           >>> pars = fit1(yobs1)
           >>> print(pars)
           0.925(35)    2.050(19)
           >>> xax = numpy.arange(0,10,0.2)
           >>> yeval = fit1.eval(xax, pars)
        """

        if not type(xax) is list:
            xax = [xax]
        pyobs.check_type(pars, 'pars', pyobs.observable)
        N = len(xax)
        if N != len(self.csq):
            raise pyobs.PyobsError(
                f'Coordinates and Paramters do not match number of internal functions'
            )
        out = []
        for ic in self.csq:
            [m, g] = self.csq[ic].eval(xax[ic], self.pdict, pars.mean)
            out.append(pyobs.derobs([pars], m, [pyobs.gradient(g)]))
        return out
Ejemplo n.º 21
0
def derobs(inps, mean, grads, description=None):
    t0 = time()
    pyobs.check_type(inps, 'inps', list)
    pyobs.check_type(mean, 'mean', numpy.ndarray, int, float, numpy.float32,
                     numpy.float64)
    pyobs.check_type(grads, 'grads', list)
    if len(inps) != len(grads):
        raise pyobs.PyobsError('Incompatible inps and grads')
    if description is None:
        description = ', '.join(set([i.description for i in inps]))
    res = pyobs.observable(description=description)
    res.set_mean(mean)

    allkeys = []
    for i in inps:
        for dn in i.delta:
            if not dn in allkeys:
                allkeys.append(dn)

    for key in allkeys:
        new_idx = []
        new_mask = []
        lat = None
        for i in range(len(inps)):
            if key in inps[i].delta:
                data = inps[i].delta[key]
                h = grads[i].get_mask(data.mask)
                if not h is None:
                    new_mask += h
                    if not new_idx:
                        new_idx = data.idx
                    else:
                        new_idx = merge_idx(new_idx, data.idx)
                    if lat is None:
                        lat = data.lat
                    else:
                        if numpy.any(lat != data.lat):  # pragma: no cover
                            raise pyobs.PyobsError(
                                f'Unexpected lattice size for master fields with same tag'
                            )
        if len(new_mask) > 0:
            res.delta[key] = delta(list(set(new_mask)), new_idx, lat=lat)
            for i in range(len(inps)):
                if key in inps[i].delta:
                    res.delta[key].axpy(grads[i], inps[i].delta[key])

    res.ename = []
    for key in res.delta:
        name = key.split(':')[0]
        if not name in res.ename:
            res.ename.append(name)

    res.cdata = {}
    allkeys = []
    for i in inps:
        for cd in i.cdata:
            if not cd in allkeys:
                allkeys.append(cd)
    for key in allkeys:
        for i in range(len(inps)):
            if key in inps[i].cdata:
                if not key in res.cdata:
                    res.cdata[key] = cdata(numpy.zeros(res.size))
                res.cdata[key].axpy(grads[i], inps[i].cdata[key])

    pyobs.memory.update(res)
    if pyobs.is_verbose('derobs'):
        print(f'derobs executed in {time()-t0:g} secs')
    return res
Ejemplo n.º 22
0
 def assign(self, submask, rd):
     if len(submask) != len(rd.mask):
         raise pyobs.PyobsError('Dimensions do not match in assignment')
     a = numpy.nonzero(numpy.in1d(self.mask, submask))[0]
     self.delta[a, :] = rd.delta
Ejemplo n.º 23
0
def eigLR(x):
    """
    Computes the eigenvalues and the left and right eigenvectors of a 
    square matrix observable. The central values are computed using 
    the `numpy.linalg.eig` routine.

    Parameters:
       x (obs): a square matrix (observable) with dimensions `NxN`; 

    Returns:
       list of obs: a vector observable with the eigenvalues and two 
       matrix observables whose columns correspond to the right and 
       left eigenvectors respectively.

    Notes:
       This input matrix is not expected to be symmetric. If it is the 
       usage of `eig` is recommended for better performance.
    
    Examples:
       >>> [l,v,w] = pyobs.linalg.eigLR(mat)
       >>> for i in range(N):
       >>>     # check eigenvalue equation  
       >>>     print(mat @ v[:,i] - v[:,i] * l[i])
       >>>     print(w[:,i] @ mat - w[:,i] * l[i])
    """
    if len(x.shape) > 2:  # pragma: no cover
        raise pyobs.PyobsError(
            f'Unexpected matrix with shape {x.shape}; only 2-D arrays are supported'
        )

    # left and right eigenvectors
    [l, v] = numpy.linalg.eig(x.mean)
    [l, w] = numpy.linalg.eig(x.mean.T)

    # d l_n = (w_n, dA v_n) / (w_n, v_n)
    gl = pyobs.gradient(
        lambda x: numpy.diag(w.T @ x @ v) / numpy.diag(w.T @ v), x.mean)

    # d v_n = sum_{m \neq n} (w_m, dA v_n) / (l_n - l_m) w_m
    def gradv(y):
        tmp = w.T @ y @ v
        gv = numpy.zeros(x.shape)
        for n in range(x.shape[0]):
            for m in range(x.shape[1]):
                if n != m:
                    gv[:, n] += tmp[m, n] / (l[n] - l[m]) * w[:, m]
        return gv

    gv = pyobs.gradient(gradv, x.mean)

    # d w_n = sum_{m \neq n} (v_m, dA^T w_n) / (l_n - l_m) v_m
    def gradw(y):
        tmp = v.T @ y.T @ w
        gw = numpy.zeros(x.shape)
        for n in range(x.shape[0]):
            for m in range(x.shape[1]):
                if n != m:
                    gw[:, n] += tmp[m, n] / (l[n] - l[m]) * v[:, m]
        return gw

    gw = pyobs.gradient(gradw, x.mean)
    return [
        pyobs.derobs([x], l, [gl]),
        pyobs.derobs([x], v, [gv]),
        pyobs.derobs([x], w, [gw])
    ]
Ejemplo n.º 24
0
    def create(self,
               ename,
               data,
               icnfg=None,
               rname=None,
               shape=(1, ),
               lat=None):
        """
        Create an observable
        
        Parameters:
           ename (str): label of the ensemble
           data (array, list of arrays): the data generated from a single 
              or multiple replica
           icnfg (array of ints or list of arrays of ints, optional): indices 
              of the configurations corresponding to data; if not passed the 
              measurements are assumed to be contiguous
           rname (str or list of str, optional): identifier of the replica; if 
              not passed integers from 0 are automatically assigned
           shape (tuple, optional): shape of the observable, data must be passed accordingly
           lat (list of ints, optional): the size of each dimension of the master-field;
              if passed data is assumed to be obtained from observables measured at different
              sites and `icnfg` is re-interpreted as the index labeling the sites; if `icnfg`
              is not passed data is assumed to be contiguous on all sites.
              
        Note:
           For data and icnfg array can mean either a list or a 1-D numpy.array.
           If the observable has already been created, calling create again will add
           a new replica to the same ensemble.
           
        Examples:
           >>> data = [0.43, 0.42, ... ] # a scalar observable
           >>> a = pyobs.observable(description='test')
           >>> a.create('EnsembleA',data)

           >>> data0 = [0.43,0.42, ... ] # replica 0
           >>> data1 = [0.40,0.41, ... ] # replica 1
           >>> a = pyobs.observable(description='test')
           >>> a.create('EnsembleA',[data0,data1],rname=['r0','r1'])

           >>> data = [0.43, 0.42, 0.44, ... ]
           >>> icnfg= [  10,   11,   13, ... ]
           >>> a = pyobs.observable(description='test')
           >>> a.create('EnsembleA',data,icnfg=icnfg)

           >>> data = [1.0, 2.0, 3.0, 4.0, ... ]
           >>> a = pyobs.observable(description='matrix')
           >>> a.create('EnsembleA',data,shape=(2,2))
       
        Examples:
           >>> data = [0.43, 0.42, 0.44, ... ]
           >>> lat = [64,32,32,32]
           >>> a = pyobs.observable(description='test-mf')
           >>> a.create('EnsembleA',data,lat=lat)
           
           >>> data = [0.43, 0.42, 0.44, ... ]
           >>> idx = [0, 2, 4, 6, ...] # measurements on all even points of time-slice
           >>> lat = [32, 32, 32]
           >>> a = pyobs.observable(description='test-mf')
           >>> a.create('EnsembleA',data,lat=lat,icnfg=idx)           
        """
        t0 = time()
        pyobs.check_type(ename, 'ename', str)
        if ':' in ename:
            raise pyobs.PyobsError(
                f'Column symbol not allowed in ename {ename}')
        pyobs.check_type(shape, 'shape', tuple)
        self.shape = shape
        self.size = numpy.prod(shape)
        mask = range(self.size)
        if not ename in self.ename:
            self.ename.append(ename)

        if isinstance(data[0], (list, numpy.ndarray)):
            R = len(data)
        elif isinstance(data[0], (int, float, numpy.float64, numpy.float32)):
            R = 1
        else:
            raise pyobs.PyobsError(f'Unexpected data type')

        if R == 1:
            pyobs.check_type(data, f'data', list, numpy.ndarray)
            nc = int(len(data) / self.size)
            if rname is None:
                rname = 0
            else:
                pyobs.check_not_type(rname, 'rname', list)
            if icnfg is None:
                icnfg = range(nc)
            else:
                pyobs.check_type(icnfg, 'icnfg', list, range)
                pyobs.check_type(icnfg[0], 'icnfg[:]', int, numpy.int32,
                                 numpy.int64)
                if len(icnfg) * self.size != len(data):
                    raise pyobs.PyobsError(
                        f'Incompatible icnfg and data, for shape={shape}')
            if numpy.size(self.mean) != 0:
                N0 = sum([self.delta[key].n for key in self.delta])
                mean0 = numpy.reshape(self.mean, (self.size, ))
                mean1 = numpy.mean(numpy.reshape(data, (nc, self.size)), 0)
                self.mean = (N0 * mean0 + nc * mean1) / (N0 + nc)
                shift = nc * (mean0 - mean1) / (N0 + nc)
                for key in self.delta:
                    self.delta[key].delta += shift[:, None]
            else:
                self.mean = numpy.mean(numpy.reshape(data, (nc, self.size)), 0)

            key = f'{ename}:{rname}'
            self.delta[key] = delta(mask, icnfg, data, self.mean, lat)
        else:
            if numpy.size(self.mean) != 0:
                raise pyobs.PyobsError(
                    'Only a single replica can be added to existing observables'
                )
            for ir in range(R):
                pyobs.check_type(data[ir], f'data[{ir}]', list, numpy.ndarray)
            self.mean = numpy.zeros((self.size, ))
            nt = 0
            for dd in data:
                nc = int(len(dd) / self.size)
                self.mean += numpy.sum(numpy.reshape(dd, (nc, self.size)), 0)
                nt += nc
            self.mean *= 1.0 / float(nt)
            if rname is None:
                rname = range(R)
            else:
                pyobs.check_type(rname, 'rname', list)
                if len(rname) != R:
                    raise pyobs.PyobsError('Incompatible rname and data')
            if not icnfg is None:
                pyobs.check_type(icnfg, 'icnfg', list)

            if icnfg is None:
                icnfg = []
                for ir in range(len(data)):
                    nc = int(len(data[ir]) / self.size)
                    icnfg.append(range(nc))
            else:
                for ir in range(len(data)):
                    if len(icnfg[ir]) * self.size != len(data[ir]):
                        raise pyobs.PyobsError(
                            f'Incompatible icnfg[{ir}] and data[{ir}], for shape={shape}'
                        )
            for ir in range(len(data)):
                key = f'{ename}:{rname[ir]}'
                self.delta[key] = delta(mask, icnfg[ir], data[ir], self.mean,
                                        lat)
        self.mean = numpy.reshape(self.mean, self.shape)
        pyobs.memory.update(self)
        if pyobs.is_verbose('create'):
            print(f'create executed in {time()-t0:g} secs')