def test_random_state(self): # ensure that the global random state is not modified because # the directed Hausdorff algorithm uses randomization rs = check_random_state(None) old_global_state = rs.get_state() directed_hausdorff(self.path_1, self.path_2) rs2 = check_random_state(None) new_global_state = rs2.get_state() assert_equal(new_global_state, old_global_state)
def test_random_state_None_int(self): # check that seed values of None or int do not alter global # random state for seed in [None, 27870671]: rs = check_random_state(None) old_global_state = rs.get_state() directed_hausdorff(self.path_1, self.path_2, seed) rs2 = check_random_state(None) new_global_state = rs2.get_state() assert_equal(new_global_state, old_global_state)
def test_check_random_state(): # If seed is None, return the RandomState singleton used by np.random. # If seed is an int, return a new RandomState instance seeded with seed. # If seed is already a RandomState instance, return it. # Otherwise raise ValueError. rsi = check_random_state(1) assert_equal(type(rsi), np.random.RandomState) rsi = check_random_state(rsi) assert_equal(type(rsi), np.random.RandomState) rsi = check_random_state(None) assert_equal(type(rsi), np.random.RandomState) assert_raises(ValueError, check_random_state, 'a')
def _init_reciprocal_perms(cats, permutations=1000, random_state=None): """ Creates a reciprocal permutation matrix. This is to ease the process of division. cats: numpy.array List of binary class assignments permutations: int Number of permutations for permutation test Note: This can only handle binary classes now """ random_state = check_random_state(random_state) num_cats = len(np.unique(cats)) # number of distinct categories c = len(cats) copy_cats = copy.deepcopy(cats) perms = np.array(np.zeros((c, num_cats*(permutations+1)), dtype=np.float64)) _samp_ones = np.array(np.ones(c), dtype=np.float64).transpose() for m in range(permutations+1): # Perform division to make mean calculation easier perms[:, 2*m] = copy_cats / float(copy_cats.sum()) perms[:, 2*m+1] = (_samp_ones - copy_cats) perms[:, 2*m+1] /= float((_samp_ones - copy_cats).sum()) random_state.shuffle(copy_cats) return perms
def __init__(self, fun, x0, bounds, seed=None, minimizer_kwargs=None, temperature_start=5230, qv=2.62, qa=-5.0, maxfun=1e7, maxsteps=500, pure_sa=False): if x0 is not None and not len(x0) == len(bounds): raise ValueError('Bounds size does not match x0') lu = list(zip(*bounds)) lower = np.array(lu[0]) upper = np.array(lu[1]) # Checking that bounds are consistent if not np.all(lower < upper): raise ValueError('Bounds are note consistent min < max') # Wrapper for the objective function and minimizer if minimizer_kwargs is None: minimizer_kwargs = dict() self.owf = ObjectiveFunWrapper(bounds, fun, **minimizer_kwargs) # Initialization of RandomState for reproducible runs if seed provided self.rs = check_random_state(seed) # Initialization of the energy state self.es = EnergyState(lower, upper) self.es.reset(self.owf, self.rs, x0) # Maximum number of function call that can be used a stopping criterion self.maxfun = maxfun # Maximum number of step (main iteration) that can be used as # stopping criterion self.maxsteps = maxsteps # Minimum value of annealing temperature reached to perform # re-annealing self.temperature_start = temperature_start self.temperature_restart = 0.1 # VisitingDistribution instance vd = VisitingDistribution(lower, upper, qv, self.rs) # Markov chain instance self.mc = MarkovChain(qa, vd, self.owf, self.rs, self.es) self.qv = qv self.pure_sa = pure_sa
def test__reset(self): owf = ObjectiveFunWrapper(self.ld_bounds, self.weirdfunc) lu = list(zip(*self.ld_bounds)) lower = np.array(lu[0]) upper = np.array(lu[1]) es = EnergyState(lower, upper) assert_raises(ValueError, es.reset, *(owf, check_random_state(None)))
def __init__(self, mixture_model, sampler=None, seed=None): super(GenericProcess, self).__init__() self._mixture_model = self._check_mixture_model(mixture_model) self._sampler = self._check_sampler(sampler) self._random_state = check_random_state(seed)
def setUp(self): # Using Rastrigin function for performing tests self.func = lambda x: np.sum(x * x - 10 * np.cos( 2 * np.pi * x)) + 10 * np.size(x) # A function that returns always a big value for initialization tests self.weirdfunc = lambda x: 2e16 self.ld_bounds = [(-5.12, 5.12)] * 2 self.hd_bounds = self.ld_bounds * 4 self.nbtestvalues = 5000 self.high_temperature = 5230 self.low_temperature = 0.1 self.qv = 2.62 self.defautgr = (self.func, None, self.ld_bounds) self.seed = 1234 self.rs = check_random_state(self.seed)
def setUp(self): # A function that returns always infinity for initialization tests self.weirdfunc = lambda x: np.inf # 2-D bounds for testing function self.ld_bounds = [(-5.12, 5.12)] * 2 # 4-D bounds for testing function self.hd_bounds = self.ld_bounds * 4 # Number of values to be generated for testing visit function self.nbtestvalues = 5000 self.high_temperature = 5230 self.low_temperature = 0.1 self.qv = 2.62 self.seed = 1234 self.rs = check_random_state(self.seed) self.nb_fun_call = 0 self.ngev = 0
def _init_perms(vec, permutations=1000, random_state=None): """ Creates a permutation matrix vec: numpy.array Array of values to be permuted permutations: int Number of permutations for permutation test Note: This can only handle binary classes now """ random_state = check_random_state(random_state) c = len(vec) copy_vec = copy.deepcopy(vec) perms = np.array(np.zeros((c, permutations+1), dtype=np.float64)) for m in range(permutations+1): perms[:, m] = copy_vec random_state.shuffle(copy_vec) return perms
def cwt_matrix(n_rows, n_columns, seed=None): r"""" Generate a matrix S which represents a Clarkson-Woodruff transform. Given the desired size of matrix, the method returns a matrix S of size (n_rows, n_columns) where each column has all the entries set to 0 except for one position which has been randomly set to +1 or -1 with equal probability. Parameters ---------- n_rows: int Number of rows of S n_columns: int Number of columns of S seed : None or int or `numpy.random.RandomState` instance, optional This parameter defines the ``RandomState`` object to use for drawing random variates. If None (or ``np.random``), the global ``np.random`` state is used. If integer, it is used to seed the local ``RandomState`` instance. Default is None. Returns ------- S : (n_rows, n_columns) csc_matrix The returned matrix has ``n_columns`` nonzero entries. Notes ----- Given a matrix A, with probability at least 9/10, .. math:: \|SA\| = (1 \pm \epsilon)\|A\| Where the error epsilon is related to the size of S. """ rng = check_random_state(seed) rows = rng.randint(0, n_rows, n_columns) cols = np.arange(n_columns+1) signs = rng.choice([1, -1], n_columns) S = csc_matrix((signs, rows, cols),shape=(n_rows, n_columns)) return S
def _init_categorical_perms(cats, permutations=1000, random_state=None): """ Creates a reciprocal permutation matrix cats: numpy.array List of binary class assignments permutations: int Number of permutations for permutation test Note: This can only handle binary classes now """ random_state = check_random_state(random_state) c = len(cats) num_cats = len(np.unique(cats)) # Number of distinct categories copy_cats = copy.deepcopy(cats) perms = np.array(np.zeros((c, num_cats*(permutations+1)), dtype=np.float64)) for m in range(permutations+1): for i in range(num_cats): perms[:, num_cats*m+i] = (copy_cats == i).astype(np.float64) random_state.shuffle(copy_cats) return perms
def __init__(self, T, random_gen=None): # Avoid ZeroDivisionError since "MBH can be regarded as a special case # of the BH framework with the Metropolis criterion, where temperature # T = 0." (Reject all steps that increase energy.) self.beta = 1.0 / T if T != 0 else float('inf') self.random_gen = check_random_state(random_gen)
def basinhopping(func, x0, niter=100, T=1.0, stepsize=0.5, minimizer_kwargs=None, take_step=None, accept_test=None, callback=None, interval=50, disp=False, niter_success=None, seed=None): """Find the global minimum of a function using the basin-hopping algorithm. Basin-hopping is a two-phase method that combines a global stepping algorithm with local minimization at each step. Designed to mimic the natural process of energy minimization of clusters of atoms, it works well for similar problems with "funnel-like, but rugged" energy landscapes [5]_. As the step-taking, step acceptance, and minimization methods are all customizable, this function can also be used to implement other two-phase methods. Parameters ---------- func : callable ``f(x, *args)`` Function to be optimized. ``args`` can be passed as an optional item in the dict ``minimizer_kwargs`` x0 : array_like Initial guess. niter : integer, optional The number of basin-hopping iterations. There will be a total of ``niter + 1`` runs of the local minimizer. T : float, optional The "temperature" parameter for the accept or reject criterion. Higher "temperatures" mean that larger jumps in function value will be accepted. For best results ``T`` should be comparable to the separation (in function value) between local minima. stepsize : float, optional Maximum step size for use in the random displacement. minimizer_kwargs : dict, optional Extra keyword arguments to be passed to the local minimizer ``scipy.optimize.minimize()`` Some important options could be: method : str The minimization method (e.g. ``"L-BFGS-B"``) args : tuple Extra arguments passed to the objective function (``func``) and its derivatives (Jacobian, Hessian). take_step : callable ``take_step(x)``, optional Replace the default step-taking routine with this routine. The default step-taking routine is a random displacement of the coordinates, but other step-taking algorithms may be better for some systems. ``take_step`` can optionally have the attribute ``take_step.stepsize``. If this attribute exists, then ``basinhopping`` will adjust ``take_step.stepsize`` in order to try to optimize the global minimum search. accept_test : callable, ``accept_test(f_new=f_new, x_new=x_new, f_old=fold, x_old=x_old)``, optional Define a test which will be used to judge whether or not to accept the step. This will be used in addition to the Metropolis test based on "temperature" ``T``. The acceptable return values are True, False, or ``"force accept"``. If any of the tests return False then the step is rejected. If the latter, then this will override any other tests in order to accept the step. This can be used, for example, to forcefully escape from a local minimum that ``basinhopping`` is trapped in. callback : callable, ``callback(x, f, accept)``, optional A callback function which will be called for all minima found. ``x`` and ``f`` are the coordinates and function value of the trial minimum, and ``accept`` is whether or not that minimum was accepted. This can be used, for example, to save the lowest N minima found. Also, ``callback`` can be used to specify a user defined stop criterion by optionally returning True to stop the ``basinhopping`` routine. interval : integer, optional interval for how often to update the ``stepsize`` disp : bool, optional Set to True to print status messages niter_success : integer, optional Stop the run if the global minimum candidate remains the same for this number of iterations. seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional If `seed` is None (or `np.random`), the `numpy.random.RandomState` singleton is used. If `seed` is an int, a new ``RandomState`` instance is used, seeded with `seed`. If `seed` is already a ``Generator`` or ``RandomState`` instance then that instance is used. Specify `seed` for repeatable minimizations. The random numbers generated with this seed only affect the default Metropolis `accept_test` and the default `take_step`. If you supply your own `take_step` and `accept_test`, and these functions use random number generation, then those functions are responsible for the state of their random number generator. Returns ------- res : OptimizeResult The optimization result represented as a ``OptimizeResult`` object. Important attributes are: ``x`` the solution array, ``fun`` the value of the function at the solution, and ``message`` which describes the cause of the termination. The ``OptimizeResult`` object returned by the selected minimizer at the lowest minimum is also contained within this object and can be accessed through the ``lowest_optimization_result`` attribute. See `OptimizeResult` for a description of other attributes. See Also -------- minimize : The local minimization function called once for each basinhopping step. ``minimizer_kwargs`` is passed to this routine. Notes ----- Basin-hopping is a stochastic algorithm which attempts to find the global minimum of a smooth scalar function of one or more variables [1]_ [2]_ [3]_ [4]_. The algorithm in its current form was described by David Wales and Jonathan Doye [2]_ http://www-wales.ch.cam.ac.uk/. The algorithm is iterative with each cycle composed of the following features 1) random perturbation of the coordinates 2) local minimization 3) accept or reject the new coordinates based on the minimized function value The acceptance test used here is the Metropolis criterion of standard Monte Carlo algorithms, although there are many other possibilities [3]_. This global minimization method has been shown to be extremely efficient for a wide variety of problems in physics and chemistry. It is particularly useful when the function has many minima separated by large barriers. See the Cambridge Cluster Database http://www-wales.ch.cam.ac.uk/CCD.html for databases of molecular systems that have been optimized primarily using basin-hopping. This database includes minimization problems exceeding 300 degrees of freedom. See the free software program GMIN (http://www-wales.ch.cam.ac.uk/GMIN) for a Fortran implementation of basin-hopping. This implementation has many different variations of the procedure described above, including more advanced step taking algorithms and alternate acceptance criterion. For stochastic global optimization there is no way to determine if the true global minimum has actually been found. Instead, as a consistency check, the algorithm can be run from a number of different random starting points to ensure the lowest minimum found in each example has converged to the global minimum. For this reason, ``basinhopping`` will by default simply run for the number of iterations ``niter`` and return the lowest minimum found. It is left to the user to ensure that this is in fact the global minimum. Choosing ``stepsize``: This is a crucial parameter in ``basinhopping`` and depends on the problem being solved. The step is chosen uniformly in the region from x0-stepsize to x0+stepsize, in each dimension. Ideally, it should be comparable to the typical separation (in argument values) between local minima of the function being optimized. ``basinhopping`` will, by default, adjust ``stepsize`` to find an optimal value, but this may take many iterations. You will get quicker results if you set a sensible initial value for ``stepsize``. Choosing ``T``: The parameter ``T`` is the "temperature" used in the Metropolis criterion. Basinhopping steps are always accepted if ``func(xnew) < func(xold)``. Otherwise, they are accepted with probability:: exp( -(func(xnew) - func(xold)) / T ) So, for best results, ``T`` should to be comparable to the typical difference (in function values) between local minima. (The height of "walls" between local minima is irrelevant.) If ``T`` is 0, the algorithm becomes Monotonic Basin-Hopping, in which all steps that increase energy are rejected. .. versionadded:: 0.12.0 References ---------- .. [1] Wales, David J. 2003, Energy Landscapes, Cambridge University Press, Cambridge, UK. .. [2] Wales, D J, and Doye J P K, Global Optimization by Basin-Hopping and the Lowest Energy Structures of Lennard-Jones Clusters Containing up to 110 Atoms. Journal of Physical Chemistry A, 1997, 101, 5111. .. [3] Li, Z. and Scheraga, H. A., Monte Carlo-minimization approach to the multiple-minima problem in protein folding, Proc. Natl. Acad. Sci. USA, 1987, 84, 6611. .. [4] Wales, D. J. and Scheraga, H. A., Global optimization of clusters, crystals, and biomolecules, Science, 1999, 285, 1368. .. [5] Olson, B., Hashmi, I., Molloy, K., and Shehu1, A., Basin Hopping as a General and Versatile Optimization Framework for the Characterization of Biological Macromolecules, Advances in Artificial Intelligence, Volume 2012 (2012), Article ID 674832, :doi:`10.1155/2012/674832` Examples -------- The following example is a 1-D minimization problem, with many local minima superimposed on a parabola. >>> from scipy.optimize import basinhopping >>> func = lambda x: np.cos(14.5 * x - 0.3) + (x + 0.2) * x >>> x0=[1.] Basinhopping, internally, uses a local minimization algorithm. We will use the parameter ``minimizer_kwargs`` to tell basinhopping which algorithm to use and how to set up that minimizer. This parameter will be passed to ``scipy.optimize.minimize()``. >>> minimizer_kwargs = {"method": "BFGS"} >>> ret = basinhopping(func, x0, minimizer_kwargs=minimizer_kwargs, ... niter=200) >>> print("global minimum: x = %.4f, f(x0) = %.4f" % (ret.x, ret.fun)) global minimum: x = -0.1951, f(x0) = -1.0009 Next consider a 2-D minimization problem. Also, this time, we will use gradient information to significantly speed up the search. >>> def func2d(x): ... f = np.cos(14.5 * x[0] - 0.3) + (x[1] + 0.2) * x[1] + (x[0] + ... 0.2) * x[0] ... df = np.zeros(2) ... df[0] = -14.5 * np.sin(14.5 * x[0] - 0.3) + 2. * x[0] + 0.2 ... df[1] = 2. * x[1] + 0.2 ... return f, df We'll also use a different local minimization algorithm. Also, we must tell the minimizer that our function returns both energy and gradient (Jacobian). >>> minimizer_kwargs = {"method":"L-BFGS-B", "jac":True} >>> x0 = [1.0, 1.0] >>> ret = basinhopping(func2d, x0, minimizer_kwargs=minimizer_kwargs, ... niter=200) >>> print("global minimum: x = [%.4f, %.4f], f(x0) = %.4f" % (ret.x[0], ... ret.x[1], ... ret.fun)) global minimum: x = [-0.1951, -0.1000], f(x0) = -1.0109 Here is an example using a custom step-taking routine. Imagine you want the first coordinate to take larger steps than the rest of the coordinates. This can be implemented like so: >>> class MyTakeStep: ... def __init__(self, stepsize=0.5): ... self.stepsize = stepsize ... self.rng = np.random.default_rng() ... def __call__(self, x): ... s = self.stepsize ... x[0] += self.rng.uniform(-2.*s, 2.*s) ... x[1:] += self.rng.uniform(-s, s, x[1:].shape) ... return x Since ``MyTakeStep.stepsize`` exists basinhopping will adjust the magnitude of ``stepsize`` to optimize the search. We'll use the same 2-D function as before >>> mytakestep = MyTakeStep() >>> ret = basinhopping(func2d, x0, minimizer_kwargs=minimizer_kwargs, ... niter=200, take_step=mytakestep) >>> print("global minimum: x = [%.4f, %.4f], f(x0) = %.4f" % (ret.x[0], ... ret.x[1], ... ret.fun)) global minimum: x = [-0.1951, -0.1000], f(x0) = -1.0109 Now, let's do an example using a custom callback function which prints the value of every minimum found >>> def print_fun(x, f, accepted): ... print("at minimum %.4f accepted %d" % (f, int(accepted))) We'll run it for only 10 basinhopping steps this time. >>> rng = np.random.default_rng() >>> ret = basinhopping(func2d, x0, minimizer_kwargs=minimizer_kwargs, ... niter=10, callback=print_fun, seed=rng) at minimum 0.4159 accepted 1 at minimum -0.4317 accepted 1 at minimum -1.0109 accepted 1 at minimum -0.9073 accepted 1 at minimum -0.4317 accepted 0 at minimum -0.1021 accepted 1 at minimum -0.7425 accepted 1 at minimum -0.9073 accepted 1 at minimum -0.4317 accepted 0 at minimum -0.7425 accepted 1 at minimum -0.9073 accepted 1 The minimum at -1.0109 is actually the global minimum, found already on the 8th iteration. Now let's implement bounds on the problem using a custom ``accept_test``: >>> class MyBounds: ... def __init__(self, xmax=[1.1,1.1], xmin=[-1.1,-1.1] ): ... self.xmax = np.array(xmax) ... self.xmin = np.array(xmin) ... def __call__(self, **kwargs): ... x = kwargs["x_new"] ... tmax = bool(np.all(x <= self.xmax)) ... tmin = bool(np.all(x >= self.xmin)) ... return tmax and tmin >>> mybounds = MyBounds() >>> ret = basinhopping(func2d, x0, minimizer_kwargs=minimizer_kwargs, ... niter=10, accept_test=mybounds) """ x0 = np.array(x0) # set up the np.random generator rng = check_random_state(seed) # set up minimizer if minimizer_kwargs is None: minimizer_kwargs = dict() wrapped_minimizer = MinimizerWrapper(scipy.optimize.minimize, func, **minimizer_kwargs) # set up step-taking algorithm if take_step is not None: if not callable(take_step): raise TypeError("take_step must be callable") # if take_step.stepsize exists then use AdaptiveStepsize to control # take_step.stepsize if hasattr(take_step, "stepsize"): take_step_wrapped = AdaptiveStepsize(take_step, interval=interval, verbose=disp) else: take_step_wrapped = take_step else: # use default displace = RandomDisplacement(stepsize=stepsize, random_gen=rng) take_step_wrapped = AdaptiveStepsize(displace, interval=interval, verbose=disp) # set up accept tests accept_tests = [] if accept_test is not None: if not callable(accept_test): raise TypeError("accept_test must be callable") accept_tests = [accept_test] # use default metropolis = Metropolis(T, random_gen=rng) accept_tests.append(metropolis) if niter_success is None: niter_success = niter + 2 bh = BasinHoppingRunner(x0, wrapped_minimizer, take_step_wrapped, accept_tests, disp=disp) # The wrapped minimizer is called once during construction of # BasinHoppingRunner, so run the callback if callable(callback): callback(bh.storage.minres.x, bh.storage.minres.fun, True) # start main iteration loop count, i = 0, 0 message = [ "requested number of basinhopping iterations completed" " successfully" ] for i in range(niter): new_global_min = bh.one_cycle() if callable(callback): # should we pass a copy of x? val = callback(bh.xtrial, bh.energy_trial, bh.accept) if val is not None: if val: message = [ "callback function requested stop early by" "returning True" ] break count += 1 if new_global_min: count = 0 elif count > niter_success: message = ["success condition satisfied"] break # prepare return object res = bh.res res.lowest_optimization_result = bh.storage.get_lowest() res.x = np.copy(res.lowest_optimization_result.x) res.fun = res.lowest_optimization_result.fun res.message = message res.nit = i + 1 res.success = res.lowest_optimization_result.success return res
def __init__(self, func, bounds, args=(), strategy='best1bin', maxiter=1000, popsize=15, tol=0.01, mutation=(0.5, 1), recombination=0.7, seed=None, maxfun=np.inf, callback=None, disp=False, polish=True, init='latinhypercube', atol=0): if strategy in self._binomial: self.mutation_func = getattr(self, self._binomial[strategy]) elif strategy in self._exponential: self.mutation_func = getattr(self, self._exponential[strategy]) else: raise ValueError("Please select a valid mutation strategy") self.strategy = strategy self.callback = callback self.polish = polish # relative and absolute tolerances for convergence self.tol, self.atol = tol, atol # Mutation constant should be in [0, 2). If specified as a sequence # then dithering is performed. self.scale = mutation if (not np.all(np.isfinite(mutation)) or np.any(np.array(mutation) >= 2) or np.any(np.array(mutation) < 0)): raise ValueError('The mutation constant must be a float in ' 'U[0, 2), or specified as a tuple(min, max)' ' where min < max and min, max are in U[0, 2).') self.dither = None if hasattr(mutation, '__iter__') and len(mutation) > 1: self.dither = [mutation[0], mutation[1]] self.dither.sort() self.cross_over_probability = recombination self.func = func self.args = args # convert tuple of lower and upper bounds to limits # [(low_0, high_0), ..., (low_n, high_n] # -> [[low_0, ..., low_n], [high_0, ..., high_n]] self.limits = np.array(bounds, dtype='float').T if (np.size(self.limits, 0) != 2 or not np.all(np.isfinite(self.limits))): raise ValueError('bounds should be a sequence containing ' 'real valued (min, max) pairs for each value' ' in x') if maxiter is None: # the default used to be None maxiter = 1000 self.maxiter = maxiter if maxfun is None: # the default used to be None maxfun = np.inf self.maxfun = maxfun # population is scaled to between [0, 1]. # We have to scale between parameter <-> population # save these arguments for _scale_parameter and # _unscale_parameter. This is an optimization self.__scale_arg1 = 0.5 * (self.limits[0] + self.limits[1]) self.__scale_arg2 = np.fabs(self.limits[0] - self.limits[1]) self.parameter_count = np.size(self.limits, 1) self.random_number_generator = check_random_state(seed) # default population initialization is a latin hypercube design, but # there are other population initializations possible. # the minimum is 5 because 'best2bin' requires a population that's at # least 5 long self.num_population_members = max(5, popsize * self.parameter_count) self.population_shape = (self.num_population_members, self.parameter_count) self._nfev = 0 if isinstance(init, string_types): if init == 'latinhypercube': self.init_population_lhs() elif init == 'random': self.init_population_random() else: raise ValueError(self.__init_error_msg) else: self.init_population_array(init) self.disp = disp
def sample( self, steps, nthin=1, random_state=None, f=None, callback=None, verbose=True, pool=-1, ): """ Performs sampling from the objective. Parameters ---------- steps : int Collect `steps` samples into the chain. The sampler will run a total of `steps * nthin` moves. nthin : int, optional Each chain sample is separated by `nthin` iterations. random_state : {int, `np.random.RandomState`, `np.random.Generator`} If `random_state` is not specified the `~np.random.RandomState` singleton is used. If `random_state` is an int, a new ``RandomState`` instance is used, seeded with random_state. If `random_state` is already a ``RandomState`` or a ``Generator`` instance, then that object is used. Specify `random_state` for repeatable minimizations. f : file-like or str File to incrementally save chain progress to. Each row in the file is a flattened array of size `(nwalkers, ndim)` or `(ntemps, nwalkers, ndim)`. There are `steps` rows in the file. callback : callable callback function to be called at each iteration step. Has the signature `callback(coords, logprob)`. verbose : bool, optional Gives updates on the sampling progress pool : int or map-like object, optional If `pool` is an `int` then it specifies the number of threads to use for parallelization. If `pool == -1`, then all CPU's are used. If pool is a map-like callable that follows the same calling sequence as the built-in map function, then this pool is used for parallelisation. Notes ----- Please see :class:`emcee.EnsembleSampler` for its detailed behaviour. >>> # we'll burn the first 500 steps >>> fitter.sample(500) >>> # after you've run those, then discard them by resetting the >>> # sampler. >>> fitter.sampler.reset() >>> # Now collect 40 steps, each step separated by 50 sampler >>> # generations. >>> fitter.sample(40, nthin=50) One can also burn and thin in `Curvefitter.process_chain`. """ self._check_vars_unchanged() # setup a random number generator rng = check_random_state(random_state) if self._state is None: self.initialise(random_state=rng) # for saving progress to file def _callback_wrapper(state, h=None): if callback is not None: callback(state.coords, state.log_prob) if h is not None: h.write(" ".join(map(str, state.coords.ravel()))) h.write("\n") # remove chains from each of the parameters because they slow down # pickling but only if they are parameter objects. flat_params = f_unique(flatten(self.objective.parameters)) flat_params = [param for param in flat_params if is_parameter(param)] # zero out all the old parameter stderrs for param in flat_params: param.stderr = None param.chain = None # make sure the checkpoint file exists if f is not None: with possibly_open_file(f, "w") as h: # write the shape of each step of the chain h.write("# ") shape = self._state.coords.shape h.write(", ".join(map(str, shape))) h.write("\n") # set the random state of the sampler # normally one could give this as an argument to the sample method # but PTSampler didn't historically accept that... if isinstance(rng, np.random.RandomState): rstate0 = rng.get_state() self._state.random_state = rstate0 self.sampler.random_state = rstate0 # using context manager means we kill off zombie pool objects # but does mean that the pool has to be specified each time. with MapWrapper(pool) as g, possibly_open_file(f, "a") as h: # these kwargs are provided to the sampler.sample method kwargs = {"iterations": steps, "thin": nthin} # if you're not creating more than 1 thread, then don't bother with # a pool. if isinstance(self.sampler, emcee.EnsembleSampler): if pool == 1: self.sampler.pool = None else: self.sampler.pool = g else: kwargs["mapper"] = g # new emcee arguments sampler_args = getargspec(self.sampler.sample).args if "progress" in sampler_args and verbose: kwargs["progress"] = True verbose = False if "thin_by" in sampler_args: kwargs["thin_by"] = nthin kwargs.pop("thin", 0) # perform the sampling for state in self.sampler.sample(self._state, **kwargs): self._state = state _callback_wrapper(state, h=h) if isinstance(self.sampler, emcee.EnsembleSampler): self.sampler.pool = None # sets parameter value and stderr return process_chain(self.objective, self.chain)
def _quadratic_assignment_faq(A, B, maximize=False, partial_match=None, rng=None, P0="barycenter", shuffle_input=False, maxiter=30, tol=0.03, **unknown_options): r""" Solve the quadratic assignment problem (approximately). This function solves the Quadratic Assignment Problem (QAP) and the Graph Matching Problem (GMP) using the Fast Approximate QAP Algorithm (FAQ) [1]_. Quadratic assignment solves problems of the following form: .. math:: \min_P & \ {\ \text{trace}(A^T P B P^T)}\\ \mbox{s.t. } & {P \ \epsilon \ \mathcal{P}}\\ where :math:`\mathcal{P}` is the set of all permutation matrices, and :math:`A` and :math:`B` are square matrices. Graph matching tries to *maximize* the same objective function. This algorithm can be thought of as finding the alignment of the nodes of two graphs that minimizes the number of induced edge disagreements, or, in the case of weighted graphs, the sum of squared edge weight differences. Note that the quadratic assignment problem is NP-hard. The results given here are approximations and are not guaranteed to be optimal. Parameters ---------- A : 2-D array, square The square matrix :math:`A` in the objective function above. B : 2-D array, square The square matrix :math:`B` in the objective function above. method : str in {'faq', '2opt'} (default: 'faq') The algorithm used to solve the problem. This is the method-specific documentation for 'faq'. :ref:`'2opt' <optimize.qap-2opt>` is also available. Options ------- maximize : bool (default: False) Maximizes the objective function if ``True``. partial_match : 2-D array of integers, optional (default: None) Fixes part of the matching. Also known as a "seed" [2]_. Each row of `partial_match` specifies a pair of matched nodes: node ``partial_match[i, 0]`` of `A` is matched to node ``partial_match[i, 1]`` of `B`. The array has shape ``(m, 2)``, where ``m`` is not greater than the number of nodes, :math:`n`. rng : int, `RandomState`, `Generator` or None, optional (default: None) Accepts an integer as a seed for the random generator or a ``RandomState`` or ``Generator`` object. If None (default), uses global `numpy.random` random state. P0 : 2-D array, "barycenter", or "randomized" (default: "barycenter") Initial position. Must be a doubly-stochastic matrix [3]_. If the initial position is an array, it must be a doubly stochastic matrix of size :math:`m' \times m'` where :math:`m' = n - m`. If ``"barycenter"`` (default), the initial position is the barycenter of the Birkhoff polytope (the space of doubly stochastic matrices). This is a :math:`m' \times m'` matrix with all entries equal to :math:`1 / m'`. If ``"randomized"`` the initial search position is :math:`P_0 = (J + K) / 2`, where :math:`J` is the barycenter and :math:`K` is a random doubly stochastic matrix. shuffle_input : bool (default: False) Set to `True` to resolve degenerate gradients randomly. For non-degenerate gradients this option has no effect. maxiter : int, positive (default: 30) Integer specifying the max number of Frank-Wolfe iterations performed. tol : float (default: 0.03) Tolerance for termination. Frank-Wolfe iteration terminates when :math:`\frac{||P_{i}-P_{i+1}||_F}{\sqrt{m')}} \leq tol`, where :math:`i` is the iteration number. Returns ------- res : OptimizeResult `OptimizeResult` containing the following fields. col_ind : 1-D array Column indices corresponding to the best permutation found of the nodes of `B`. fun : float The objective value of the solution. nit : int The number of Frank-Wolfe iterations performed. Notes ----- The algorithm may be sensitive to the initial permutation matrix (or search "position") due to the possibility of several local minima within the feasible region. A barycenter initialization is more likely to result in a better solution than a single random initialization. However, calling ``quadratic_assignment`` several times with different random initializations may result in a better optimum at the cost of longer total execution time. Examples -------- As mentioned above, a barycenter initialization often results in a better solution than a single random initialization. >>> np.random.seed(0) >>> n = 15 >>> A = np.random.rand(n, n) >>> B = np.random.rand(n, n) >>> res = quadratic_assignment(A, B) # FAQ is default method >>> print(res.fun) 46.871483385480545 >>> options = {"P0": "randomized"} # use randomized initialization >>> res = quadratic_assignment(A, B, options=options) >>> print(res.fun) 47.224831071310625 # may vary However, consider running from several randomized initializations and keeping the best result. >>> res = min([quadratic_assignment(A, B, options=options) ... for i in range(30)], key=lambda x: x.fun) >>> print(res.fun) 46.671852533681516 # may vary The '2-opt' method can be used to further refine the results. >>> options = {"partial_guess": np.array([np.arange(n), res.col_ind]).T} >>> res = quadratic_assignment(A, B, method="2opt", options=options) >>> print(res.fun) 46.47160735721583 # may vary References ---------- .. [1] J.T. Vogelstein, J.M. Conroy, V. Lyzinski, L.J. Podrazik, S.G. Kratzer, E.T. Harley, D.E. Fishkind, R.J. Vogelstein, and C.E. Priebe, "Fast approximate quadratic programming for graph matching," PLOS one, vol. 10, no. 4, p. e0121002, 2015, :doi:`10.1371/journal.pone.0121002` .. [2] D. Fishkind, S. Adali, H. Patsolic, L. Meng, D. Singh, V. Lyzinski, C. Priebe, "Seeded graph matching", Pattern Recognit. 87 (2019): 203-215, :doi:`10.1016/j.patcog.2018.09.014` .. [3] "Doubly stochastic Matrix," Wikipedia. https://en.wikipedia.org/wiki/Doubly_stochastic_matrix """ _check_unknown_options(unknown_options) maxiter = operator.index(maxiter) # ValueError check A, B, partial_match = _common_input_validation(A, B, partial_match) msg = None if isinstance(P0, str) and P0 not in {'barycenter', 'randomized'}: msg = "Invalid 'P0' parameter string" elif maxiter <= 0: msg = "'maxiter' must be a positive integer" elif tol <= 0: msg = "'tol' must be a positive float" if msg is not None: raise ValueError(msg) rng = check_random_state(rng) n = len(A) # number of vertices in graphs n_seeds = len(partial_match) # number of seeds n_unseed = n - n_seeds # [1] Algorithm 1 Line 1 - choose initialization if not isinstance(P0, str): P0 = np.atleast_2d(P0) if P0.shape != (n_unseed, n_unseed): msg = "`P0` matrix must have shape m' x m', where m'=n-m" elif ((P0 < 0).any() or not np.allclose(np.sum(P0, axis=0), 1) or not np.allclose(np.sum(P0, axis=1), 1)): msg = "`P0` matrix must be doubly stochastic" if msg is not None: raise ValueError(msg) elif P0 == 'barycenter': P0 = np.ones((n_unseed, n_unseed)) / n_unseed elif P0 == 'randomized': J = np.ones((n_unseed, n_unseed)) / n_unseed # generate a nxn matrix where each entry is a random number [0, 1] # would use rand, but Generators don't have it # would use random, but old mtrand.RandomStates don't have it K = _doubly_stochastic(rng.uniform(size=(n_unseed, n_unseed))) P0 = (J + K) / 2 # check trivial cases if n == 0 or n_seeds == n: score = _calc_score(A, B, partial_match[:, 1]) res = {"col_ind": partial_match[:, 1], "fun": score, "nit": 0} return OptimizeResult(res) obj_func_scalar = 1 if maximize: obj_func_scalar = -1 nonseed_B = np.setdiff1d(range(n), partial_match[:, 1]) if shuffle_input: nonseed_B = rng.permutation(nonseed_B) nonseed_A = np.setdiff1d(range(n), partial_match[:, 0]) perm_A = np.concatenate([partial_match[:, 0], nonseed_A]) perm_B = np.concatenate([partial_match[:, 1], nonseed_B]) # definitions according to Seeded Graph Matching [2]. A11, A12, A21, A22 = _split_matrix(A[perm_A][:, perm_A], n_seeds) B11, B12, B21, B22 = _split_matrix(B[perm_B][:, perm_B], n_seeds) const_sum = A21 @ B21.T + A12.T @ B12 P = P0 # [1] Algorithm 1 Line 2 - loop while stopping criteria not met for n_iter in range(1, maxiter + 1): # [1] Algorithm 1 Line 3 - compute the gradient of f(P) = -tr(APB^tP^t) grad_fp = (const_sum + A22 @ P @ B22.T + A22.T @ P @ B22) # [1] Algorithm 1 Line 4 - get direction Q by solving Eq. 8 _, cols = linear_sum_assignment(grad_fp, maximize=maximize) Q = np.eye(n_unseed)[cols] # [1] Algorithm 1 Line 5 - compute the step size # Noting that e.g. trace(Ax) = trace(A)*x, expand and re-collect # terms as ax**2 + bx + c. c does not affect location of minimum # and can be ignored. Also, note that trace(A@B) = (A.T*B).sum(); # apply where possible for efficiency. R = P - Q b21 = ((R.T @ A21) * B21).sum() b12 = ((R.T @ A12.T) * B12.T).sum() AR22 = A22.T @ R BR22 = B22 @ R.T b22a = (AR22 * B22.T[cols]).sum() b22b = (A22 * BR22[cols]).sum() a = (AR22.T * BR22).sum() b = b21 + b12 + b22a + b22b # critical point of ax^2 + bx + c is at x = -d/(2*e) # if a * obj_func_scalar > 0, it is a minimum # if minimum is not in [0, 1], only endpoints need to be considered if a * obj_func_scalar > 0 and 0 <= -b / (2 * a) <= 1: alpha = -b / (2 * a) else: alpha = np.argmin([0, (b + a) * obj_func_scalar]) # [1] Algorithm 1 Line 6 - Update P P_i1 = alpha * P + (1 - alpha) * Q if np.linalg.norm(P - P_i1) / np.sqrt(n_unseed) < tol: P = P_i1 break P = P_i1 # [1] Algorithm 1 Line 7 - end main loop # [1] Algorithm 1 Line 8 - project onto the set of permutation matrices _, col = linear_sum_assignment(P, maximize=True) perm = np.concatenate((np.arange(n_seeds), col + n_seeds)) unshuffled_perm = np.zeros(n, dtype=int) unshuffled_perm[perm_A] = perm_B[perm] score = _calc_score(A, B, unshuffled_perm) res = {"col_ind": unshuffled_perm, "fun": score, "nit": n_iter} return OptimizeResult(res)
def fastica(X, n_components=None, algorithm="parallel", whiten=True, fun="logcosh", fun_args=None, max_iter=200, tol=1e-04, w_init=None, random_state=None, return_X_mean=False, compute_sources=True, return_n_iter=False): """Perform Fast Independent Component Analysis. Read more in the :ref:`User Guide <ICA>`. Parameters ---------- X : array-like, shape (n_samples, n_features) Training vector, where n_samples is the number of samples and n_features is the number of features. n_components : int, optional Number of components to extract. If None no dimension reduction is performed. algorithm : {'parallel', 'deflation'}, optional Apply a parallel or deflational FASTICA algorithm. whiten : boolean, optional If True perform an initial whitening of the data. If False, the data is assumed to have already been preprocessed: it should be centered, normed and white. Otherwise you will get incorrect results. In this case the parameter n_components will be ignored. fun : string or function, optional. Default: 'logcosh' The functional form of the G function used in the approximation to neg-entropy. Could be either 'logcosh', 'exp', or 'cube'. You can also provide your own function. It should return a tuple containing the value of the function, and of its derivative, in the point. The derivative should be averaged along its last dimension. Example: def my_g(x): return x ** 3, np.mean(3 * x ** 2, axis=-1) fun_args : dictionary, optional Arguments to send to the functional form. If empty or None and if fun='logcosh', fun_args will take value {'alpha' : 1.0} max_iter : int, optional Maximum number of iterations to perform. tol : float, optional A positive scalar giving the tolerance at which the un-mixing matrix is considered to have converged. w_init : (n_components, n_components) array, optional Initial un-mixing array of dimension (n.comp,n.comp). If None (default) then an array of normal r.v.'s is used. random_state : int, RandomState instance or None, optional (default=None) If int, random_state is the seed used by the random number generator; If RandomState instance, random_state is the random number generator; If None, the random number generator is the RandomState instance used by `np.random`. return_X_mean : bool, optional If True, X_mean is returned too. compute_sources : bool, optional If False, sources are not computed, but only the rotation matrix. This can save memory when working with big data. Defaults to True. return_n_iter : bool, optional Whether or not to return the number of iterations. Returns ------- K : array, shape (n_components, n_features) | None. If whiten is 'True', K is the pre-whitening matrix that projects data onto the first n_components principal components. If whiten is 'False', K is 'None'. W : array, shape (n_components, n_components) Estimated un-mixing matrix. The mixing matrix can be obtained by:: w = np.dot(W, K.T) A = w.T * (w * w.T).I S : array, shape (n_samples, n_components) | None Estimated source matrix X_mean : array, shape (n_features, ) The mean over features. Returned only if return_X_mean is True. n_iter : int If the algorithm is "deflation", n_iter is the maximum number of iterations run across all components. Else they are just the number of iterations taken to converge. This is returned only when return_n_iter is set to `True`. Notes ----- The data matrix X is considered to be a linear combination of non-Gaussian (independent) components i.e. X = AS where columns of S contain the independent components and A is a linear mixing matrix. In short ICA attempts to `un-mix' the data by estimating an un-mixing matrix W where ``S = W K X.`` This implementation was originally made for data of shape [n_features, n_samples]. Now the input is transposed before the algorithm is applied. This makes it slightly faster for Fortran-ordered input. Implemented using FastICA: `A. Hyvarinen and E. Oja, Independent Component Analysis: Algorithms and Applications, Neural Networks, 13(4-5), 2000, pp. 411-430` """ K = None X_mean = None random_state = check_random_state(random_state) fun_args = {} if fun_args is None else fun_args alpha = fun_args.get('alpha', 1.0) if not 1 <= alpha <= 2: raise ValueError('alpha must be in [1,2]') if fun == 'logcosh': g = _logcosh elif fun == 'exp': g = _exp elif fun == 'cube': g = _cube elif callable(fun): def g(x, fun_args): return fun(x, **fun_args) else: exc = ValueError if isinstance(fun, string_types) else TypeError raise exc("Unknown function %r;" " should be one of 'logcosh', 'exp', 'cube' or callable" % fun) X = validate_and_transpose_source(X) n, p = X.shape if not whiten and n_components is not None: n_components = None warnings.warn('Ignoring n_components with whiten=False.') if n_components is None: n_components = min(n, p) if n_components > min(n, p): n_components = min(n, p) warnings.warn('n_components is too large: it will be set to %s' % n_components) if whiten: # Centering the columns (ie the variables) X_mean = X.mean(axis=-1) X -= X_mean[:, numpy.newaxis] # Whitening and preprocessing by PCA u, d, _ = linalg.svd(X, full_matrices=False) del _ K = (u / d).T[:n_components] # see (6.33) p.140 del u, d X1 = numpy.dot(K, X) # see (13.6) p.267 Here X1 is white and data # in X has been projected onto a subspace by PCA X1 *= numpy.sqrt(p) else: X1 = X if w_init is None: w_init = numpy.asarray(random_state.normal(size=(n_components, n_components)), dtype=X1.dtype) else: w_init = numpy.asarray(w_init) if w_init.shape != (n_components, n_components): raise ValueError('w_init has invalid shape -- should be %(shape)s' % {'shape': (n_components, n_components)}) kwargs = {'tol': tol, 'g': g, 'fun_args': fun_args, 'max_iter': max_iter, 'w_init': w_init} if algorithm == 'parallel': W, n_iter = _ica_par(X1, **kwargs) elif algorithm == 'deflation': W, n_iter = _ica_def(X1, **kwargs) else: raise ValueError('Invalid algorithm: must be either `parallel` or' ' `deflation`.') del X1 if whiten: if compute_sources: S = numpy.dot(numpy.dot(W, K), X).T else: S = None if return_X_mean: if return_n_iter: return K, W, S, X_mean, n_iter else: return K, W, S, X_mean else: if return_n_iter: return K, W, S, n_iter else: return K, W, S else: if compute_sources: S = numpy.dot(W, X).T else: S = None if return_X_mean: if return_n_iter: return None, W, S, None, n_iter else: return None, W, S, None else: if return_n_iter: return None, W, S, n_iter else: return None, W, S
def dual_annealing(func, bounds, args=(), maxiter=1000, local_search_options={}, initial_temp=5230., restart_temp_ratio=2.e-5, visit=2.62, accept=-5.0, maxfun=1e7, seed=None, no_local_search=False, callback=None, x0=None): """ Find the global minimum of a function using Dual Annealing. Parameters ---------- func : callable The objective function to be minimized. Must be in the form ``f(x, *args)``, where ``x`` is the argument in the form of a 1-D array and ``args`` is a tuple of any additional fixed parameters needed to completely specify the function. bounds : sequence, shape (n, 2) Bounds for variables. ``(min, max)`` pairs for each element in ``x``, defining bounds for the objective function parameter. args : tuple, optional Any additional fixed parameters needed to completely specify the objective function. maxiter : int, optional The maximum number of global search iterations. Default value is 1000. local_search_options : dict, optional Extra keyword arguments to be passed to the local minimizer (`minimize`). Some important options could be: ``method`` for the minimizer method to use and ``args`` for objective function additional arguments. initial_temp : float, optional The initial temperature, use higher values to facilitates a wider search of the energy landscape, allowing dual_annealing to escape local minima that it is trapped in. Default value is 5230. Range is (0.01, 5.e4]. restart_temp_ratio : float, optional During the annealing process, temperature is decreasing, when it reaches ``initial_temp * restart_temp_ratio``, the reannealing process is triggered. Default value of the ratio is 2e-5. Range is (0, 1). visit : float, optional Parameter for visiting distribution. Default value is 2.62. Higher values give the visiting distribution a heavier tail, this makes the algorithm jump to a more distant region. The value range is (0, 3]. accept : float, optional Parameter for acceptance distribution. It is used to control the probability of acceptance. The lower the acceptance parameter, the smaller the probability of acceptance. Default value is -5.0 with a range (-1e4, -5]. maxfun : int, optional Soft limit for the number of objective function calls. If the algorithm is in the middle of a local search, this number will be exceeded, the algorithm will stop just after the local search is done. Default value is 1e7. seed : {int, `~numpy.random.RandomState`, `~numpy.random.Generator`}, optional If `seed` is not specified the `~numpy.random.RandomState` singleton is used. If `seed` is an int, a new ``RandomState`` instance is used, seeded with `seed`. If `seed` is already a ``RandomState`` or ``Generator`` instance, then that instance is used. Specify `seed` for repeatable minimizations. The random numbers generated with this seed only affect the visiting distribution function and new coordinates generation. no_local_search : bool, optional If `no_local_search` is set to True, a traditional Generalized Simulated Annealing will be performed with no local search strategy applied. callback : callable, optional A callback function with signature ``callback(x, f, context)``, which will be called for all minima found. ``x`` and ``f`` are the coordinates and function value of the latest minimum found, and ``context`` has value in [0, 1, 2], with the following meaning: - 0: minimum detected in the annealing process. - 1: detection occurred in the local search process. - 2: detection done in the dual annealing process. If the callback implementation returns True, the algorithm will stop. x0 : ndarray, shape(n,), optional Coordinates of a single N-D starting point. Returns ------- res : OptimizeResult The optimization result represented as a `OptimizeResult` object. Important attributes are: ``x`` the solution array, ``fun`` the value of the function at the solution, and ``message`` which describes the cause of the termination. See `OptimizeResult` for a description of other attributes. Notes ----- This function implements the Dual Annealing optimization. This stochastic approach derived from [3]_ combines the generalization of CSA (Classical Simulated Annealing) and FSA (Fast Simulated Annealing) [1]_ [2]_ coupled to a strategy for applying a local search on accepted locations [4]_. An alternative implementation of this same algorithm is described in [5]_ and benchmarks are presented in [6]_. This approach introduces an advanced method to refine the solution found by the generalized annealing process. This algorithm uses a distorted Cauchy-Lorentz visiting distribution, with its shape controlled by the parameter :math:`q_{v}` .. math:: g_{q_{v}}(\\Delta x(t)) \\propto \\frac{ \\ \\left[T_{q_{v}}(t) \\right]^{-\\frac{D}{3-q_{v}}}}{ \\ \\left[{1+(q_{v}-1)\\frac{(\\Delta x(t))^{2}} { \\ \\left[T_{q_{v}}(t)\\right]^{\\frac{2}{3-q_{v}}}}}\\right]^{ \\ \\frac{1}{q_{v}-1}+\\frac{D-1}{2}}} Where :math:`t` is the artificial time. This visiting distribution is used to generate a trial jump distance :math:`\\Delta x(t)` of variable :math:`x(t)` under artificial temperature :math:`T_{q_{v}}(t)`. From the starting point, after calling the visiting distribution function, the acceptance probability is computed as follows: .. math:: p_{q_{a}} = \\min{\\{1,\\left[1-(1-q_{a}) \\beta \\Delta E \\right]^{ \\ \\frac{1}{1-q_{a}}}\\}} Where :math:`q_{a}` is a acceptance parameter. For :math:`q_{a}<1`, zero acceptance probability is assigned to the cases where .. math:: [1-(1-q_{a}) \\beta \\Delta E] < 0 The artificial temperature :math:`T_{q_{v}}(t)` is decreased according to .. math:: T_{q_{v}}(t) = T_{q_{v}}(1) \\frac{2^{q_{v}-1}-1}{\\left( \\ 1 + t\\right)^{q_{v}-1}-1} Where :math:`q_{v}` is the visiting parameter. .. versionadded:: 1.2.0 References ---------- .. [1] Tsallis C. Possible generalization of Boltzmann-Gibbs statistics. Journal of Statistical Physics, 52, 479-487 (1998). .. [2] Tsallis C, Stariolo DA. Generalized Simulated Annealing. Physica A, 233, 395-406 (1996). .. [3] Xiang Y, Sun DY, Fan W, Gong XG. Generalized Simulated Annealing Algorithm and Its Application to the Thomson Model. Physics Letters A, 233, 216-220 (1997). .. [4] Xiang Y, Gong XG. Efficiency of Generalized Simulated Annealing. Physical Review E, 62, 4473 (2000). .. [5] Xiang Y, Gubian S, Suomela B, Hoeng J. Generalized Simulated Annealing for Efficient Global Optimization: the GenSA Package for R. The R Journal, Volume 5/1 (2013). .. [6] Mullen, K. Continuous Global Optimization in R. Journal of Statistical Software, 60(6), 1 - 45, (2014). DOI:10.18637/jss.v060.i06 Examples -------- The following example is a 10-D problem, with many local minima. The function involved is called Rastrigin (https://en.wikipedia.org/wiki/Rastrigin_function) >>> from scipy.optimize import dual_annealing >>> func = lambda x: np.sum(x*x - 10*np.cos(2*np.pi*x)) + 10*np.size(x) >>> lw = [-5.12] * 10 >>> up = [5.12] * 10 >>> ret = dual_annealing(func, bounds=list(zip(lw, up)), seed=1234) >>> ret.x array([-4.26437714e-09, -3.91699361e-09, -1.86149218e-09, -3.97165720e-09, -6.29151648e-09, -6.53145322e-09, -3.93616815e-09, -6.55623025e-09, -6.05775280e-09, -5.00668935e-09]) # may vary >>> ret.fun 0.000000 """ # noqa: E501 if x0 is not None and not len(x0) == len(bounds): raise ValueError('Bounds size does not match x0') lu = list(zip(*bounds)) lower = np.array(lu[0]) upper = np.array(lu[1]) # Check that restart temperature ratio is correct if restart_temp_ratio <= 0. or restart_temp_ratio >= 1.: raise ValueError('Restart temperature ratio has to be in range (0, 1)') # Checking bounds are valid if (np.any(np.isinf(lower)) or np.any(np.isinf(upper)) or np.any( np.isnan(lower)) or np.any(np.isnan(upper))): raise ValueError('Some bounds values are inf values or nan values') # Checking that bounds are consistent if not np.all(lower < upper): raise ValueError('Bounds are not consistent min < max') # Checking that bounds are the same length if not len(lower) == len(upper): raise ValueError('Bounds do not have the same dimensions') # Wrapper for the objective function func_wrapper = ObjectiveFunWrapper(func, maxfun, *args) # Wrapper fot the minimizer minimizer_wrapper = LocalSearchWrapper( bounds, func_wrapper, **local_search_options) # Initialization of RandomState for reproducible runs if seed provided rand_state = check_random_state(seed) # Initialization of the energy state energy_state = EnergyState(lower, upper, callback) energy_state.reset(func_wrapper, rand_state, x0) # Minimum value of annealing temperature reached to perform # re-annealing temperature_restart = initial_temp * restart_temp_ratio # VisitingDistribution instance visit_dist = VisitingDistribution(lower, upper, visit, rand_state) # Strategy chain instance strategy_chain = StrategyChain(accept, visit_dist, func_wrapper, minimizer_wrapper, rand_state, energy_state) need_to_stop = False iteration = 0 message = [] # OptimizeResult object to be returned optimize_res = OptimizeResult() optimize_res.success = True optimize_res.status = 0 t1 = np.exp((visit - 1) * np.log(2.0)) - 1.0 # Run the search loop while(not need_to_stop): for i in range(maxiter): # Compute temperature for this step s = float(i) + 2.0 t2 = np.exp((visit - 1) * np.log(s)) - 1.0 temperature = initial_temp * t1 / t2 if iteration >= maxiter: message.append("Maximum number of iteration reached") need_to_stop = True break # Need a re-annealing process? if temperature < temperature_restart: energy_state.reset(func_wrapper, rand_state) break # starting strategy chain val = strategy_chain.run(i, temperature) if val is not None: message.append(val) need_to_stop = True optimize_res.success = False break # Possible local search at the end of the strategy chain if not no_local_search: val = strategy_chain.local_search() if val is not None: message.append(val) need_to_stop = True optimize_res.success = False break iteration += 1 # Setting the OptimizeResult values optimize_res.x = energy_state.xbest optimize_res.fun = energy_state.ebest optimize_res.nit = iteration optimize_res.nfev = func_wrapper.nfev optimize_res.njev = func_wrapper.ngev optimize_res.nhev = func_wrapper.nhev optimize_res.message = message return optimize_res
def rvs(self, *args, **kwds): """ Random variates of given type. Parameters ---------- arg1, arg2, arg3,... : array_like The shape parameter(s) for the distribution (see docstring of the instance object for more information). loc : array_like, optional Location parameter (default=0). scale : array_like, optional Scale parameter (default=1). size : int or tuple of ints, optional Defining number of random variates (default is 1). random_state : None or int or ``np.random.RandomState`` instance, optional If int or RandomState, use it for drawing the random variates. If None, rely on ``self.random_state``. Default is None. Returns ------- rvs : ndarray or scalar Random variates of given `size`. """ discrete = kwds.pop('discrete', None) rndm = kwds.pop('random_state', None) args, loc, scale, size = self._parse_args_rvs(*args, **kwds) cond = logical_and(self._argcheck(*args), (scale >= 0)) if not np.all(cond): raise ValueError("Domain error in arguments.") if np.all(scale == 0): return loc*ones(size, 'd') # extra gymnastics needed for a custom random_state if rndm is not None: random_state_saved = self._random_state self._random_state = check_random_state(rndm) # `size` should just be an argument to _rvs(), but for, um, # historical reasons, it is made an attribute that is read # by _rvs(). self._size = size vals = self._rvs(*args) #print('scale is:',scale,' loc is:',loc) # Scale is second parameter, location is first parameter # Logic is uniformly sample the values, then Scale them to the #logarithic values of the scale and loc parameter, and then return # the exponent from that value vals = vals * scale + loc shape = vals.shape if shape is np.array(None).shape: #print('vals shape is:',vals.shape) shape = 1 else: shape = shape[0] if shape>1: array_10 = np.full((shape), 10) vals = np.power(array_10,vals) else: vals = 10**vals #vals = np.exp(vals) # do not forget to restore the _random_state if rndm is not None: self._random_state = random_state_saved # Cast to int if discrete if discrete: if size == (): vals = int(vals) else: vals = vals.astype(int) return vals
def rvs_ratio_uniforms(pdf, umax, vmin, vmax, size=1, c=0, random_state=None): """ Generate random samples from a probability density function using the ratio-of-uniforms method. Parameters ---------- pdf : callable A function with signature `pdf(x)` that is proportional to the probability density function of the distribution. umax : float The upper bound of the bounding rectangle in the u-direction. vmin : float The lower bound of the bounding rectangle in the v-direction. vmax : float The upper bound of the bounding rectangle in the v-direction. size : int or tuple of ints, optional Defining number of random variates (default is 1). c : float, optional. Shift parameter of ratio-of-uniforms method, see Notes. Default is 0. random_state : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional If `seed` is None (or `np.random`), the `numpy.random.RandomState` singleton is used. If `seed` is an int, a new ``RandomState`` instance is used, seeded with `seed`. If `seed` is already a ``Generator`` or ``RandomState`` instance then that instance is used. Returns ------- rvs : ndarray The random variates distributed according to the probability distribution defined by the pdf. Notes ----- Given a univariate probability density function `pdf` and a constant `c`, define the set ``A = {(u, v) : 0 < u <= sqrt(pdf(v/u + c))}``. If `(U, V)` is a random vector uniformly distributed over `A`, then `V/U + c` follows a distribution according to `pdf`. The above result (see [1]_, [2]_) can be used to sample random variables using only the pdf, i.e. no inversion of the cdf is required. Typical choices of `c` are zero or the mode of `pdf`. The set `A` is a subset of the rectangle ``R = [0, umax] x [vmin, vmax]`` where - ``umax = sup sqrt(pdf(x))`` - ``vmin = inf (x - c) sqrt(pdf(x))`` - ``vmax = sup (x - c) sqrt(pdf(x))`` In particular, these values are finite if `pdf` is bounded and ``x**2 * pdf(x)`` is bounded (i.e. subquadratic tails). One can generate `(U, V)` uniformly on `R` and return `V/U + c` if `(U, V)` are also in `A` which can be directly verified. The algorithm is not changed if one replaces `pdf` by k * `pdf` for any constant k > 0. Thus, it is often convenient to work with a function that is proportional to the probability density function by dropping unneccessary normalization factors. Intuitively, the method works well if `A` fills up most of the enclosing rectangle such that the probability is high that `(U, V)` lies in `A` whenever it lies in `R` as the number of required iterations becomes too large otherwise. To be more precise, note that the expected number of iterations to draw `(U, V)` uniformly distributed on `R` such that `(U, V)` is also in `A` is given by the ratio ``area(R) / area(A) = 2 * umax * (vmax - vmin) / area(pdf)``, where `area(pdf)` is the integral of `pdf` (which is equal to one if the probability density function is used but can take on other values if a function proportional to the density is used). The equality holds since the area of `A` is equal to 0.5 * area(pdf) (Theorem 7.1 in [1]_). If the sampling fails to generate a single random variate after 50000 iterations (i.e. not a single draw is in `A`), an exception is raised. If the bounding rectangle is not correctly specified (i.e. if it does not contain `A`), the algorithm samples from a distribution different from the one given by `pdf`. It is therefore recommended to perform a test such as `~scipy.stats.kstest` as a check. References ---------- .. [1] L. Devroye, "Non-Uniform Random Variate Generation", Springer-Verlag, 1986. .. [2] W. Hoermann and J. Leydold, "Generating generalized inverse Gaussian random variates", Statistics and Computing, 24(4), p. 547--557, 2014. .. [3] A.J. Kinderman and J.F. Monahan, "Computer Generation of Random Variables Using the Ratio of Uniform Deviates", ACM Transactions on Mathematical Software, 3(3), p. 257--260, 1977. Examples -------- >>> from scipy import stats >>> rng = np.random.default_rng() Simulate normally distributed random variables. It is easy to compute the bounding rectangle explicitly in that case. For simplicity, we drop the normalization factor of the density. >>> f = lambda x: np.exp(-x**2 / 2) >>> v_bound = np.sqrt(f(np.sqrt(2))) * np.sqrt(2) >>> umax, vmin, vmax = np.sqrt(f(0)), -v_bound, v_bound >>> rvs = stats.rvs_ratio_uniforms(f, umax, vmin, vmax, size=2500, ... random_state=rng) The K-S test confirms that the random variates are indeed normally distributed (normality is not rejected at 5% significance level): >>> stats.kstest(rvs, 'norm')[1] 0.250634764150542 The exponential distribution provides another example where the bounding rectangle can be determined explicitly. >>> rvs = stats.rvs_ratio_uniforms(lambda x: np.exp(-x), umax=1, ... vmin=0, vmax=2*np.exp(-1), size=1000, ... random_state=rng) >>> stats.kstest(rvs, 'expon')[1] 0.21121052054580314 """ if vmin >= vmax: raise ValueError("vmin must be smaller than vmax.") if umax <= 0: raise ValueError("umax must be positive.") size1d = tuple(np.atleast_1d(size)) N = np.prod(size1d) # number of rvs needed, reshape upon return # start sampling using ratio of uniforms method rng = check_random_state(random_state) x = np.zeros(N) simulated, i = 0, 1 # loop until N rvs have been generated: expected runtime is finite. # to avoid infinite loop, raise exception if not a single rv has been # generated after 50000 tries. even if the expected numer of iterations # is 1000, the probability of this event is (1-1/1000)**50000 # which is of order 10e-22 while simulated < N: k = N - simulated # simulate uniform rvs on [0, umax] and [vmin, vmax] u1 = umax * rng.uniform(size=k) v1 = rng.uniform(vmin, vmax, size=k) # apply rejection method rvs = v1 / u1 + c accept = (u1**2 <= pdf(rvs)) num_accept = np.sum(accept) if num_accept > 0: x[simulated:(simulated + num_accept)] = rvs[accept] simulated += num_accept if (simulated == 0) and (i*N >= 50000): msg = ("Not a single random variate could be generated in {} " "attempts. The ratio of uniforms method does not appear " "to work for the provided parameters. Please check the " "pdf and the bounds.".format(i*N)) raise RuntimeError(msg) i += 1 return np.reshape(x, size1d)
def __init__(self, func, bounds, config, args=(), strategy='best1bin', maxiter=1000, popsize=15, tol=0.01, mutation=(0.5, 1), recombination=0.7, seed=None, maxfun=np.inf, callback=None, disp=False, polish=True, init='latinhypercube', atol=0, updating='immediate', workers=1): self.config = config if strategy in self._binomial: self.mutation_func = getattr(self, self._binomial[strategy]) elif strategy in self._exponential: self.mutation_func = getattr(self, self._exponential[strategy]) else: raise ValueError("Please select a valid mutation strategy") self.strategy = strategy self.callback = callback self.polish = polish # set the updating / parallelisation options if updating in ['immediate', 'deferred']: self._updating = updating # want to use parallelisation, but updating is immediate if workers != 1 and updating == 'immediate': warnings.warn("differential_evolution: the 'workers' keyword has" " overridden updating='immediate' to" " updating='deferred'", UserWarning) self._updating = 'deferred' # an object with a map method. self._mapwrapper = MapWrapper(workers) # relative and absolute tolerances for convergence self.tol, self.atol = tol, atol # Mutation constant should be in [0, 2). If specified as a sequence # then dithering is performed. self.scale = mutation if (not np.all(np.isfinite(mutation)) or np.any(np.array(mutation) >= 2) or np.any(np.array(mutation) < 0)): raise ValueError('The mutation constant must be a float in ' 'U[0, 2), or specified as a tuple(min, max)' ' where min < max and min, max are in U[0, 2).') self.dither = None if hasattr(mutation, '__iter__') and len(mutation) > 1: self.dither = [mutation[0], mutation[1]] self.dither.sort() self.cross_over_probability = recombination # we create a wrapped function to allow the use of map (and Pool.map # in the future) self.func = _FunctionWrapper(func, args) self.args = args # convert tuple of lower and upper bounds to limits # [(low_0, high_0), ..., (low_n, high_n] # -> [[low_0, ..., low_n], [high_0, ..., high_n]] if isinstance(bounds, Bounds): self.limits = np.array(new_bounds_to_old(bounds.lb, bounds.ub, len(bounds.lb)), dtype=float).T else: self.limits = np.array(bounds, dtype='float').T if (np.size(self.limits, 0) != 2 or not np.all(np.isfinite(self.limits))): raise ValueError('bounds should be a sequence containing ' 'real valued (min, max) pairs for each value' ' in x') if maxiter is None: # the default used to be None maxiter = 1000 self.maxiter = maxiter if maxfun is None: # the default used to be None maxfun = np.inf self.maxfun = maxfun # population is scaled to between [0, 1]. # We have to scale between parameter <-> population # save these arguments for _scale_parameter and # _unscale_parameter. This is an optimization self.__scale_arg1 = 0.5 * (self.limits[0] + self.limits[1]) self.__scale_arg2 = np.fabs(self.limits[0] - self.limits[1]) self.parameter_count = np.size(self.limits, 1) self.random_number_generator = check_random_state(seed) # default population initialization is a latin hypercube design, but # there are other population initializations possible. # the minimum is 5 because 'best2bin' requires a population that's at # least 5 long self.num_population_members = max(5, popsize * self.parameter_count) self.population_shape = (self.num_population_members, self.parameter_count) self._nfev = 0 if isinstance(init, string_types): if init == 'latinhypercube': self.init_population_lhs() elif init == 'random': self.init_population_random() else: raise ValueError(self.__init_error_msg) else: self.init_population_array(init) self.disp = disp self.fx_history = [] self.x_best_history = np.zeros((self.maxiter, self.parameter_count))
def sample(self, steps, nthin=1, random_state=None, f=None, callback=None, verbose=True, pool=0): """ Performs sampling from the objective. Parameters ---------- steps : int Collect `steps` samples into the chain. The sampler will run a total of `steps * nthin` moves. nthin : int, optional Each chain sample is separated by `nthin` iterations. random_state : int or `np.random.RandomState`, optional If `random_state` is an int, a new `np.random.RandomState` instance is used, seeded with `random_state`. If `random_state` is already a `np.random.RandomState` instance, then that `np.random.RandomState` instance is used. Specify `random_state` for repeatable sampling f : file-like or str File to incrementally save chain progress to. Each row in the file is a flattened array of size `(nwalkers, ndim)` or `(ntemps, nwalkers, ndim)`. There are `steps` rows in the file. callback : callable callback function to be called at each iteration step verbose : bool, optional Gives updates on the sampling progress pool : int or map-like object, optional If `pool` is an `int` then it specifies the number of threads to use for parallelization. If `pool == 0`, then all CPU's are used. If pool is an object with a map method that follows the same calling sequence as the built-in map function, then this pool is used for parallelisation. Notes ----- Please see :class:`emcee.EnsembleSampler` for its detailed behaviour. >>> # we'll burn the first 500 steps >>> fitter.sample(500) >>> # after you've run those, then discard them by resetting the >>> # sampler. >>> fitter.sampler.reset() >>> # Now collect 40 steps, each step separated by 50 sampler >>> # generations. >>> fitter.sample(40, nthin=50) One can also burn and thin in `Curvefitter.process_chain`. """ self._check_vars_unchanged() if self._state is None: self.initialise() self.__pt_iterations = 0 if isinstance(self.sampler, PTSampler): steps *= nthin # for saving progress to file def _callback_wrapper(state, h=None): if callback is not None: callback(state.coords, state.log_prob) if h is not None: # if you're parallel tempering, then you only # want to save every nthin if isinstance(self.sampler, PTSampler): self.__pt_iterations += 1 if self.__pt_iterations % nthin: return None h.write(' '.join(map(str, state.coords.ravel()))) h.write('\n') # set the random state of the sampler # normally one could give this as an argument to the sample method # but PTSampler didn't historically accept that... if random_state is not None: rstate0 = check_random_state(random_state).get_state() self._state.random_state = rstate0 if isinstance(self.sampler, PTSampler): self.sampler._random = rstate0 # remove chains from each of the parameters because they slow down # pickling but only if they are parameter objects. flat_params = f_unique(flatten(self.objective.parameters)) flat_params = [param for param in flat_params if is_parameter(param)] # zero out all the old parameter stderrs for param in flat_params: param.stderr = None param.chain = None # make sure the checkpoint file exists if f is not None: with possibly_open_file(f, 'w') as h: # write the shape of each step of the chain h.write('# ') shape = self._state.coords.shape h.write(', '.join(map(str, shape))) h.write('\n') # using context manager means we kill off zombie pool objects # but does mean that the pool has to be specified each time. with possibly_create_pool(pool) as g, possibly_open_file(f, 'a') as h: # if you're not creating more than 1 thread, then don't bother with # a pool. if pool == 1: self.sampler.pool = None else: self.sampler.pool = g # these kwargs are provided to the sampler.sample method kwargs = {'iterations': steps, 'thin': nthin} # new emcee arguments sampler_args = getargspec(self.sampler.sample).args if 'progress' in sampler_args and verbose: kwargs['progress'] = True verbose = False if 'thin_by' in sampler_args: kwargs['thin_by'] = nthin kwargs.pop('thin', 0) # ptemcee returns coords, lnprob # emcee returns a State object if isinstance(self.sampler, PTSampler): for result in self.sampler.sample(self._state.coords, **kwargs): self._state = State(result[0], log_prob=result[1] + result[2], random_state=self.sampler._random) _callback_wrapper(self._state, h=h) else: for state in self.sampler.sample(self._state, **kwargs): self._state = state _callback_wrapper(state, h=h) self.sampler.pool = None # finish off the progress bar if verbose: sys.stdout.write("\n") # sets parameter value and stderr return process_chain(self.objective, self.chain)
def _perm_test( test, ksim, sim, n=100, p=1, noise=False, reps=1000, workers=1, random_state=None, angle=90, trans=0, ): r""" Helper function that calculates the statistical. Parameters ---------- test : callable() The independence test class requested. sim : callable() The simulation used to generate the input data. reps : int, optional (default: 1000) The number of replications used to estimate the null distribution when using the permutation test used to calculate the p-value. workers : int, optional (default: -1) The number of cores to parallelize the p-value computation over. Supply -1 to use all cores available to the Process. Returns ------- null_dist : list The approximated null distribution. """ # set seeds random_state = check_random_state(random_state) rngs = [ np.random.RandomState( random_state.randint(1 << 32, size=4, dtype=np.uint32)) for _ in range(reps) ] # use all cores to create function that parallelizes over number of reps mapwrapper = MapWrapper(workers) parallelp = _ParallelP( test=test, ksim=ksim, sim=sim, n=n, p=p, noise=noise, rngs=rngs, angle=angle, trans=trans, ) alt_dist, null_dist = map(list, zip(*list(mapwrapper(parallelp, range(reps))))) alt_dist = np.array(alt_dist) null_dist = np.array(null_dist) return alt_dist, null_dist
sim : callable() The simulation used to generate the input data. reps : int, optional (default: 1000) The number of replications used to estimate the null distribution when using the permutation test used to calculate the p-value. workers : int, optional (default: -1) The number of cores to parallelize the p-value computation over. Supply -1 to use all cores available to the Process. Returns ------- null_dist : list The approximated null distribution. """ # set seeds random_state = check_random_state(random_state) rngs = [ np.random.RandomState( random_state.randint(1 << 32, size=4, dtype=np.uint32)) for _ in range(reps) ] # use all cores to create function that parallelizes over number of reps mapwrapper = MapWrapper(workers) parallelp = _ParallelP_4samp_2way( test, n, epsilon1, epsilon2, effect_mask, weight,
def _quadratic_assignment_2opt(A, B, maximize=False, rng=None, partial_match=None, partial_guess=None, **unknown_options): r""" Solve the quadratic assignment problem (approximately). This function solves the Quadratic Assignment Problem (QAP) and the Graph Matching Problem (GMP) using the 2-opt algorithm [1]_. Quadratic assignment solves problems of the following form: .. math:: \min_P & \ {\ \text{trace}(A^T P B P^T)}\\ \mbox{s.t. } & {P \ \epsilon \ \mathcal{P}}\\ where :math:`\mathcal{P}` is the set of all permutation matrices, and :math:`A` and :math:`B` are square matrices. Graph matching tries to *maximize* the same objective function. This algorithm can be thought of as finding the alignment of the nodes of two graphs that minimizes the number of induced edge disagreements, or, in the case of weighted graphs, the sum of squared edge weight differences. Note that the quadratic assignment problem is NP-hard. The results given here are approximations and are not guaranteed to be optimal. Parameters ---------- A : 2-D array, square The square matrix :math:`A` in the objective function above. B : 2-D array, square The square matrix :math:`B` in the objective function above. method : str in {'faq', '2opt'} (default: 'faq') The algorithm used to solve the problem. This is the method-specific documentation for '2opt'. :ref:`'faq' <optimize.qap-faq>` is also available. Options ------- maximize : bool (default: False) Maximizes the objective function if ``True``. rng : int, `RandomState`, `Generator` or None, optional (default: None) Accepts an integer as a seed for the random generator or a ``RandomState`` or ``Generator`` object. If None (default), uses global `numpy.random` random state. partial_match : 2-D array of integers, optional (default: None) Fixes part of the matching. Also known as a "seed" [2]_. Each row of `partial_match` specifies a pair of matched nodes: node ``partial_match[i, 0]`` of `A` is matched to node ``partial_match[i, 1]`` of `B`. The array has shape ``(m, 2)``, where ``m`` is not greater than the number of nodes, :math:`n`. partial_guess : 2-D array of integers, optional (default: None) A guess for the matching between the two matrices. Unlike `partial_match`, `partial_guess` does not fix the indices; they are still free to be optimized. Each row of `partial_guess` specifies a pair of matched nodes: node ``partial_guess[i, 0]`` of `A` is matched to node ``partial_guess[i, 1]`` of `B`. The array has shape ``(m, 2)``, where ``m`` is not greater than the number of nodes, :math:`n`. Returns ------- res : OptimizeResult `OptimizeResult` containing the following fields. col_ind : 1-D array Column indices corresponding to the best permutation found of the nodes of `B`. fun : float The objective value of the solution. nit : int The number of iterations performed during optimization. Notes ----- This is a greedy algorithm that works similarly to bubble sort: beginning with an initial permutation, it iteratively swaps pairs of indices to improve the objective function until no such improvements are possible. References ---------- .. [1] "2-opt," Wikipedia. https://en.wikipedia.org/wiki/2-opt .. [2] D. Fishkind, S. Adali, H. Patsolic, L. Meng, D. Singh, V. Lyzinski, C. Priebe, "Seeded graph matching", Pattern Recognit. 87 (2019): 203-215, https://doi.org/10.1016/j.patcog.2018.09.014 """ _check_unknown_options(unknown_options) rng = check_random_state(rng) A, B, partial_match = _common_input_validation(A, B, partial_match) N = len(A) # check trivial cases if N == 0 or partial_match.shape[0] == N: score = _calc_score(A, B, partial_match[:, 1]) res = {"col_ind": partial_match[:, 1], "fun": score, "nit": 0} return OptimizeResult(res) if partial_guess is None: partial_guess = np.array([[], []]).T partial_guess = np.atleast_2d(partial_guess).astype(int) msg = None if partial_guess.shape[0] > A.shape[0]: msg = ("`partial_guess` can have only as " "many entries as there are nodes") elif partial_guess.shape[1] != 2: msg = "`partial_guess` must have two columns" elif partial_guess.ndim != 2: msg = "`partial_guess` must have exactly two dimensions" elif (partial_guess < 0).any(): msg = "`partial_guess` must contain only positive indices" elif (partial_guess >= len(A)).any(): msg = "`partial_guess` entries must be less than number of nodes" elif (not len(set(partial_guess[:, 0])) == len(partial_guess[:, 0]) or not len(set(partial_guess[:, 1])) == len(partial_guess[:, 1])): msg = "`partial_guess` column entries must be unique" if msg is not None: raise ValueError(msg) fixed_rows = None if partial_match.size or partial_guess.size: # use partial_match and partial_guess for initial permutation, # but randomly permute the rest. guess_rows = np.zeros(N, dtype=bool) guess_cols = np.zeros(N, dtype=bool) fixed_rows = np.zeros(N, dtype=bool) fixed_cols = np.zeros(N, dtype=bool) perm = np.zeros(N, dtype=int) rg, cg = partial_guess.T guess_rows[rg] = True guess_cols[cg] = True perm[guess_rows] = cg # match overrides guess rf, cf = partial_match.T fixed_rows[rf] = True fixed_cols[cf] = True perm[fixed_rows] = cf random_rows = ~fixed_rows & ~guess_rows random_cols = ~fixed_cols & ~guess_cols perm[random_rows] = rng.permutation(np.arange(N)[random_cols]) else: perm = rng.permutation(np.arange(N)) best_score = _calc_score(A, B, perm) i_free = np.arange(N) if fixed_rows is not None: i_free = i_free[~fixed_rows] better = operator.gt if maximize else operator.lt n_iter = 0 done = False while not done: # equivalent to nested for loops i in range(N), j in range(i, N) for i, j in itertools.combinations_with_replacement(i_free, 2): n_iter += 1 perm[i], perm[j] = perm[j], perm[i] score = _calc_score(A, B, perm) if better(score, best_score): best_score = score break # faster to swap back than to create a new list every time perm[i], perm[j] = perm[j], perm[i] else: # no swaps made done = True res = {"col_ind": perm, "fun": best_score, "nit": n_iter} return OptimizeResult(res)
def _get_random_state(self, random_state): if random_state is not None: return check_random_state(random_state) else: return self._random_state
def _quadratic_assignment_faq(A, B, maximize=False, partial_match=None, rng=None, P0="barycenter", shuffle_input=False, maxiter=30, tol=0.03, **unknown_options): r""" Solve the quadratic assignment problem (approximately). This function solves the Quadratic Assignment Problem (QAP) and the Graph Matching Problem (GMP) using the Fast Approximate QAP Algorithm (FAQ) [1]_. Quadratic assignment solves problems of the following form: .. math:: \min_P & \ {\ \text{trace}(A^T P B P^T)}\\ \mbox{s.t. } & {P \ \epsilon \ \mathcal{P}}\\ where :math:`\mathcal{P}` is the set of all permutation matrices, and :math:`A` and :math:`B` are square matrices. Graph matching tries to *maximize* the same objective function. This algorithm can be thought of as finding the alignment of the nodes of two graphs that minimizes the number of induced edge disagreements, or, in the case of weighted graphs, the sum of squared edge weight differences. Note that the quadratic assignment problem is NP-hard, is not known to be solvable in polynomial time, and is computationally intractable. Therefore, the results given are approximations, not guaranteed to be exact solutions. Parameters ---------- A : 2d-array, square The square matrix :math:`A` in the objective function above. B : 2d-array, square The square matrix :math:`B` in the objective function above. method : str in {'faq', '2opt'} (default: 'faq') The algorithm used to solve the problem. This is the method-specific documentation for 'faq'. :ref:`'2opt' <optimize.qap-2opt>` is also available. Options ------- maximize : bool (default = False) Setting `maximize` to ``True`` solves the Graph Matching Problem (GMP) rather than the Quadratic Assingnment Problem (QAP). This is accomplished through trivial negation of the objective function. rng : {None, int, `~np.random.RandomState`, `~np.random.Generator`} This parameter defines the object to use for drawing random variates. If `rng` is ``None`` the `~np.random.RandomState` singleton is used. If `rng` is an int, a new ``RandomState`` instance is used, seeded with `rng`. If `rng` is already a ``RandomState`` or ``Generator`` instance, then that object is used. Default is None. partial_match : 2d-array of integers, optional, (default = None) Allows the user to fix part of the matching between the two matrices. In the literature, a partial match is also known as a "seed". Each row of `partial_match` specifies the indices of a pair of corresponding nodes, that is, node ``partial_match[i, 0]`` of `A` is matched to node ``partial_match[i, 1]`` of `B`. Accordingly, ``partial_match`` is an array of size ``(m , 2)``, where ``m`` is not greater than the number of nodes, :math:`n`. P0 : 2d-array, "barycenter", or "randomized" (default = "barycenter") The initial (guess) permutation matrix or search "position" `P0`. `P0` need not be a proper permutation matrix; however, it must be :math:`m' x m'`, where :math:`m' = n - m`, and it must be doubly stochastic: each of its rows and columns must sum to 1. If unspecified or ``"barycenter"``, the non-informative "flat doubly stochastic matrix" :math:`J = 1*1^T/m'`, where :math:`1` is a :math:`m' \times 1` array of ones, is used. This is the "barycenter" of the search space of doubly-stochastic matrices. If ``"randomized"``, the algorithm will start from the randomized initial search position :math:`P_0 = (J + K)/2`, where :math:`J` is the "barycenter" and :math:`K` is a random doubly stochastic matrix. shuffle_input : bool (default = False) To avoid artificially high or low matching due to inherent sorting of input matrices, gives users the option to shuffle the nodes. Results are then unshuffled so that the returned results correspond with the node order of inputs. Shuffling may cause the algorithm to be non-deterministic, unless a random seed is set or an `rng` option is provided. maxiter : int, positive (default = 30) Integer specifying the max number of Franke-Wolfe iterations performed. tol : float (default = 0.03) A threshold for the stopping criterion. Franke-Wolfe iteration terminates when the change in search position between iterations is sufficiently small, that is, when the relative Frobenius norm, :math:`\frac{||P_{i}-P_{i+1}||_F}{\sqrt{len(P_{i})}} \leq tol`, where :math:`i` is the iteration number. Returns ------- res : OptimizeResult A :class:`scipy.optimize.OptimizeResult` containing the following fields. col_ind : 1-D array An array of column indices corresponding with the best permutation of the nodes of `B` found. fun : float The corresponding value of the objective function. nit : int The number of Franke-Wolfe iterations performed. Notes ----- The algorithm may be sensitive to the initial permutation matrix (or search "position") due to the possibility of several local minima within the feasible region. A barycenter initialization is more likely to result in a better solution than a single random initialization. However, ``quadratic_assignment`` calling several times with different random initializations may result in a better optimum at the cost of longer total execution time. Examples -------- As mentioned above, a barycenter initialization often results in a better solution than a single random initialization. >>> np.random.seed(0) >>> n = 15 >>> A = np.random.rand(n, n) >>> B = np.random.rand(n, n) >>> res = quadratic_assignment(A, B) # FAQ is default method >>> print(res.fun) 46.871483385480545 # may vary >>> options = {"P0": "randomized"} # use randomized initialization >>> res = quadratic_assignment(A, B, options=options) >>> print(res.fun) 47.224831071310625 # may vary However, consider running from several randomized initializations and keeping the best result. >>> res = min([quadratic_assignment(A, B, options=options) ... for i in range(30)], key=lambda x: x.fun) >>> print(res.fun) 46.671852533681516 # may vary The '2-opt' method can be used to further refine the results. >>> options = {"partial_guess": np.array([np.arange(n), res.col_ind]).T} >>> res = quadratic_assignment(A, B, method="2opt", options=options) >>> print(res.fun) 46.47160735721583 # may vary References ---------- .. [1] J.T. Vogelstein, J.M. Conroy, V. Lyzinski, L.J. Podrazik, S.G. Kratzer, E.T. Harley, D.E. Fishkind, R.J. Vogelstein, and C.E. Priebe, "Fast approximate quadratic programming for graph matching," PLOS one, vol. 10, no. 4, p. e0121002, 2015, :doi:`10.1371/journal.pone.0121002` .. [2] D. Fishkind, S. Adali, H. Patsolic, L. Meng, D. Singh, V. Lyzinski, C. Priebe, "Seeded graph matching", Pattern Recognit. 87 (2019): 203-215, :doi:`10.1016/j.patcog.2018.09.014` """ _check_unknown_options(unknown_options) maxiter = operator.index(maxiter) # ValueError check A, B, partial_match = _common_input_validation(A, B, partial_match) msg = None if isinstance(P0, str) and P0 not in {'barycenter', 'randomized'}: msg = "Invalid 'P0' parameter string" elif maxiter <= 0: msg = "'maxiter' must be a positive integer" elif tol <= 0: msg = "'tol' must be a positive float" if msg is not None: raise ValueError(msg) rng = check_random_state(rng) n = A.shape[0] # number of vertices in graphs n_seeds = partial_match.shape[0] # number of seeds n_unseed = n - n_seeds # check outlier cases if n == 0 or partial_match.shape[0] == n: score = _calc_score(A, B, partial_match[:, 1]) res = {"col_ind": partial_match[:, 1], "fun": score, "nit": 0} return OptimizeResult(res) obj_func_scalar = 1 if maximize: obj_func_scalar = -1 nonseed_B = np.setdiff1d(range(n), partial_match[:, 1]) if shuffle_input: nonseed_B = rng.permutation(nonseed_B) # shuffle_input to avoid results from inputs that were already matched nonseed_A = np.setdiff1d(range(n), partial_match[:, 0]) perm_A = np.concatenate([partial_match[:, 0], nonseed_A]) perm_B = np.concatenate([partial_match[:, 1], nonseed_B]) # definitions according to Seeded Graph Matching [2]. A11, A12, A21, A22 = _split_matrix(A[perm_A][:, perm_A], n_seeds) B11, B12, B21, B22 = _split_matrix(B[perm_B][:, perm_B], n_seeds) # [1] Algorithm 1 Line 1 - choose initialization if isinstance(P0, str): # initialize J, a doubly stochastic barycenter J = np.ones((n_unseed, n_unseed)) / n_unseed if P0 == 'barycenter': P = J elif P0 == 'randomized': # generate a nxn matrix where each entry is a random number [0, 1] # would use rand, but Generators don't have it # would use random, but old mtrand.RandomStates don't have it K = rng.uniform(size=(n_unseed, n_unseed)) # Sinkhorn balancing K = _doubly_stochastic(K) P = J * 0.5 + K * 0.5 else: P0 = np.atleast_2d(P0) _check_init_input(P0, n_unseed) P = P0 const_sum = A21 @ B21.T + A12.T @ B12 # [1] Algorithm 1 Line 2 - loop while stopping criteria not met for n_iter in range(1, maxiter + 1): # [1] Algorithm 1 Line 3 - compute the gradient of f(P) = -tr(APB^tP^t) grad_fp = (const_sum + A22 @ P @ B22.T + A22.T @ P @ B22) # [1] Algorithm 1 Line 4 - get direction Q by solving Eq. 8 _, cols = linear_sum_assignment(grad_fp, maximize=maximize) Q = np.eye(n_unseed)[cols] # [1] Algorithm 1 Line 5 - compute the step size # Noting that e.g. trace(Ax) = trace(A)*x, expand and re-collect # terms as ax**2 + bx + c. c does not affect location of minimum # and can be ignored. Also, note that trace(A@B) = (A.T*B).sum(); # apply where possible for efficiency. R = P - Q b21 = ((R.T @ A21) * B21).sum() b12 = ((R.T @ A12.T) * B12.T).sum() AR22 = A22.T @ R BR22 = B22 @ R.T b22a = (AR22 * B22.T[cols]).sum() b22b = (A22 * BR22[cols]).sum() a = (AR22.T * BR22).sum() b = b21 + b12 + b22a + b22b # critical point of ax^2 + bx + c is at x = -d/(2*e) # if a * obj_func_scalar > 0, it is a minimum # if minimum is not in [0, 1], only endpoints need to be considered if a * obj_func_scalar > 0 and 0 <= -b / (2 * a) <= 1: alpha = -b / (2 * a) else: alpha = np.argmin([0, (b + a) * obj_func_scalar]) # [1] Algorithm 1 Line 6 - Update P P_i1 = alpha * P + (1 - alpha) * Q if np.linalg.norm(P - P_i1) / np.sqrt(n_unseed) < tol: P = P_i1 break P = P_i1 # [1] Algorithm 1 Line 7 - end main loop # [1] Algorithm 1 Line 8 - project onto the set of permutation matrices _, col = linear_sum_assignment(-P) perm = np.concatenate((np.arange(n_seeds), col + n_seeds)) unshuffled_perm = np.zeros(n, dtype=int) unshuffled_perm[perm_A] = perm_B[perm] score = _calc_score(A, B, unshuffled_perm) res = {"col_ind": unshuffled_perm, "fun": score, "nit": n_iter} return OptimizeResult(res)
def random(m, n, density=0.01, format='coo', dtype=None, random_state=None, data_rvs=None): """Generate a sparse matrix of the given shape and density with randomly distributed values. Parameters ---------- m, n : int shape of the matrix density : real, optional density of the generated matrix: density equal to one means a full matrix, density of 0 means a matrix with no non-zero items. format : str, optional sparse matrix format. dtype : dtype, optional type of the returned matrix values. random_state : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional If `seed` is None (or `np.random`), the `numpy.random.RandomState` singleton is used. If `seed` is an int, a new ``RandomState`` instance is used, seeded with `seed`. If `seed` is already a ``Generator`` or ``RandomState`` instance then that instance is used. This random state will be used for sampling the sparsity structure, but not necessarily for sampling the values of the structurally nonzero entries of the matrix. data_rvs : callable, optional Samples a requested number of random values. This function should take a single argument specifying the length of the ndarray that it will return. The structurally nonzero entries of the sparse random matrix will be taken from the array sampled by this function. By default, uniform [0, 1) random values will be sampled using the same random state as is used for sampling the sparsity structure. Returns ------- res : sparse matrix Notes ----- Only float types are supported for now. Examples -------- >>> from scipy.sparse import random >>> from scipy import stats >>> from numpy.random import default_rng >>> rng = default_rng() >>> rvs = stats.poisson(25, loc=10).rvs >>> S = random(3, 4, density=0.25, random_state=rng, data_rvs=rvs) >>> S.A array([[ 36., 0., 33., 0.], # random [ 0., 0., 0., 0.], [ 0., 0., 36., 0.]]) >>> from scipy.sparse import random >>> from scipy.stats import rv_continuous >>> class CustomDistribution(rv_continuous): ... def _rvs(self, size=None, random_state=None): ... return random_state.standard_normal(size) >>> X = CustomDistribution(seed=rng) >>> Y = X() # get a frozen version of the distribution >>> S = random(3, 4, density=0.25, random_state=rng, data_rvs=Y.rvs) >>> S.A array([[ 0. , 0. , 0. , 0. ], # random [ 0.13569738, 1.9467163 , -0.81205367, 0. ], [ 0. , 0. , 0. , 0. ]]) """ if density < 0 or density > 1: raise ValueError("density expected to be 0 <= density <= 1") dtype = np.dtype(dtype) mn = m * n tp = np.intc if mn > np.iinfo(tp).max: tp = np.int64 if mn > np.iinfo(tp).max: msg = """\ Trying to generate a random sparse matrix such as the product of dimensions is greater than %d - this is not supported on this machine """ raise ValueError(msg % np.iinfo(tp).max) # Number of non zero values k = int(round(density * m * n)) random_state = check_random_state(random_state) if data_rvs is None: if np.issubdtype(dtype, np.integer): def data_rvs(n): return rng_integers(random_state, np.iinfo(dtype).min, np.iinfo(dtype).max, n, dtype=dtype) elif np.issubdtype(dtype, np.complexfloating): def data_rvs(n): return (random_state.uniform(size=n) + random_state.uniform(size=n) * 1j) else: data_rvs = partial(random_state.uniform, 0., 1.) ind = random_state.choice(mn, size=k, replace=False) j = np.floor(ind * 1. / m).astype(tp, copy=False) i = (ind - j * m).astype(tp, copy=False) vals = data_rvs(k).astype(dtype, copy=False) return coo_matrix((vals, (i, j)), shape=(m, n)).asformat(format, copy=False)
def _quadratic_assignment_2opt(A, B, maximize=False, partial_match=None, rng=None, partial_guess=None, **unknown_options): r""" Solve the quadratic assignment problem (approximately). This function solves the Quadratic Assignment Problem (QAP) and the Graph Matching Problem (GMP) using the 2-opt algorithm [3]_. Quadratic assignment solves problems of the following form: .. math:: \min_P & \ {\ \text{trace}(A^T P B P^T)}\\ \mbox{s.t. } & {P \ \epsilon \ \mathcal{P}}\\ where :math:`\mathcal{P}` is the set of all permutation matrices, and :math:`A` and :math:`B` are square matrices. Graph matching tries to *maximize* the same objective function. This algorithm can be thought of as finding the alignment of the nodes of two graphs that minimizes the number of induced edge disagreements, or, in the case of weighted graphs, the sum of squared edge weight differences. Note that the quadratic assignment problem is NP-hard, is not known to be solvable in polynomial time, and is computationally intractable. Therefore, the results given are approximations, not guaranteed to be exact solutions. Parameters ---------- A : 2d-array, square The square matrix :math:`A` in the objective function above. B : 2d-array, square The square matrix :math:`B` in the objective function above. method : str in {'faq', '2opt'} (default: 'faq') The algorithm used to solve the problem. This is the method-specific documentation for '2opt'. :ref:`'faq' <optimize.qap-faq>` is also available. Options ------- maximize : bool (default = False) Setting `maximize` to ``True`` solves the Graph Matching Problem (GMP) rather than the Quadratic Assingnment Problem (QAP). rng : {None, int, `~np.random.RandomState`, `~np.random.Generator`} This parameter defines the object to use for drawing random variates. If `rng` is ``None`` the `~np.random.RandomState` singleton is used. If `rng` is an int, a new ``RandomState`` instance is used, seeded with `rng`. If `rng` is already a ``RandomState`` or ``Generator`` instance, then that object is used. Default is None. partial_match : 2d-array of integers, optional, (default = None) Allows the user to fix part of the matching between the two matrices. In the literature, a partial match is also known as a "seed". Each row of `partial_match` specifies the indices of a pair of corresponding nodes, that is, node ``partial_match[i, 0]`` of `A` is matched to node ``partial_match[i, 1]`` of `B`. Accordingly, ``partial_match`` is an array of size ``(m , 2)``, where ``m`` is not greater than the number of nodes. partial_guess : 2d-array of integers, optional, (default = None) Allows the user to provide a guess for the matching between the two matrices. Unlike `partial_match`, `partial_guess` does not fix the indices; they are still free to be optimized. Each row of `partial_guess` specifies the indices of a pair of corresponding nodes, that is, node ``partial_guess[i, 0]`` of `A` is matched to node ``partial_guess[i, 1]`` of `B`. Accordingly, ``partial_guess`` is an array of size ``(m , 2)``, where ``m`` is less than or equal to the number of nodes. Returns ------- res : OptimizeResult A :class:`scipy.optimize.OptimizeResult` containing the following fields. col_ind : 1-D array An array of column indices corresponding with the best permutation of the nodes of `B` found. fun : float The corresponding value of the objective function. nit : int The number of iterations performed during optimization. Notes ----- This is a greedy algorithm that works similarly to bubble sort: beginning with an initial permutation, it iteratively swaps pairs of indices to improve the objective function until no such improvements are possible. References ---------- .. [3] "2-opt," Wikipedia. https://en.wikipedia.org/wiki/2-opt """ _check_unknown_options(unknown_options) rng = check_random_state(rng) A, B, partial_match = _common_input_validation(A, B, partial_match) N = len(A) # check outlier cases if N == 0 or partial_match.shape[0] == N: score = _calc_score(A, B, partial_match[:, 1]) res = {"col_ind": partial_match[:, 1], "fun": score, "nit": 0} return OptimizeResult(res) if partial_guess is None: partial_guess = np.array([[], []]).T partial_guess = np.atleast_2d(partial_guess).astype(int) msg = None if partial_guess.shape[0] > A.shape[0]: msg = ("`partial_guess` can have only as " "many entries as there are nodes") elif partial_guess.shape[1] != 2: msg = "`partial_guess` must have two columns" elif partial_guess.ndim != 2: msg = "`partial_guess` must have exactly two dimensions" elif (partial_guess < 0).any(): msg = "`partial_guess` must contain only positive indices" elif (partial_guess >= len(A)).any(): msg = "`partial_guess` entries must be less than number of nodes" elif (not len(set(partial_guess[:, 0])) == len(partial_guess[:, 0]) or not len(set(partial_guess[:, 1])) == len(partial_guess[:, 1])): msg = "`partial_guess` column entries must be unique" if msg is not None: raise ValueError(msg) fixed_rows = None if partial_match.size or partial_guess.size: # use partial_match and partial_guess for initial permutation, # but randomly permute the rest. guess_rows = np.zeros(N, dtype=bool) guess_cols = np.zeros(N, dtype=bool) fixed_rows = np.zeros(N, dtype=bool) fixed_cols = np.zeros(N, dtype=bool) perm = np.zeros(N, dtype=int) rg, cg = partial_guess.T guess_rows[rg] = True guess_cols[cg] = True perm[guess_rows] = cg # match overrides guess rf, cf = partial_match.T fixed_rows[rf] = True fixed_cols[cf] = True perm[fixed_rows] = cf random_rows = ~fixed_rows & ~guess_rows random_cols = ~fixed_cols & ~guess_cols perm[random_rows] = rng.permutation(np.arange(N)[random_cols]) else: perm = rng.permutation(np.arange(N)) best_score = _calc_score(A, B, perm) i_free = np.arange(N) if fixed_rows is not None: i_free = i_free[~fixed_rows] better = operator.gt if maximize else operator.lt n_iter = 0 done = False while not done: # equivalent to nested for loops i in range(N), j in range(i, N) for i, j in itertools.combinations_with_replacement(i_free, 2): n_iter += 1 perm[i], perm[j] = perm[j], perm[i] score = _calc_score(A, B, perm) if better(score, best_score): best_score = score break # faster to swap back than to create a new list every time perm[i], perm[j] = perm[j], perm[i] else: # no swaps made done = True res = {"col_ind": perm, "fun": best_score, "nit": n_iter} return OptimizeResult(res)
def __init__(self, fun, x0, bounds, args=(), seed=None, temperature_start=5230, qv=2.62, qa=-5.0, maxfun=1e7, maxsteps=500, pure_sa=False): self.fun = fun self.args = args self.pure_sa = pure_sa lu = list(zip(*bounds)) self._lower = np.array(lu[0]) self._upper = np.array(lu[1]) if x0 is not None and not len(x0) == len(self._lower): raise ValueError('Bounds size does not match x0') # Checking that bounds are consistent if not np.all(self._lower < self._upper): raise ValueError('Bounds are note consistent min < max') # Initialization of RandomState for reproducible runs if seed provided self._random_state = check_random_state(seed) if x0 is None: x0 = self._lower + self._random_state.random_sample( len(self._lower)) * (self._upper - self._lower) self.seed = seed # Number of maximum sof iteration for local search self.itsoftmax = x0.size * 6 # Size of the markov chain. Twice the dimension problem is a # recommended value. self.markov_length = x0.size * 2 # In case the real value of the global minimum is known # it can be used as stopping criterion self.know_real = False self.real_threshold = -np.inf # Maximum duration time of execution as a stopping criterion # Default is unlimited duration self.maxtime = np.inf # Maximum number of function call that can be used a stopping criterion self.maxfuncall = maxfun # Maximum number of step (main iteration) that ca be used as # stopping criterion self.maxsteps = maxsteps # Minimum value of annealing temperature reached to perform # re-annealing self.temperature_restart = 0.1 # Visiting distribution parameter self.qv = qv # Acceptance parameter value self.qa = qa # Initial temperature value for annealing self.temperature_start = temperature_start # Not yet implemented contraint function that would be used in the # future self.factr = 1000 self.pgtol = 1.e-6 self.reps = 1.e-6 self._x = np.array(x0) self._xrange = self._upper - self._lower self._xbackup = np.array(self._x) self._xmin = np.array(self._x) self._xbuffer = np.zeros(self._x.size) self._temperature = self.temperature_start self._usey = 1 self._xgas = 0.0 self._ygas = 0.0 self._ranbyx = 0.0 self._ranbyy = 0.0 self._sgas = -1.0 self._step_record = 0 self._nbfuncall = 0 self._emin_unchanged = True self._index_no_emin_update = 0 self._temperature_qa = 0 self._initialize()
def sample(self, samples, random_state=None): """ Sample the beam for reflected signal. 2400000 samples roughly corresponds to 1200 sec of *PLATYPUS* using dlambda=3.3 and dtheta=3.3 at angle=0.65. 150000000 samples roughly corresponds to 3600 sec of *PLATYPUS* using dlambda=3.3 and dtheta=3.3 at angle=3.0. (The sample number <--> actual acquisition time correspondence has not been checked fully) Parameters ---------- samples: int How many samples to run. random_state: {int, `~np.random.RandomState`, `~np.random.Generator`}, optional If `random_state` is not specified the `~np.random.RandomState` singleton is used. If `random_state` is an int, a new ``RandomState`` instance is used, seeded with seed. If `random_state` is already a ``RandomState`` or a ``Generator`` instance, then that object is used. Specify `random_state` for repeatable minimizations. """ # grab a random number generator rng = check_random_state(random_state) # generate neutrons of various wavelengths wavelengths = self.spectrum_dist.rvs(size=samples, random_state=rng) # generate neutrons of different angular divergence angles = self.angular_dist.rvs(samples, random_state=rng) + self.angle # angular deviation due to gravity # --> no correction for gravity affecting width of angular resolution if self.gravity: speeds = general.wavelength_velocity(wavelengths) # trajectories through slits for different wavelengths trajectories = pm.find_trajectory(self.L12 / 1000.0, 0, speeds) # elevation at sample elevations = pm.elevation( trajectories, speeds, (self.L12 + self.L2S) / 1000.0 ) angles -= elevations # calculate Q q = general.q(angles, wavelengths) # calculate reflectivities for a neutron of a given Q. # the angular resolution smearing has already been done. The wavelength # resolution smearing follows. r = self.model(q, x_err=0.0) # accept or reject neutrons based on the reflectivity of # sample at a given Q. criterion = rng.uniform(size=samples) accepted = criterion < r # implement wavelength smearing from choppers. Jitter the wavelengths # by a uniform distribution whose full width is dlambda / 0.68. if self.force_gaussian: noise = rng.standard_normal(size=samples) jittered_wavelengths = wavelengths * ( 1 + self.dlambda / 2.3548 * noise ) else: noise = rng.uniform(-0.5, 0.5, size=samples) jittered_wavelengths = wavelengths * ( 1 + self.dlambda / 0.68 * noise ) # update reflected beam counts. Rebin smearing # is taken into account due to the finite size of the wavelength # bins. hist = np.histogram( jittered_wavelengths[accepted], self.wavelength_bins ) self.reflected_beam += hist[0] self.bmon_reflect += float(samples) # update resolution kernel. If we have more than 100000 in all # bins skip if ( len(self._res_kernel) and np.min([len(v) for v in self._res_kernel.values()]) > 500000 ): return bin_loc = np.digitize(jittered_wavelengths, self.wavelength_bins) for i in range(1, len(self.wavelength_bins)): # extract q values that fall in each wavelength bin q_for_bin = np.copy(q[bin_loc == i]) q_samples_so_far = self._res_kernel.get(i - 1, np.array([])) updated_samples = np.concatenate((q_samples_so_far, q_for_bin)) # no need to keep double precision for these sample arrays self._res_kernel[i - 1] = updated_samples.astype(np.float32)
def initialise(self, pos="covar", random_state=None): """ Initialise the emcee walkers. Parameters ---------- pos : str or np.ndarray Method for initialising the emcee walkers. One of: - 'covar', use the estimated covariance of the system. - 'jitter', add a small amount of gaussian noise to each parameter - 'prior', sample random locations from the prior - pos, an array that specifies a snapshot of the walkers. Has shape `(nwalkers, ndim)`, or `(ntemps, nwalkers, ndim)` if parallel tempering is employed. You can also provide a previously created chain. random_state : {int, `np.random.RandomState`, `np.random.Generator`} If `random_state` is not specified the `~np.random.RandomState` singleton is used. If `random_state` is an int, a new ``RandomState`` instance is used, seeded with random_state. If `random_state` is already a ``RandomState`` or a ``Generator`` instance, then that object is used. Specify `random_state` for repeatable initialisations. """ nwalkers = self._nwalkers nvary = self.nvary # acquire a random number generator rng = check_random_state(random_state) # account for parallel tempering _ntemps = self._ntemps # If you're not doing parallel tempering, temporarily set the number of # temperatures to be created to 1, thereby producing initial positions # of (1, nwalkers, nvary), this first dimension should be removed at # the end of the method if self._ntemps == -1: _ntemps = 1 # position is specified with array (no parallel tempering) if (isinstance(pos, np.ndarray) and self._ntemps == -1 and pos.shape == (nwalkers, nvary)): init_walkers = np.copy(pos)[np.newaxis] # position is specified with array (with parallel tempering) elif (isinstance(pos, np.ndarray) and self._ntemps > -1 and pos.shape == (_ntemps, nwalkers, nvary)): init_walkers = np.copy(pos) # position is specified with existing chain elif isinstance(pos, np.ndarray): self.initialise_with_chain(pos) return # position is to be created from covariance matrix elif pos == "covar": p0 = np.array(self._varying_parameters) cov = self.objective.covar() init_walkers = rng.multivariate_normal(np.atleast_1d(p0), np.atleast_2d(cov), size=(_ntemps, nwalkers)) # position is specified by jittering the parameters with gaussian noise elif pos == "jitter": var_arr = np.array(self._varying_parameters) pos = 1 + rng.standard_normal((_ntemps, nwalkers, nvary)) * 1.0e-4 pos *= var_arr init_walkers = pos # use the prior to initialise position elif pos == "prior": arr = np.zeros((_ntemps, nwalkers, nvary)) for i, param in enumerate(self._varying_parameters): # bounds are not a closed interval, just jitter it. if (isinstance(param.bounds, Interval) and not param.bounds._closed_bounds): vals = (1 + rng.standard_normal( (_ntemps, nwalkers)) * 1.0e-1) vals *= param.value arr[..., i] = vals else: arr[..., i] = param.bounds.rvs(size=(_ntemps, nwalkers), random_state=rng) init_walkers = arr else: raise RuntimeError("Didn't use any known method for " "CurveFitter.initialise") # if you're not doing parallel tempering then remove the first # dimension if self._ntemps == -1: init_walkers = init_walkers[0] # now validate initialisation, ensuring all init pos have finite # logpost for i, param in enumerate(self._varying_parameters): init_walkers[..., i] = param.valid(init_walkers[..., i]) rstate0 = None if isinstance(rng, np.random.RandomState): rstate0 = rng.get_state() self._state = State(init_walkers, random_state=rstate0) # finally reset the sampler to reset the chain # you have to do this at the end, not at the start because resetting # makes self.sampler.chain == None and the PTsampler creation doesn't # work self.sampler.reset()
def __init__(self, seed=None): self._random_state = check_random_state(seed)
def _sprandn(m, n, density=0.01, format="coo", dtype=None, random_state=None): # Helper function for testing. random_state = check_random_state(random_state) data_rvs = random_state.standard_normal return construct.random(m, n, density, format, dtype, random_state, data_rvs)
def random_state(self, seed): self._dist._random_state = check_random_state(seed)
def make_blobs(n_samples=100, n_features=2, centers=3, cluster_std=1.0, center_box=(-10.0, 10.0), shuffle=True, random_state=None): """Generate isotropic Gaussian blobs for clustering. Read more in the :ref:`User Guide <sample_generators>`. Parameters ---------- n_samples : int, optional (default=100) The total number of points equally divided among clusters. n_features : int, optional (default=2) The number of features for each sample. centers : int or array of shape [n_centers, n_features], optional (default=3) The number of centers to generate, or the fixed center locations. cluster_std : float or sequence of floats, optional (default=1.0) The standard deviation of the clusters. center_box : pair of floats (min, max), optional (default=(-10.0, 10.0)) The bounding box for each cluster center when centers are generated at random. shuffle : boolean, optional (default=True) Shuffle the samples. random_state : int, RandomState instance or None, optional (default=None) If int, random_state is the seed used by the random number generator; If RandomState instance, random_state is the random number generator; If None, the random number generator is the RandomState instance used by `np.random`. Returns ------- X : array of shape [n_samples, n_features] The generated samples. y : array of shape [n_samples] The integer labels for cluster membership of each sample. """ from scipy._lib._util import check_random_state generator = check_random_state(random_state) if isinstance(centers, numbers.Integral): centers = generator.uniform(center_box[0], center_box[1], size=(centers, n_features)) else: from sklearn.utils import check_array centers = check_array(centers) n_features = centers.shape[1] if isinstance(cluster_std, numbers.Real): cluster_std = np.ones(len(centers)) * cluster_std X = [] y = [] n_centers = centers.shape[0] n_samples_per_center = [int(n_samples // n_centers)] * n_centers for i in range(n_samples % n_centers): n_samples_per_center[i] += 1 for i, (n, std) in enumerate(zip(n_samples_per_center, cluster_std)): X.append(centers[i] + generator.normal(scale=std, size=(n, n_features))) y += [i] * n X = np.concatenate(X) y = np.array(y) if shuffle: indices = np.arange(n_samples) generator.shuffle(indices) X = X[indices] y = y[indices] return X, y
def dual_annealing(func, x0, bounds, args=(), maxiter=1000, local_search_options={}, initial_temp=5230., restart_temp_ratio=2.e-5, visit=2.62, accept=-5.0, maxfun=1e7, seed=None, no_local_search=False, callback=None): """ Find the global minimum of a function using Dual Annealing. Parameters ---------- func : callable The objective function to be minimized. Must be in the form ``f(x, *args)``, where ``x`` is the argument in the form of a 1-D array and ``args`` is a tuple of any additional fixed parameters needed to completely specify the function. x0 : ndarray, shape(n,) A single initial starting point coordinates. If ``None`` is provided, initial coordinates are automatically generated (using the ``reset`` method from the internal ``EnergyState`` class). bounds : sequence, shape (n, 2) Bounds for variables. ``(min, max)`` pairs for each element in ``x``, defining bounds for the objective function parameter. args : tuple, optional Any additional fixed parameters needed to completely specify the objective function. maxiter : int, optional The maximum number of global search iterations. Default value is 1000. local_search_options : dict, optional Extra keyword arguments to be passed to the local minimizer (`minimize`). Some important options could be: ``method`` for the minimizer method to use and ``args`` for objective function additional arguments. initial_temp : float, optional The initial temperature, use higher values to facilitates a wider search of the energy landscape, allowing dual_annealing to escape local minima that it is trapped in. Default value is 5230. Range is (0.01, 5.e4]. restart_temp_ratio : float, optional During the annealing process, temperature is decreasing, when it reaches ``initial_temp * restart_temp_ratio``, the reannealing process is triggered. Default value of the ratio is 2e-5. Range is (0, 1). visit : float, optional Parameter for visiting distribution. Default value is 2.62. Higher values give the visiting distribution a heavier tail, this makes the algorithm jump to a more distant region. The value range is (0, 3]. accept : float, optional Parameter for acceptance distribution. It is used to control the probability of acceptance. The lower the acceptance parameter, the smaller the probability of acceptance. Default value is -5.0 with a range (-1e4, -5]. maxfun : int, optional Soft limit for the number of objective function calls. If the algorithm is in the middle of a local search, this number will be exceeded, the algorithm will stop just after the local search is done. Default value is 1e7. seed : {int or `numpy.random.RandomState` instance}, optional If `seed` is not specified the `numpy.random.RandomState` singleton is used. If `seed` is an int, a new ``RandomState`` instance is used, seeded with `seed`. If `seed` is already a ``RandomState`` instance, then that instance is used. Specify `seed` for repeatable minimizations. The random numbers generated with this seed only affect the visiting distribution function and new coordinates generation. no_local_search : bool, optional If `no_local_search` is set to True, a traditional Generalized Simulated Annealing will be performed with no local search strategy applied. callback : callable, optional A callback function with signature ``callback(x, f, context)``, which will be called for all minima found. ``x`` and ``f`` are the coordinates and function value of the latest minimum found, and ``context`` has value in [0, 1, 2], with the following meaning: - 0: minimum detected in the annealing process. - 1: detection occured in the local search process. - 2: detection done in the dual annealing process. If the callback implementation returns True, the algorithm will stop. Returns ------- res : OptimizeResult The optimization result represented as a `OptimizeResult` object. Important attributes are: ``x`` the solution array, ``fun`` the value of the function at the solution, and ``message`` which describes the cause of the termination. See `OptimizeResult` for a description of other attributes. Notes ----- This function implements the Dual Annealing optimization. This stochastic approach derived from [3]_ combines the generalization of CSA (Classical Simulated Annealing) and FSA (Fast Simulated Annealing) [1]_ [2]_ coupled to a strategy for applying a local search on accepted locations [4]_. An alternative implementation of this same algorithm is described in [5]_ and benchmarks are presented in [6]_. This approach introduces an advanced method to refine the solution found by the generalized annealing process. This algorithm uses a distorted Cauchy-Lorentz visiting distribution, with its shape controlled by the parameter :math:`q_{v}` .. math:: g_{q_{v}}(\\Delta x(t)) \\propto \\frac{ \\ \\left[T_{q_{v}}(t) \\right]^{-\\frac{D}{3-q_{v}}}}{ \\ \\left[{1+(q_{v}-1)\\frac{(\\Delta x(t))^{2}} { \\ \\left[T_{q_{v}}(t)\\right]^{\\frac{2}{3-q_{v}}}}}\\right]^{ \\ \\frac{1}{q_{v}-1}+\\frac{D-1}{2}}} Where :math:`t` is the artificial time. This visiting distribution is used to generate a trial jump distance :math:`\\Delta x(t)` of variable :math:`x(t)` under artificial temperature :math:`T_{q_{v}}(t)`. From the starting point, after calling the visiting distribution function, the acceptance probability is computed as follows: .. math:: p_{q_{a}} = \\min{\\{1,\\left[1-(1-q_{a}) \\beta \\Delta E \\right]^{ \\ \\frac{1}{1-q_{a}}}\\}} Where :math:`q_{a}` is a acceptance parameter. For :math:`q_{a}<1`, zero acceptance probability is assigned to the cases where .. math:: [1-(1-q_{a}) \\beta \\Delta E] < 0 The artificial temperature :math:`T_{q_{v}}(t)` is decreased according to .. math:: T_{q_{v}}(t) = T_{q_{v}}(1) \\frac{2^{q_{v}-1}-1}{\\left( \\ 1 + t\\right)^{q_{v}-1}-1} Where :math:`q_{v}` is the visiting parameter. .. versionadded:: 1.2.0 References ---------- .. [1] Tsallis C. Possible generalization of Boltzmann-Gibbs statistics. Journal of Statistical Physics, 52, 479-487 (1998). .. [2] Tsallis C, Stariolo DA. Generalized Simulated Annealing. Physica A, 233, 395-406 (1996). .. [3] Xiang Y, Sun DY, Fan W, Gong XG. Generalized Simulated Annealing Algorithm and Its Application to the Thomson Model. Physics Letters A, 233, 216-220 (1997). .. [4] Xiang Y, Gong XG. Efficiency of Generalized Simulated Annealing. Physical Review E, 62, 4473 (2000). .. [5] Xiang Y, Gubian S, Suomela B, Hoeng J. Generalized Simulated Annealing for Efficient Global Optimization: the GenSA Package for R. The R Journal, Volume 5/1 (2013). .. [6] Mullen, K. Continuous Global Optimization in R. Journal of Statistical Software, 60(6), 1 - 45, (2014). DOI:10.18637/jss.v060.i06 Examples -------- The following example is a 10-dimensional problem, with many local minima. The function involved is called Rastrigin (https://en.wikipedia.org/wiki/Rastrigin_function) >>> from scipy.optimize import dual_annealing >>> func = lambda x: np.sum(x*x - 10*np.cos(2*np.pi*x)) + 10*np.size(x) >>> lw = [-5.12] * 10 >>> up = [5.12] * 10 >>> ret = dual_annealing(func, None, bounds=list(zip(lw, up)), seed=1234) >>> print("global minimum: xmin = {0}, f(xmin) = {1:.6f}".format( ... ret.x, ret.fun)) global minimum: xmin = [-4.26437714e-09 -3.91699361e-09 -1.86149218e-09 -3.97165720e-09 -6.29151648e-09 -6.53145322e-09 -3.93616815e-09 -6.55623025e-09 -6.05775280e-09 -5.00668935e-09], f(xmin) = 0.000000 """ if x0 is not None and not len(x0) == len(bounds): raise ValueError('Bounds size does not match x0') lu = list(zip(*bounds)) lower = np.array(lu[0]) upper = np.array(lu[1]) # Check that restart temperature ratio is correct if restart_temp_ratio <= 0. or restart_temp_ratio >= 1.: raise ValueError('Restart temperature ratio has to be in range (0, 1)') # Checking bounds are valid if (np.any(np.isinf(lower)) or np.any(np.isinf(upper)) or np.any( np.isnan(lower)) or np.any(np.isnan(upper))): raise ValueError('Some bounds values are inf values or nan values') # Checking that bounds are consistent if not np.all(lower < upper): raise ValueError('Bounds are note consistent min < max') # Wrapper for the objective function func_wrapper = ObjectiveFunWrapper(func, maxfun, *args) # Wrapper fot the minimizer minimizer_wrapper = LocalSearchWrapper( bounds, func_wrapper, **local_search_options) # Initialization of RandomState for reproducible runs if seed provided rand_state = check_random_state(seed) # Initialization of the energy state energy_state = EnergyState(lower, upper, callback) energy_state.reset(func_wrapper, rand_state, x0) # Minimum value of annealing temperature reached to perform # re-annealing temperature_restart = initial_temp * restart_temp_ratio # VisitingDistribution instance visit_dist = VisitingDistribution(lower, upper, visit, rand_state) # Strategy chain instance strategy_chain = StrategyChain(accept, visit_dist, func_wrapper, minimizer_wrapper, rand_state, energy_state) # Run the search loop need_to_stop = False iteration = 0 message = [] t1 = np.exp((visit - 1) * np.log(2.0)) - 1.0 while(not need_to_stop): for i in range(maxiter): # Compute temperature for this step s = float(i) + 2.0 t2 = np.exp((visit - 1) * np.log(s)) - 1.0 temperature = initial_temp * t1 / t2 iteration += 1 if iteration >= maxiter: message.append("Maximum number of iteration reached") need_to_stop = True break # Need a re-annealing process? if temperature < temperature_restart: energy_state.reset(func_wrapper, rand_state) break # starting strategy chain val = strategy_chain.run(i, temperature) if val is not None: message.append(val) need_to_stop = True break # Possible local search at the end of the strategy chain if not no_local_search: val = strategy_chain.local_search() if val is not None: message.append(val) need_to_stop = True break # Return the OptimizeResult res = OptimizeResult() res.x = energy_state.xbest res.fun = energy_state.ebest res.nit = iteration res.nfev = func_wrapper.nfev res.njev = func_wrapper.ngev res.message = message return res
def __init__(self, T, random_state=None): self.beta = 1.0 / T self.random_state = check_random_state(random_state)
def __init__(self, func, bounds, args=(), strategy='best1bin', maxiter=1000, popsize=15, tol=0.01, mutation=(0.5, 1), recombination=0.7, seed=None, maxfun=np.inf, callback=None, disp=False, polish=True, init='latinhypercube', atol=0): if strategy in self._binomial: self.mutation_func = getattr(self, self._binomial[strategy]) elif strategy in self._exponential: self.mutation_func = getattr(self, self._exponential[strategy]) else: raise ValueError("Please select a valid mutation strategy") self.strategy = strategy self.callback = callback self.polish = polish # relative and absolute tolerances for convergence self.tol, self.atol = tol, atol # Mutation constant should be in [0, 2). If specified as a sequence # then dithering is performed. self.scale = mutation if (not np.all(np.isfinite(mutation)) or np.any(np.array(mutation) >= 2) or np.any(np.array(mutation) < 0)): raise ValueError('The mutation constant must be a float in ' 'U[0, 2), or specified as a tuple(min, max)' ' where min < max and min, max are in U[0, 2).') self.dither = None if hasattr(mutation, '__iter__') and len(mutation) > 1: self.dither = [mutation[0], mutation[1]] self.dither.sort() self.cross_over_probability = recombination self.func = func self.args = args # convert tuple of lower and upper bounds to limits # [(low_0, high_0), ..., (low_n, high_n] # -> [[low_0, ..., low_n], [high_0, ..., high_n]] self.limits = np.array(bounds, dtype='float').T if (np.size(self.limits, 0) != 2 or not np.all(np.isfinite(self.limits))): raise ValueError('bounds should be a sequence containing ' 'real valued (min, max) pairs for each value' ' in x') if maxiter is None: # the default used to be None maxiter = 1000 self.maxiter = maxiter if maxfun is None: # the default used to be None maxfun = np.inf self.maxfun = maxfun # population is scaled to between [0, 1]. # We have to scale between parameter <-> population # save these arguments for _scale_parameter and # _unscale_parameter. This is an optimization self.__scale_arg1 = 0.5 * (self.limits[0] + self.limits[1]) self.__scale_arg2 = np.fabs(self.limits[0] - self.limits[1]) self.parameter_count = np.size(self.limits, 1) self.random_number_generator = check_random_state(seed) # default population initialization is a latin hypercube design, but # there are other population initializations possible. self.num_population_members = popsize * self.parameter_count self.population_shape = (self.num_population_members, self.parameter_count) self._nfev = 0 if init == 'latinhypercube': self.init_population_lhs() elif init == 'random': self.init_population_random() else: raise ValueError("The population initialization method must be one" "of 'latinhypercube' or 'random'") self.disp = disp
def kmeans2(data, k, iter=10, thresh=1e-5, minit='random', missing='warn', check_finite=True, *, seed=None): """ Classify a set of observations into k clusters using the k-means algorithm. The algorithm attempts to minimize the Euclidean distance between observations and centroids. Several initialization methods are included. Parameters ---------- data : ndarray A 'M' by 'N' array of 'M' observations in 'N' dimensions or a length 'M' array of 'M' 1-D observations. k : int or ndarray The number of clusters to form as well as the number of centroids to generate. If `minit` initialization string is 'matrix', or if a ndarray is given instead, it is interpreted as initial cluster to use instead. iter : int, optional Number of iterations of the k-means algorithm to run. Note that this differs in meaning from the iters parameter to the kmeans function. thresh : float, optional (not used yet) minit : str, optional Method for initialization. Available methods are 'random', 'points', '++' and 'matrix': 'random': generate k centroids from a Gaussian with mean and variance estimated from the data. 'points': choose k observations (rows) at random from data for the initial centroids. '++': choose k observations accordingly to the kmeans++ method (careful seeding) 'matrix': interpret the k parameter as a k by M (or length k array for 1-D data) array of initial centroids. missing : str, optional Method to deal with empty clusters. Available methods are 'warn' and 'raise': 'warn': give a warning and continue. 'raise': raise an ClusterError and terminate the algorithm. check_finite : bool, optional Whether to check that the input matrices contain only finite numbers. Disabling may give a performance gain, but may result in problems (crashes, non-termination) if the inputs do contain infinities or NaNs. Default: True seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional Seed for initializing the pseudo-random number generator. If `seed` is None (or `numpy.random`), the `numpy.random.RandomState` singleton is used. If `seed` is an int, a new ``RandomState`` instance is used, seeded with `seed`. If `seed` is already a ``Generator`` or ``RandomState`` instance then that instance is used. The default is None. Returns ------- centroid : ndarray A 'k' by 'N' array of centroids found at the last iteration of k-means. label : ndarray label[i] is the code or index of the centroid the ith observation is closest to. See Also -------- kmeans References ---------- .. [1] D. Arthur and S. Vassilvitskii, "k-means++: the advantages of careful seeding", Proceedings of the Eighteenth Annual ACM-SIAM Symposium on Discrete Algorithms, 2007. Examples -------- >>> from scipy.cluster.vq import kmeans2 >>> import matplotlib.pyplot as plt Create z, an array with shape (100, 2) containing a mixture of samples from three multivariate normal distributions. >>> rng = np.random.default_rng() >>> a = rng.multivariate_normal([0, 6], [[2, 1], [1, 1.5]], size=45) >>> b = rng.multivariate_normal([2, 0], [[1, -1], [-1, 3]], size=30) >>> c = rng.multivariate_normal([6, 4], [[5, 0], [0, 1.2]], size=25) >>> z = np.concatenate((a, b, c)) >>> rng.shuffle(z) Compute three clusters. >>> centroid, label = kmeans2(z, 3, minit='points') >>> centroid array([[ 2.22274463, -0.61666946], # may vary [ 0.54069047, 5.86541444], [ 6.73846769, 4.01991898]]) How many points are in each cluster? >>> counts = np.bincount(label) >>> counts array([29, 51, 20]) # may vary Plot the clusters. >>> w0 = z[label == 0] >>> w1 = z[label == 1] >>> w2 = z[label == 2] >>> plt.plot(w0[:, 0], w0[:, 1], 'o', alpha=0.5, label='cluster 0') >>> plt.plot(w1[:, 0], w1[:, 1], 'd', alpha=0.5, label='cluster 1') >>> plt.plot(w2[:, 0], w2[:, 1], 's', alpha=0.5, label='cluster 2') >>> plt.plot(centroid[:, 0], centroid[:, 1], 'k*', label='centroids') >>> plt.axis('equal') >>> plt.legend(shadow=True) >>> plt.show() """ if int(iter) < 1: raise ValueError("Invalid iter (%s), " "must be a positive integer." % iter) try: miss_meth = _valid_miss_meth[missing] except KeyError as e: raise ValueError("Unknown missing method %r" % (missing, )) from e data = _asarray_validated(data, check_finite=check_finite) if data.ndim == 1: d = 1 elif data.ndim == 2: d = data.shape[1] else: raise ValueError("Input of rank > 2 is not supported.") if data.size < 1: raise ValueError("Empty input is not supported.") # If k is not a single value, it should be compatible with data's shape if minit == 'matrix' or not np.isscalar(k): code_book = np.array(k, copy=True) if data.ndim != code_book.ndim: raise ValueError("k array doesn't match data rank") nc = len(code_book) if data.ndim > 1 and code_book.shape[1] != d: raise ValueError("k array doesn't match data dimension") else: nc = int(k) if nc < 1: raise ValueError("Cannot ask kmeans2 for %d clusters" " (k was %s)" % (nc, k)) elif nc != k: warnings.warn("k was not an integer, was converted.") try: init_meth = _valid_init_meth[minit] except KeyError as e: raise ValueError("Unknown init method %r" % (minit, )) from e else: rng = check_random_state(seed) code_book = init_meth(data, k, rng) for i in range(iter): # Compute the nearest neighbor for each obs using the current code book label = vq(data, code_book)[0] # Update the code book by computing centroids new_code_book, has_members = _vq.update_cluster_means(data, label, nc) if not has_members.all(): miss_meth() # Set the empty clusters to their previous positions new_code_book[~has_members] = code_book[~has_members] code_book = new_code_book return code_book, label
def __init__(self, stepsize=0.5, random_gen=None): self.stepsize = stepsize self.random_gen = check_random_state(random_gen)
def _bootstrap_iv(data, statistic, vectorized, paired, axis, confidence_level, n_resamples, batch, method, random_state): """Input validation and standardization for `bootstrap`.""" if vectorized not in {True, False}: raise ValueError("`vectorized` must be `True` or `False`.") if not vectorized: statistic = _vectorize_statistic(statistic) axis_int = int(axis) if axis != axis_int: raise ValueError("`axis` must be an integer.") n_samples = 0 try: n_samples = len(data) except TypeError: raise ValueError("`data` must be a sequence of samples.") if n_samples == 0: raise ValueError("`data` must contain at least one sample.") data_iv = [] for sample in data: sample = np.atleast_1d(sample) if sample.shape[axis_int] <= 1: raise ValueError("each sample in `data` must contain two or more " "observations along `axis`.") sample = np.moveaxis(sample, axis_int, -1) data_iv.append(sample) if paired not in {True, False}: raise ValueError("`paired` must be `True` or `False`.") if paired: n = data_iv[0].shape[-1] for sample in data_iv[1:]: if sample.shape[-1] != n: message = ("When `paired is True`, all samples must have the " "same length along `axis`") raise ValueError(message) # to generate the bootstrap distribution for paired-sample statistics, # resample the indices of the observations def statistic(i, axis=-1, data=data_iv, unpaired_statistic=statistic): data = [sample[..., i] for sample in data] return unpaired_statistic(*data, axis=axis) data_iv = [np.arange(n)] confidence_level_float = float(confidence_level) n_resamples_int = int(n_resamples) if n_resamples != n_resamples_int or n_resamples_int <= 0: raise ValueError("`n_resamples` must be a positive integer.") if batch is None: batch_iv = batch else: batch_iv = int(batch) if batch != batch_iv or batch_iv <= 0: raise ValueError("`batch` must be a positive integer or None.") methods = {'percentile', 'basic', 'bca'} method = method.lower() if method not in methods: raise ValueError(f"`method` must be in {methods}") message = "`method = 'BCa' is only available for one-sample statistics" if not paired and n_samples > 1 and method == 'bca': raise ValueError(message) random_state = check_random_state(random_state) return (data_iv, statistic, vectorized, paired, axis_int, confidence_level_float, n_resamples_int, batch_iv, method, random_state)
def _iv(A, k, ncv, tol, which, v0, maxiter, return_singular, solver, random_state): # input validation/standardization for `solver` # out of order because it's needed for other parameters solver = str(solver).lower() solvers = {"arpack", "lobpcg", "propack"} if solver not in solvers: raise ValueError(f"solver must be one of {solvers}.") # input validation/standardization for `A` A = aslinearoperator(A) # this takes care of some input validation if not (np.issubdtype(A.dtype, np.complexfloating) or np.issubdtype(A.dtype, np.floating)): message = "`A` must be of floating or complex floating data type." raise ValueError(message) if np.prod(A.shape) == 0: message = "`A` must not be empty." raise ValueError(message) # input validation/standardization for `k` kmax = min(A.shape) if solver == 'propack' else min(A.shape) - 1 if int(k) != k or not (0 < k <= kmax): message = "`k` must be an integer satisfying `0 < k < min(A.shape)`." raise ValueError(message) k = int(k) # input validation/standardization for `ncv` if solver == "arpack" and ncv is not None: if int(ncv) != ncv or not (k < ncv < min(A.shape)): message = ("`ncv` must be an integer satisfying " "`k < ncv < min(A.shape)`.") raise ValueError(message) ncv = int(ncv) # input validation/standardization for `tol` if tol < 0 or not np.isfinite(tol): message = "`tol` must be a non-negative floating point value." raise ValueError(message) tol = float(tol) # input validation/standardization for `which` which = str(which).upper() whichs = {'LM', 'SM'} if which not in whichs: raise ValueError(f"`which` must be in {whichs}.") # input validation/standardization for `v0` if v0 is not None: v0 = np.atleast_1d(v0) if not (np.issubdtype(v0.dtype, np.complexfloating) or np.issubdtype(v0.dtype, np.floating)): message = ("`v0` must be of floating or complex floating " "data type.") raise ValueError(message) shape = (A.shape[0], ) if solver == 'propack' else (min(A.shape), ) if v0.shape != shape: message = "`v0` must have shape {shape}." raise ValueError(message) # input validation/standardization for `maxiter` if maxiter is not None and (int(maxiter) != maxiter or maxiter <= 0): message = "`maxiter` must be a positive integer." raise ValueError(message) maxiter = int(maxiter) if maxiter is not None else maxiter # input validation/standardization for `return_singular_vectors` # not going to be flexible with this; too complicated for little gain rs_options = {True, False, "vh", "u"} if return_singular not in rs_options: raise ValueError(f"`return_singular_vectors` must be in {rs_options}.") random_state = check_random_state(random_state) return (A, k, ncv, tol, which, v0, maxiter, return_singular, solver, random_state)
def __init__(self, seed=None): super(multi_rv_generic, self).__init__() self._random_state = check_random_state(seed)
def __init__(self, stepsize=0.5, random_state=None): self.stepsize = stepsize self.random_state = check_random_state(random_state)
def __init__(self, T, random_state=None): # Avoid ZeroDivisionError since "MBH can be regarded as a special case # of the BH framework with the Metropolis criterion, where temperature # T = 0." (Reject all steps that increase energy.) self.beta = 1.0 / T if T != 0 else float('inf') self.random_state = check_random_state(random_state)
def __init__(self, random_state): self._random_state = check_random_state(random_state)
def basinhopping(func, x0, niter=100, T=1.0, stepsize=0.5, minimizer_kwargs=None, take_step=None, accept_test=None, callback=None, interval=50, disp=False, niter_success=None, seed=None): """ Find the global minimum of a function using the basin-hopping algorithm Parameters ---------- func : callable ``f(x, *args)`` Function to be optimized. ``args`` can be passed as an optional item in the dict ``minimizer_kwargs`` x0 : array_like Initial guess. niter : integer, optional The number of basin-hopping iterations T : float, optional The "temperature" parameter for the accept or reject criterion. Higher "temperatures" mean that larger jumps in function value will be accepted. For best results ``T`` should be comparable to the separation (in function value) between local minima. stepsize : float, optional Maximum step size for use in the random displacement. minimizer_kwargs : dict, optional Extra keyword arguments to be passed to the local minimizer ``scipy.optimize.minimize()`` Some important options could be: method : str The minimization method (e.g. ``"L-BFGS-B"``) args : tuple Extra arguments passed to the objective function (``func``) and its derivatives (Jacobian, Hessian). take_step : callable ``take_step(x)``, optional Replace the default step-taking routine with this routine. The default step-taking routine is a random displacement of the coordinates, but other step-taking algorithms may be better for some systems. ``take_step`` can optionally have the attribute ``take_step.stepsize``. If this attribute exists, then ``basinhopping`` will adjust ``take_step.stepsize`` in order to try to optimize the global minimum search. accept_test : callable, ``accept_test(f_new=f_new, x_new=x_new, f_old=fold, x_old=x_old)``, optional Define a test which will be used to judge whether or not to accept the step. This will be used in addition to the Metropolis test based on "temperature" ``T``. The acceptable return values are True, False, or ``"force accept"``. If any of the tests return False then the step is rejected. If the latter, then this will override any other tests in order to accept the step. This can be used, for example, to forcefully escape from a local minimum that ``basinhopping`` is trapped in. callback : callable, ``callback(x, f, accept)``, optional A callback function which will be called for all minima found. ``x`` and ``f`` are the coordinates and function value of the trial minimum, and ``accept`` is whether or not that minimum was accepted. This can be used, for example, to save the lowest N minima found. Also, ``callback`` can be used to specify a user defined stop criterion by optionally returning True to stop the ``basinhopping`` routine. interval : integer, optional interval for how often to update the ``stepsize`` disp : bool, optional Set to True to print status messages niter_success : integer, optional Stop the run if the global minimum candidate remains the same for this number of iterations. seed : int or `np.random.RandomState`, optional If `seed` is not specified the `np.RandomState` singleton is used. If `seed` is an int, a new `np.random.RandomState` instance is used, seeded with seed. If `seed` is already a `np.random.RandomState instance`, then that `np.random.RandomState` instance is used. Specify `seed` for repeatable minimizations. The random numbers generated with this seed only affect the default Metropolis `accept_test` and the default `take_step`. If you supply your own `take_step` and `accept_test`, and these functions use random number generation, then those functions are responsible for the state of their random number generator. Returns ------- res : OptimizeResult The optimization result represented as a ``OptimizeResult`` object. Important attributes are: ``x`` the solution array, ``fun`` the value of the function at the solution, and ``message`` which describes the cause of the termination. The ``OptimizeResult`` object returned by the selected minimizer at the lowest minimum is also contained within this object and can be accessed through the ``lowest_optimization_result`` attribute. See `OptimizeResult` for a description of other attributes. See Also -------- minimize : The local minimization function called once for each basinhopping step. ``minimizer_kwargs`` is passed to this routine. Notes ----- Basin-hopping is a stochastic algorithm which attempts to find the global minimum of a smooth scalar function of one or more variables [1]_ [2]_ [3]_ [4]_. The algorithm in its current form was described by David Wales and Jonathan Doye [2]_ http://www-wales.ch.cam.ac.uk/. The algorithm is iterative with each cycle composed of the following features 1) random perturbation of the coordinates 2) local minimization 3) accept or reject the new coordinates based on the minimized function value The acceptance test used here is the Metropolis criterion of standard Monte Carlo algorithms, although there are many other possibilities [3]_. This global minimization method has been shown to be extremely efficient for a wide variety of problems in physics and chemistry. It is particularly useful when the function has many minima separated by large barriers. See the Cambridge Cluster Database http://www-wales.ch.cam.ac.uk/CCD.html for databases of molecular systems that have been optimized primarily using basin-hopping. This database includes minimization problems exceeding 300 degrees of freedom. See the free software program GMIN (http://www-wales.ch.cam.ac.uk/GMIN) for a Fortran implementation of basin-hopping. This implementation has many different variations of the procedure described above, including more advanced step taking algorithms and alternate acceptance criterion. For stochastic global optimization there is no way to determine if the true global minimum has actually been found. Instead, as a consistency check, the algorithm can be run from a number of different random starting points to ensure the lowest minimum found in each example has converged to the global minimum. For this reason ``basinhopping`` will by default simply run for the number of iterations ``niter`` and return the lowest minimum found. It is left to the user to ensure that this is in fact the global minimum. Choosing ``stepsize``: This is a crucial parameter in ``basinhopping`` and depends on the problem being solved. Ideally it should be comparable to the typical separation between local minima of the function being optimized. ``basinhopping`` will, by default, adjust ``stepsize`` to find an optimal value, but this may take many iterations. You will get quicker results if you set a sensible value for ``stepsize``. Choosing ``T``: The parameter ``T`` is the temperature used in the Metropolis criterion. Basinhopping steps are accepted with probability ``1`` if ``func(xnew) < func(xold)``, or otherwise with probability:: exp( -(func(xnew) - func(xold)) / T ) So, for best results, ``T`` should to be comparable to the typical difference in function values between local minima. If ``T`` is 0, the algorithm becomes Monotonic Basin-Hopping, in which all steps that increase energy are rejected. .. versionadded:: 0.12.0 References ---------- .. [1] Wales, David J. 2003, Energy Landscapes, Cambridge University Press, Cambridge, UK. .. [2] Wales, D J, and Doye J P K, Global Optimization by Basin-Hopping and the Lowest Energy Structures of Lennard-Jones Clusters Containing up to 110 Atoms. Journal of Physical Chemistry A, 1997, 101, 5111. .. [3] Li, Z. and Scheraga, H. A., Monte Carlo-minimization approach to the multiple-minima problem in protein folding, Proc. Natl. Acad. Sci. USA, 1987, 84, 6611. .. [4] Wales, D. J. and Scheraga, H. A., Global optimization of clusters, crystals, and biomolecules, Science, 1999, 285, 1368. Examples -------- The following example is a one-dimensional minimization problem, with many local minima superimposed on a parabola. >>> from scipy.optimize import basinhopping >>> func = lambda x: np.cos(14.5 * x - 0.3) + (x + 0.2) * x >>> x0=[1.] Basinhopping, internally, uses a local minimization algorithm. We will use the parameter ``minimizer_kwargs`` to tell basinhopping which algorithm to use and how to set up that minimizer. This parameter will be passed to ``scipy.optimize.minimize()``. >>> minimizer_kwargs = {"method": "BFGS"} >>> ret = basinhopping(func, x0, minimizer_kwargs=minimizer_kwargs, ... niter=200) >>> print("global minimum: x = %.4f, f(x0) = %.4f" % (ret.x, ret.fun)) global minimum: x = -0.1951, f(x0) = -1.0009 Next consider a two-dimensional minimization problem. Also, this time we will use gradient information to significantly speed up the search. >>> def func2d(x): ... f = np.cos(14.5 * x[0] - 0.3) + (x[1] + 0.2) * x[1] + (x[0] + ... 0.2) * x[0] ... df = np.zeros(2) ... df[0] = -14.5 * np.sin(14.5 * x[0] - 0.3) + 2. * x[0] + 0.2 ... df[1] = 2. * x[1] + 0.2 ... return f, df We'll also use a different local minimization algorithm. Also we must tell the minimizer that our function returns both energy and gradient (jacobian) >>> minimizer_kwargs = {"method":"L-BFGS-B", "jac":True} >>> x0 = [1.0, 1.0] >>> ret = basinhopping(func2d, x0, minimizer_kwargs=minimizer_kwargs, ... niter=200) >>> print("global minimum: x = [%.4f, %.4f], f(x0) = %.4f" % (ret.x[0], ... ret.x[1], ... ret.fun)) global minimum: x = [-0.1951, -0.1000], f(x0) = -1.0109 Here is an example using a custom step-taking routine. Imagine you want the first coordinate to take larger steps than the rest of the coordinates. This can be implemented like so: >>> class MyTakeStep(object): ... def __init__(self, stepsize=0.5): ... self.stepsize = stepsize ... def __call__(self, x): ... s = self.stepsize ... x[0] += np.random.uniform(-2.*s, 2.*s) ... x[1:] += np.random.uniform(-s, s, x[1:].shape) ... return x Since ``MyTakeStep.stepsize`` exists basinhopping will adjust the magnitude of ``stepsize`` to optimize the search. We'll use the same 2-D function as before >>> mytakestep = MyTakeStep() >>> ret = basinhopping(func2d, x0, minimizer_kwargs=minimizer_kwargs, ... niter=200, take_step=mytakestep) >>> print("global minimum: x = [%.4f, %.4f], f(x0) = %.4f" % (ret.x[0], ... ret.x[1], ... ret.fun)) global minimum: x = [-0.1951, -0.1000], f(x0) = -1.0109 Now let's do an example using a custom callback function which prints the value of every minimum found >>> def print_fun(x, f, accepted): ... print("at minimum %.4f accepted %d" % (f, int(accepted))) We'll run it for only 10 basinhopping steps this time. >>> np.random.seed(1) >>> ret = basinhopping(func2d, x0, minimizer_kwargs=minimizer_kwargs, ... niter=10, callback=print_fun) at minimum 0.4159 accepted 1 at minimum -0.9073 accepted 1 at minimum -0.1021 accepted 1 at minimum -0.1021 accepted 1 at minimum 0.9102 accepted 1 at minimum 0.9102 accepted 1 at minimum 2.2945 accepted 0 at minimum -0.1021 accepted 1 at minimum -1.0109 accepted 1 at minimum -1.0109 accepted 1 The minimum at -1.0109 is actually the global minimum, found already on the 8th iteration. Now let's implement bounds on the problem using a custom ``accept_test``: >>> class MyBounds(object): ... def __init__(self, xmax=[1.1,1.1], xmin=[-1.1,-1.1] ): ... self.xmax = np.array(xmax) ... self.xmin = np.array(xmin) ... def __call__(self, **kwargs): ... x = kwargs["x_new"] ... tmax = bool(np.all(x <= self.xmax)) ... tmin = bool(np.all(x >= self.xmin)) ... return tmax and tmin >>> mybounds = MyBounds() >>> ret = basinhopping(func2d, x0, minimizer_kwargs=minimizer_kwargs, ... niter=10, accept_test=mybounds) """ x0 = np.array(x0) # set up the np.random.RandomState generator rng = check_random_state(seed) # set up minimizer if minimizer_kwargs is None: minimizer_kwargs = dict() wrapped_minimizer = MinimizerWrapper(scipy.optimize.minimize, func, **minimizer_kwargs) # set up step-taking algorithm if take_step is not None: if not isinstance(take_step, collections.Callable): raise TypeError("take_step must be callable") # if take_step.stepsize exists then use AdaptiveStepsize to control # take_step.stepsize if hasattr(take_step, "stepsize"): take_step_wrapped = AdaptiveStepsize(take_step, interval=interval, verbose=disp) else: take_step_wrapped = take_step else: # use default displace = RandomDisplacement(stepsize=stepsize, random_state=rng) take_step_wrapped = AdaptiveStepsize(displace, interval=interval, verbose=disp) # set up accept tests if accept_test is not None: if not isinstance(accept_test, collections.Callable): raise TypeError("accept_test must be callable") accept_tests = [accept_test] else: accept_tests = [] # use default metropolis = Metropolis(T, random_state=rng) accept_tests.append(metropolis) if niter_success is None: niter_success = niter + 2 bh = BasinHoppingRunner(x0, wrapped_minimizer, take_step_wrapped, accept_tests, disp=disp) # start main iteration loop count, i = 0, 0 message = ["requested number of basinhopping iterations completed" " successfully"] for i in range(niter): new_global_min = bh.one_cycle() if isinstance(callback, collections.Callable): # should we pass a copy of x? val = callback(bh.xtrial, bh.energy_trial, bh.accept) if val is not None: if val: message = ["callback function requested stop early by" "returning True"] break count += 1 if new_global_min: count = 0 elif count > niter_success: message = ["success condition satisfied"] break # prepare return object res = bh.res res.lowest_optimization_result = bh.storage.get_lowest() res.x = np.copy(res.lowest_optimization_result.x) res.fun = res.lowest_optimization_result.fun res.message = message res.nit = i + 1 return res
def random_state(self, seed): self._random_state = check_random_state(seed)
def rvs_ratio_uniforms(pdf, umax, vmin, vmax, size=1, c=0, random_state=None): """ Generate random samples from a probability density function using the ratio-of-uniforms method. Parameters ---------- pdf : callable A function with signature `pdf(x)` that is the probability density function of the distribution. umax : float The upper bound of the bounding rectangle in the u-direction. vmin : float The lower bound of the bounding rectangle in the v-direction. vmax : float The upper bound of the bounding rectangle in the v-direction. size : int or tuple of ints, optional Defining number of random variates (default is 1). c : float, optional. Shift parameter of ratio-of-uniforms method, see Notes. Default is 0. random_state : int or np.random.RandomState instance, optional If already a RandomState instance, use it. If seed is an int, return a new RandomState instance seeded with seed. If None, use np.random.RandomState. Default is None. Returns ------- rvs : ndarray The random variates distributed according to the probability distribution defined by the pdf. Notes ----- Given a univariate probability density function `pdf` and a constant `c`, define the set ``A = {(u, v) : 0 < u <= sqrt(pdf(v/u + c))}``. If `(U, V)` is a random vector uniformly distributed over `A`, then `V/U + c` follows a distribution according to `pdf`. The above result (see [1]_, [2]_) can be used to sample random variables using only the pdf, i.e. no inversion of the cdf is required. Typical choices of `c` are zero or the mode of `pdf`. The set `A` is a subset of the rectangle ``R = [0, umax] x [vmin, vmax]`` where - ``umax = sup sqrt(pdf(x))`` - ``vmin = inf (x - c) sqrt(pdf(x))`` - ``vmax = sup (x - c) sqrt(pdf(x))`` In particular, these values are finite if `pdf` is bounded and ``x**2 * pdf(x)`` is bounded (i.e. subquadratic tails). One can generate `(U, V)` uniformly on `R` and return `V/U + c` if `(U, V)` are also in `A` which can be directly verified. Intuitively, the method works well if `A` fills up most of the enclosing rectangle such that the probability is high that `(U, V)` lies in `A` whenever it lies in `R` as the number of required iterations becomes too large otherwise. To be more precise, note that the expected number of iterations to draw `(U, V)` uniformly distributed on `R` such that `(U, V)` is also in `A` is given by the ratio ``area(R) / area(A) = 2 * umax * (vmax - vmin)``, using the fact that the area of `A` is equal to 1/2 (Theorem 7.1 in [1]_). A warning is displayed if this ratio is larger than 20. Moreover, if the sampling fails to generate a single random variate after 50000 iterations (i.e. not a single draw is in `A`), an exception is raised. If the bounding rectangle is not correctly specified (i.e. if it does not contain `A`), the algorithm samples from a distribution different from the one given by `pdf`. It is therefore recommended to perform a test such as `~scipy.stats.kstest` as a check. References ---------- .. [1] L. Devroye, "Non-Uniform Random Variate Generation", Springer-Verlag, 1986. .. [2] W. Hoermann and J. Leydold, "Generating generalized inverse Gaussian random variates", Statistics and Computing, 24(4), p. 547--557, 2014. .. [3] A.J. Kinderman and J.F. Monahan, "Computer Generation of Random Variables Using the Ratio of Uniform Deviates", ACM Transactions on Mathematical Software, 3(3), p. 257--260, 1977. Examples -------- >>> from scipy import stats Simulate normally distributed random variables. It is easy to compute the bounding rectangle explicitly in that case. >>> f = stats.norm.pdf >>> v_bound = np.sqrt(f(np.sqrt(2))) * np.sqrt(2) >>> umax, vmin, vmax = np.sqrt(f(0)), -v_bound, v_bound >>> np.random.seed(12345) >>> rvs = stats.rvs_ratio_uniforms(f, umax, vmin, vmax, size=2500) The K-S test confirms that the random variates are indeed normally distributed (normality is not rejected at 5% significance level): >>> stats.kstest(rvs, 'norm')[1] 0.3420173467307603 The exponential distribution provides another example where the bounding rectangle can be determined explicitly. >>> np.random.seed(12345) >>> rvs = stats.rvs_ratio_uniforms(lambda x: np.exp(-x), umax=1, ... vmin=0, vmax=2*np.exp(-1), size=1000) >>> stats.kstest(rvs, 'expon')[1] 0.928454552559516 Sometimes it can be helpful to use a non-zero shift parameter `c`, see e.g. [2]_ above in the case of the generalized inverse Gaussian distribution. """ if vmin >= vmax: raise ValueError("vmin must be smaller than vmax.") if umax <= 0: raise ValueError("umax must be positive.") exp_iter = 2 * (vmax - vmin) * umax # rejection constant (see [1]) if exp_iter > 20: msg = ("The expected number of iterations to generate a single random " "number from the desired distribution is larger than {}, " "potentially causing bad performance.".format(int(exp_iter))) warnings.warn(msg, RuntimeWarning) size1d = tuple(np.atleast_1d(size)) N = np.prod(size1d) # number of rvs needed, reshape upon return # start sampling using ratio of uniforms method rng = check_random_state(random_state) x = np.zeros(N) simulated, i = 0, 1 # loop until N rvs have been generated: expected runtime is finite # to avoid infinite loop, raise exception if not a single rv has been # generated after 50000 tries. even if exp_iter = 1000, probability of # this event is (1-1/1000)**50000 which is of order 10e-22 while True: k = N - simulated # simulate uniform rvs on [0, umax] and [vmin, vmax] u1 = umax * rng.random_sample(size=k) v1 = vmin + (vmax - vmin) * rng.random_sample(size=k) # apply rejection method rvs = v1 / u1 + c accept = (u1**2 <= pdf(rvs)) num_accept = np.sum(accept) if num_accept > 0: take = min(num_accept, N - simulated) x[simulated:(simulated + take)] = rvs[accept][0:take] simulated += take if simulated >= N: return np.reshape(x, size1d) if (simulated == 0) and (i*N >= 50000): msg = ("Not a single random variate could be generated in {} " "attempts. The ratio of uniforms method does not appear " "to work for the provided parameters. Please check the " "pdf and the bounds.".format(i*N)) raise RuntimeError(msg) i += 1
def __init__(self, func, bounds, args=(), strategy='best1bin', maxiter=1000, popsize=15, tol=0.01, mutation=(0.5, 1), recombination=0.7, seed=None, maxfun=np.inf, callback=None, disp=False, polish=True, init='latinhypercube', atol=0, updating='immediate', workers=1): if strategy in self._binomial: self.mutation_func = getattr(self, self._binomial[strategy]) elif strategy in self._exponential: self.mutation_func = getattr(self, self._exponential[strategy]) else: raise ValueError("Please select a valid mutation strategy") self.strategy = strategy self.callback = callback self.polish = polish # set the updating / parallelisation options if updating in ['immediate', 'deferred']: self._updating = updating # want to use parallelisation, but updating is immediate if workers != 1 and updating == 'immediate': warnings.warn("differential_evolution: the 'workers' keyword has" " overridden updating='immediate' to" " updating='deferred'", UserWarning) self._updating = 'deferred' # an object with a map method. self._mapwrapper = MapWrapper(workers) # relative and absolute tolerances for convergence self.tol, self.atol = tol, atol # Mutation constant should be in [0, 2). If specified as a sequence # then dithering is performed. self.scale = mutation if (not np.all(np.isfinite(mutation)) or np.any(np.array(mutation) >= 2) or np.any(np.array(mutation) < 0)): raise ValueError('The mutation constant must be a float in ' 'U[0, 2), or specified as a tuple(min, max)' ' where min < max and min, max are in U[0, 2).') self.dither = None if hasattr(mutation, '__iter__') and len(mutation) > 1: self.dither = [mutation[0], mutation[1]] self.dither.sort() self.cross_over_probability = recombination # we create a wrapped function to allow the use of map (and Pool.map # in the future) self.func = _FunctionWrapper(func, args) self.args = args # convert tuple of lower and upper bounds to limits # [(low_0, high_0), ..., (low_n, high_n] # -> [[low_0, ..., low_n], [high_0, ..., high_n]] self.limits = np.array(bounds, dtype='float').T if (np.size(self.limits, 0) != 2 or not np.all(np.isfinite(self.limits))): raise ValueError('bounds should be a sequence containing ' 'real valued (min, max) pairs for each value' ' in x') if maxiter is None: # the default used to be None maxiter = 1000 self.maxiter = maxiter if maxfun is None: # the default used to be None maxfun = np.inf self.maxfun = maxfun # population is scaled to between [0, 1]. # We have to scale between parameter <-> population # save these arguments for _scale_parameter and # _unscale_parameter. This is an optimization self.__scale_arg1 = 0.5 * (self.limits[0] + self.limits[1]) self.__scale_arg2 = np.fabs(self.limits[0] - self.limits[1]) self.parameter_count = np.size(self.limits, 1) self.random_number_generator = check_random_state(seed) # default population initialization is a latin hypercube design, but # there are other population initializations possible. # the minimum is 5 because 'best2bin' requires a population that's at # least 5 long self.num_population_members = max(5, popsize * self.parameter_count) self.population_shape = (self.num_population_members, self.parameter_count) self._nfev = 0 if isinstance(init, string_types): if init == 'latinhypercube': self.init_population_lhs() elif init == 'random': self.init_population_random() else: raise ValueError(self.__init_error_msg) else: self.init_population_array(init) self.disp = disp