예제 #1
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)
예제 #2
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
예제 #3
0
    def error(self, errinfo={}, plot=False, pfile=None):
        """
        Estimate the error of the observable, by summing in quadrature
        the systematic errors with the statistical errors computed from 
        all ensembles and master fields.
        
        Parameters:
           errinfo (dict, optional): dictionary containg one instance of 
              the errinfo class for each ensemble/master-field. The errinfo
              class provides additional details for the automatic or manual
              windowing procedure in the Gamma method. If not passed default
              parameters are assumed.
           plot (bool, optional): if specified a plot is produced, for 
              every element of the observable, and for every ensemble/master-field 
              where the corresponding element has fluctuations. In addition 
              one piechart plot is produced for every element, showing the 
              contributions to the error from the various sources, only if there
              are multiple sources, ie several ensembles. It is recommended to use 
              the plotting function only for observables with small dimensions.
           pfile (str, optional): if specified all plots produced with the flag
              `plot` are saved to disk, using `pfile` as base name with an additional
              suffix.
        
        Returns:
           list of two arrays: the central value and error of the observable.
           
        Note:
           By default, the errors are computed with the Gamma method, with the `Stau` 
           parameter equal to 1.5. Additionally the jackknife method can be used 
           by passing the appropriate `errinfo` dictionary with argument `bs` set
           to a non-zero integer value. For master fields the error is computed 
           using the master-field approach and the automatic windowing procedure
           requires the additional argument `k` (see main documentation), which 
           by default is zero, but can be specified via the errinfo dictionary.
           Through the `errinfo` dictionary the user can treat every ensemble 
           differently, as explained in the examples below.
           
           
        Examples:
           >>> obsA = pyobs.observable('obsA')
           >>> obsA.create('A',dataA) # create the observable A from ensemble A
           >>> [v,e] = obsA.error() # collect central value and error in v,e
           >>> einfo = {'A': errinfo(Stau=3.0)} # specify non-standard Stau for ensemble A
           >>> [_,e1] = obsA.error(errinfo=einfo)
           >>> print(e,e1) # check difference in error estimation
        
           >>> obsB = pyobs.observable('obsB')
           >>> obsB.create('B',dataB) # create the observable B from ensemble B
           >>> obsC = obsA * obsB # derived observable with fluctuations from ensembles A,B
           >>> einfo = {'A': errinfo(Stau=3.0), 'B': errinfo(W=30)}
           >>> [v,e] = obsC.error(errinfo=einfo,plot=True)
        """
        t0 = time()
        [sigma, sigma_tot, _] = self.error_core(errinfo, plot, pfile)

        if plot:  # pragma: no cover
            h = [len(self.ename), len(self.cdata)]
            if sum(h) > 1:
                plot_piechart(self.description, sigma, sigma_tot)

        if pyobs.is_verbose('error'):
            print(f'error executed in {time()-t0:g} secs')
        return [self.mean, numpy.sqrt(sigma_tot)]
예제 #4
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')
예제 #5
0
def diff(f, x, dx):
    """
    Utility function to compute the gradient and hessian of a function using symbolic calculus
    
    Parameters:
       f (string): the reference function
       x (string): variables that are not differentiated; different variables
           must be separated by a comma
       dx (string): variables that are differentiated; different variables
           must be separated by a comma
    
    Returns:
       lambda : (scalar) function `f`
       lambda : (vector) function of the gradient of `f`
       lambda : (matrix) function of the hessian of `f`
    
    Notes: 
       The symbolic manipulation is based on the library `sympy` and the user 
       must follow the `sympy` syntax when passing the argument `f`. The analytic
       form of the gradient and hessian can be printed by activating the 'diff'
       verbose flag.
       
    Examples:
       >>> res = diff('a+b*x','x','a,b') # differentiate wrt to a and b
       a + b*x
       [1, x]
       [[0, 0], [0, 0]]
       >>> for i in range(3):
       >>>     print(res[i](0.4,1.,2.))
       1.8
       [1, 0.4]
       [[0, 0], [0, 0]]
    """
    pyobs.check_type(f, 'f', str)
    pyobs.check_type(x, 'x', str)
    pyobs.check_type(dx, 'dx', str)

    sym = {}
    for y in dx.rsplit(','):
        sym[y] = sympy.Symbol(y)

    expr = parse_expr(f, local_dict=sym)
    allvars = f'{x},{dx}'
    func = sympy.lambdify(allvars, expr, 'numpy')
    dexpr = []
    ddexpr = []
    for y in sym:
        dexpr.append(sympy.diff(expr, sym[y]))
        tmp = []
        for z in sym:
            tmp.append(sympy.diff(dexpr[-1], sym[z]))
        ddexpr.append(tmp)

    if pyobs.is_verbose('diff') or pyobs.is_verbose('symbolic.diff'):
        display(expr)
        display(dexpr)
        display(ddexpr)

    dfunc = sympy.lambdify(allvars, dexpr, 'numpy')
    ddfunc = sympy.lambdify(allvars, ddexpr, 'numpy')
    return [func, dfunc, ddfunc]