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])
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)
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)
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)
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)
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
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
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
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)
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])
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])
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])
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
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])]
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)
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]
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])
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)
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]
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
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
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
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]) ]
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')