def __init_cache(self): """ensure model has a mystic.cache""" model = self.__model__ mvl = getattr( self, 'ny', getattr(model, 'ny', getattr( model, '__axis__', None))) is not None # True if multivalued name = getattr(model, '__name__', None) #XXX: do better? if not hasattr(model, '__cache__') or not hasattr( model, '__inverse__'): import mystic.cache as mc model = mc.cached(archive=name, multivalued=mvl)(model) self.__model__ = model if name is not None: self.__model__.__name__ = name return
def __call__(self, axis=None, **kwds): """apply the reducer to the sampled statistical quantity Input: axis: int, the index of y on which to find bound (all, by default) Additional Input: reducer: function to reduce a list to a single value (e.g. mean, max) dist: a mystic.tools.Distribution instance (or list of Distributions) npts: number of sample points [default = 10000] clip: if True, clip at bounds, else resample [default = False] Returns: sampled statistical quantity, for the specified axis, reduced to a float """ #XXX: return what? "energy and solution?" reduced? reducer = kwds.pop('reducer', None) if kwds.get('npts', None) is None: kwds.pop('npts', None) s = random_samples(self.lb, self.ub, **kwds).T fobj = cached(archive=dict_archive())(self.objective) #XXX: bad idea? self._pts = fobj.__cache__( ) #XXX: also bad idea? include from *_bounds? objective = lambda rv: fobj(self.constraint(rv), axis=axis) import multiprocess.dummy as mp #FIXME: process pickle/recursion Error pool = mp.Pool() # len(s) map = pool.map #TODO: don't hardwire map s = map(objective, s) #NOTE: s = [(...),(...)] or [...] pool.close() pool.join() if axis is None and self.axes is not None: # apply per axis if reducer is None: return tuple(sum(si) / len(si) for si in zip(*s)) #XXX: better tuple(reducer(s, axis=0).tolist()) if numpy ufunc? return tuple(reducer(si) for si in zip(*s)) s = tuple(s) return sum(s) / len(s) if reducer is None else reducer(s)
#!/usr/bin/env python # # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) # Copyright (c) 2020-2022 The Uncertainty Quantification Foundation. # License: 3-clause BSD. The full license text is available at: # - https://github.com/uqfoundation/mystic/blob/master/LICENSE import mystic.cache as mc import mystic.models as mm # basic interaction with an archive d = mc.archive.read('test', type=mc.archive.dict_archive) assert len(d) == 0 mc.archive.write(d, dict(a=1, b=2, c=3)) assert len(d) == 3 # basic pattern to cache an objective d = mc.archive.read('rosen', type=mc.archive.dict_archive) model = mc.cached(archive=d)(mm.rosen) model([1, 2, 1]) model([1, 1, 1]) c = model.__cache__() assert len(c) == 2 model.__inverse__([1, 2, 3]) == -model([1, 2, 3]) assert len(c) == 3
def sample(model, bounds, pts=None, **kwds): """sample model within bounds, writing to an archive and returning data Inputs: model: a cached model function, of form y = model(x, axis=None) bounds: list of tuples of (lower,upper), bounds on each 'x' pts: int, number of points sampled by the sampler Additional Inputs: sampler: the mystic.sampler type [default: LatticeSampler] solver: the mystic.solver type [default: NelderMeadSimplexSolver] dist: a distribution type (or float amplitude) [default: None] map: a map instance [default: builtins.map] ny: int, number of model outputs, len(y) [default: None] axis: int, index of output on which to search [default: None] Returns: the mystic.math.legacydata.dataset of sampled data NOTE: additional keywords (evalmon, stepmon, maxiter, maxfun, saveiter, state, termination, constraints, penalty, reducer) are available for use. See mystic.ensemble for more details. NOTE: dist can be used to add randomness to the sampler, and can accept a numpy distribution type such as numpy.random.normal, or a mystic distribution type built with mystic.math.Distribution. if dist=N, where N is an int or float, use normalized Gaussian noise, mystic.math.Distribution(numpy.random.normal, 0, sigma), where sigma is N * sum(bound) for each bound in the bounds, and N scales the amplitude of the noise (typically, N ~ 0.05). NOTE: if pts is negative (i.e. pts=-4), use solver-directed sampling. initial points are chosen by the sampler, then solvers run until converged. a LatticeSampler also accepts a list of pts, indicating the number of bins on each axis; if the product of npts is negative, then use solver-directed sampling. NOTE: given the model is cached, a klepto.dir_archive is created by default """ from mystic.samplers import LatticeSampler searcher = kwds.pop('sampler', LatticeSampler) ax = getattr(model, '__axis__', None) axis = None if hasattr(ax, '__len__') else ax # get default for axis axis = kwds.pop('axis', axis) # allow override? ny = kwds.pop('ny', getattr(model, 'ny', None)) #XXX: best? mvl = ny is not None # True if multivalued axis = axis if mvl else None #XXX: allow multi-axis search? dist = kwds.pop('dist', None) if isinstance(dist, (int, float)): # noise N(0, sig); sig = dist*(ub+lb) import numpy as np from mystic.math import Distribution sig = [dist * (ub + lb) for (lb, ub) in bounds] #FIXME: allow None and inf dist = Distribution(np.random.normal, 0, sig) map_ = kwds.pop('map', map) if not hasattr(model, '__cache__') or not hasattr(model, '__inverse__'): import mystic.cache as mc name = getattr(model, '__name__', None) #XXX: do better? model = mc.cached(archive=name, multivalued=mvl)(model) cache = model.__cache__ imodel = model.__inverse__ if hasattr(pts, '__len__'): import numpy as np pts, _pts = np.prod(pts), [abs(i) for i in pts] else: _pts = None if pts is None: pts = -1 if pts == 0: # don't sample, just grab the archive pass elif pts > 0: # sample pts without optimizing pts = pts if _pts is None else _pts def doit(axis=None): _model = _modelaxis(model, axis) s = searcher(bounds, _model, npts=pts, dist=dist, **kwds) s.sample() return s if mvl and axis is None: # as we don't optimize, we really don't need axis...? doit(axis=0) else: doit(axis) else: # search for minima until terminated pts = -pts if _pts is None else _pts def lower(axis=None): _model = _modelaxis(model, axis) s = searcher(bounds, _model, npts=pts, dist=dist, **kwds) s.sample_until(terminated=all) return s def upper(axis=None): model_ = _modelaxis(imodel, axis) si = searcher(bounds, model_, npts=pts, dist=dist, **kwds) si.sample_until(terminated=all) return si def _apply(f, arg): return f(arg) fs = lower, upper def doit(axis=None): return list(map_(_apply, fs, [axis] * len(fs))) if mvl and axis is None: if ny: import multiprocess.dummy as mt pool = mt.Pool() tmap = pool.map list(tmap(doit, range(ny))) pool.close() pool.join() else: #XXX: default to 0, warn, or error? doit(axis=0) else: doit(axis) import dataset as ds return ds.from_archive(cache(), axis=None)
def sample(model, bounds, pts=None, **kwds): """sample model within bounds, writing to an archive and returning data Inputs: model: a cached model function, of form y = model(x, axis=None) bounds: list of tuples of (lower,upper), bounds on each 'x' pts: int, number of points sampled by the sampler Additional Inputs: sampler: the mystic.sampler type [default: LatticeSampler] solver: the mystic.solver type [default: NelderMeadSimplexSolver] dist: a distribution type [default: numpy.random.normal] map: a map instance [default: builtins.map] ny: int, number of model outputs, len(y) [default: None] axis: int, index of output on which to search [default: None] Returns: the mystic.math.legacydata.dataset of sampled data NOTE: given the model is cached, a klepto.dir_archive is created by default NOTE: if pts is negative (i.e. pts=-4), use solver-directed sampling where initial points chosen by the sampler, then solvers run until converged NOTE: dist can be used to add randomness to the sampler """ from mystic.samplers import LatticeSampler searcher = kwds.get('sampler', LatticeSampler) from mystic.solvers import NelderMeadSimplexSolver solver = kwds.get('solver', NelderMeadSimplexSolver) ax = getattr(model, '__axis__', None) axis = None if hasattr(ax, '__len__') else ax # get default for axis axis = kwds.get('axis', axis) # allow override? ny = kwds.get('ny', getattr(model, 'ny', None)) #XXX: best? mvl = ny is not None # True if multivalued axis = axis if mvl else None #XXX: allow multi-axis search? import numpy as np dist = kwds.get('dist', np.random.normal) map_ = kwds.get('map', map) if not hasattr(model, '__cache__') or not hasattr(model, '__inverse__'): import mystic.cache as mc name = getattr(model, '__name__', None) #XXX: do better? model = mc.cached(archive=name, multivalued=mvl)(model) cache = model.__cache__ imodel = model.__inverse__ if hasattr(pts, '__len__'): pts, _pts = -np.prod(pts), pts else: _pts = None if pts is None: pts = -1 if pts == 0: # don't sample, just grab the archive pass elif pts > 0: # sample pts without optimizing def doit(axis=None): _model = lambda x: model(x, axis=axis) s = searcher(bounds, _model, npts=pts, solver=solver, dist=dist) s.sample() return s if mvl and axis is None: # as we don't optimize, we really don't need axis...? doit(axis=0) else: doit(axis) else: # search for minima until terminated pts = -pts if _pts is None else _pts def lower(axis=None): _model = lambda x: model(x, axis=axis) s = searcher(bounds, _model, npts=pts, solver=solver, dist=dist) s.sample_until(terminated=all) return s def upper(axis=None): model_ = lambda x: imodel(x, axis=axis) si = searcher(bounds, model_, npts=pts, solver=solver, dist=dist) si.sample_until(terminated=all) return si def _apply(f, arg): return f(arg) fs = lower, upper def doit(axis=None): return list(map_(_apply, fs, [axis] * len(fs))) if mvl and axis is None: if ny: import multiprocess.dummy as mt pool = mt.Pool() tmap = pool.map list(tmap(doit, range(ny))) pool.close() pool.join() else: #XXX: default to 0, warn, or error? doit(axis=0) else: doit(axis) import dataset as ds return ds.from_archive(cache(), axis=None)